mirror of
https://github.com/enzet/map-machine.git
synced 2025-06-14 08:41:53 +02:00
Merge main.
This commit is contained in:
commit
db1c69fb42
18 changed files with 276 additions and 188 deletions
|
@ -36,6 +36,7 @@
|
||||||
<w>rasterized</w>
|
<w>rasterized</w>
|
||||||
<w>röntgen</w>
|
<w>röntgen</w>
|
||||||
<w>scoria</w>
|
<w>scoria</w>
|
||||||
|
<w>skillion</w>
|
||||||
<w>subattributes</w>
|
<w>subattributes</w>
|
||||||
<w>subelement</w>
|
<w>subelement</w>
|
||||||
<w>subparser</w>
|
<w>subparser</w>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
python_files="map_machine setup.py tests data/githooks/commit-msg"
|
python_files="map_machine setup.py tests data/githooks/commit-msg"
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
echo "Looking for changes..."
|
echo "Looking for changes..."
|
||||||
files=`git status --porcelain | wc -l`
|
files=`git status --porcelain | wc -l`
|
||||||
|
|
|
@ -11,7 +11,7 @@ __url__ = "https://github.com/enzet/map-machine"
|
||||||
__doc_url__ = f"{__url__}/blob/main/README.md"
|
__doc_url__ = f"{__url__}/blob/main/README.md"
|
||||||
__author__ = "Sergey Vartanov"
|
__author__ = "Sergey Vartanov"
|
||||||
__email__ = "me@enzet.ru"
|
__email__ = "me@enzet.ru"
|
||||||
__version__ = "0.1.4"
|
__version__ = "0.1.5"
|
||||||
|
|
||||||
REQUIREMENTS = [
|
REQUIREMENTS = [
|
||||||
"CairoSVG>=2.5.0",
|
"CairoSVG>=2.5.0",
|
||||||
|
|
|
@ -15,10 +15,8 @@ from map_machine.feature.building import Building, BUILDING_SCALE
|
||||||
from map_machine.feature.crater import Crater
|
from map_machine.feature.crater import Crater
|
||||||
from map_machine.feature.direction import DirectionSector
|
from map_machine.feature.direction import DirectionSector
|
||||||
from map_machine.feature.road import Road, Roads
|
from map_machine.feature.road import Road, Roads
|
||||||
from map_machine.figure import (
|
|
||||||
StyledFigure,
|
|
||||||
)
|
|
||||||
from map_machine.feature.tree import Tree
|
from map_machine.feature.tree import Tree
|
||||||
|
from map_machine.figure import StyledFigure
|
||||||
from map_machine.geometry.flinger import Flinger
|
from map_machine.geometry.flinger import Flinger
|
||||||
from map_machine.map_configuration import DrawingMode, MapConfiguration
|
from map_machine.map_configuration import DrawingMode, MapConfiguration
|
||||||
from map_machine.osm.osm_reader import (
|
from map_machine.osm.osm_reader import (
|
||||||
|
|
|
@ -9,8 +9,8 @@ from typing import Any, Optional, List, Dict, Set
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import svgwrite
|
import svgwrite
|
||||||
from svgwrite import Drawing
|
from svgwrite import Drawing
|
||||||
from svgwrite.text import Text
|
|
||||||
from svgwrite.shapes import Line, Rect
|
from svgwrite.shapes import Line, Rect
|
||||||
|
from svgwrite.text import Text
|
||||||
|
|
||||||
from map_machine.map_configuration import MapConfiguration
|
from map_machine.map_configuration import MapConfiguration
|
||||||
from map_machine.osm.osm_reader import Tags
|
from map_machine.osm.osm_reader import Tags
|
||||||
|
|
|
@ -7,14 +7,21 @@ from typing import Dict, List, Optional, Tuple
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from svgwrite import Drawing
|
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.constructor import Constructor
|
||||||
|
from map_machine.geometry.boundary_box import BoundaryBox
|
||||||
from map_machine.geometry.flinger import Flinger
|
from map_machine.geometry.flinger import Flinger
|
||||||
from map_machine.pictogram.icon import ShapeExtractor
|
|
||||||
from map_machine.map_configuration import MapConfiguration
|
from map_machine.map_configuration import MapConfiguration
|
||||||
from map_machine.mapper import Map
|
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.scheme import Scheme
|
||||||
from map_machine.workspace import Workspace
|
from map_machine.workspace import Workspace
|
||||||
|
|
||||||
|
@ -26,45 +33,6 @@ SHAPE_EXTRACTOR: ShapeExtractor = ShapeExtractor(
|
||||||
)
|
)
|
||||||
DEFAULT_ZOOM: float = 18.0
|
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]] = [
|
ROAD_WIDTHS_AND_FEATURES: List[Dict[str, str]] = [
|
||||||
{"width": "4"},
|
{"width": "4"},
|
||||||
{"width": "8"},
|
{"width": "8"},
|
||||||
|
@ -79,8 +47,6 @@ ROAD_WIDTHS_AND_FEATURES: List[Dict[str, str]] = [
|
||||||
{"embankment": "yes", "width": "4"},
|
{"embankment": "yes", "width": "4"},
|
||||||
{"embankment": "yes", "width": "8"},
|
{"embankment": "yes", "width": "8"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
ROAD_LANES_AND_FEATURES: List[Dict[str, str]] = [
|
ROAD_LANES_AND_FEATURES: List[Dict[str, str]] = [
|
||||||
{"lanes": "1"},
|
{"lanes": "1"},
|
||||||
{"lanes": "2"},
|
{"lanes": "2"},
|
||||||
|
@ -106,7 +72,6 @@ PLACEMENT_FEATURES_1: List[Dict[str, str]] = [
|
||||||
{"placement": "transition"},
|
{"placement": "transition"},
|
||||||
{"lanes": "3", "placement": "right_of:1"}, # or placement=left_of:2
|
{"lanes": "3", "placement": "right_of:1"}, # or placement=left_of:2
|
||||||
]
|
]
|
||||||
|
|
||||||
PLACEMENT_FEATURES_2: List[Dict[str, str]] = [
|
PLACEMENT_FEATURES_2: List[Dict[str, str]] = [
|
||||||
{"lanes": "2"},
|
{"lanes": "2"},
|
||||||
# or placement:backward=left_of:1
|
# or placement:backward=left_of:1
|
||||||
|
@ -120,16 +85,18 @@ PLACEMENT_FEATURES_2: List[Dict[str, str]] = [
|
||||||
class Grid:
|
class Grid:
|
||||||
"""Creating map with elements ordered in grid."""
|
"""Creating map with elements ordered in grid."""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self, x_step: float = 0.0002, y_step: float = 0.0003):
|
||||||
self.x_step: float = 0.0002
|
self.x_step: float = x_step
|
||||||
self.y_step: float = 0.0003
|
self.y_step: float = y_step
|
||||||
self.x_start: float = 0.0
|
|
||||||
self.index: int = 0
|
self.index: int = 0
|
||||||
self.nodes: Dict[OSMNode, Tuple[int, int]] = {}
|
self.nodes: Dict[OSMNode, Tuple[int, int]] = {}
|
||||||
self.max_j: float = 0
|
self.max_j: float = 0
|
||||||
self.max_i: 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."""
|
"""Add OSM node to the grid."""
|
||||||
self.index += 1
|
self.index += 1
|
||||||
node: OSMNode = OSMNode(
|
node: OSMNode = OSMNode(
|
||||||
|
@ -142,21 +109,79 @@ class Grid:
|
||||||
self.max_i = max(self.max_i, i * self.y_step)
|
self.max_i = max(self.max_i, i * self.y_step)
|
||||||
return node
|
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:
|
def get_boundary_box(self) -> BoundaryBox:
|
||||||
"""Compute resulting boundary box with margin of one grid step."""
|
"""Compute resulting boundary box with margin of one grid step."""
|
||||||
return BoundaryBox(
|
return BoundaryBox(
|
||||||
-self.x_step,
|
-self.x_step * 1.5,
|
||||||
-self.max_i - self.y_step,
|
-self.max_i - self.y_step * 1.5,
|
||||||
self.max_j + self.x_step,
|
self.max_j + self.x_step * 1.5,
|
||||||
self.y_step,
|
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
|
types: List[Dict[str, str]], features: List[Dict[str, str]], path: Path
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Draw test image with different road features."""
|
"""Draw test image with different road features."""
|
||||||
osm_data: OSMData = OSMData()
|
|
||||||
grid: Grid = Grid()
|
grid: Grid = Grid()
|
||||||
|
|
||||||
for i, type_ in enumerate(types):
|
for i, type_ in enumerate(types):
|
||||||
|
@ -168,58 +193,41 @@ def road_features(
|
||||||
if previous:
|
if previous:
|
||||||
tags: Dict[str, str] = dict(type_)
|
tags: Dict[str, str] = dict(type_)
|
||||||
tags |= dict(features[j - 1])
|
tags |= dict(features[j - 1])
|
||||||
way: OSMWay = OSMWay(
|
grid.add_way(tags, [previous, node])
|
||||||
tags, i * (len(features) + 1) + j, [previous, node]
|
|
||||||
)
|
|
||||||
osm_data.add_way(way)
|
|
||||||
previous = node
|
previous = node
|
||||||
|
|
||||||
draw(osm_data, path, grid.get_boundary_box())
|
grid.draw(path)
|
||||||
|
|
||||||
|
|
||||||
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}.")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
logging.basicConfig(format="%(levelname)s %(message)s", level=logging.INFO)
|
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_tags: List[Dict[str, str]] = [
|
||||||
{"highway": value} for value in HIGHWAY_VALUES
|
{"highway": value} for value in HIGHWAY_VALUES
|
||||||
]
|
]
|
||||||
aeroway_tags: List[Dict[str, str]] = [
|
aeroway_tags: List[Dict[str, str]] = [
|
||||||
{"aeroway": value} for value in AEROWAY_VALUES
|
{"aeroway": value} for value in AEROWAY_VALUES
|
||||||
]
|
]
|
||||||
|
railway_tags: list[dict[str, str]] = [
|
||||||
|
{"railway": value} for value in RAILWAY_VALUES
|
||||||
|
]
|
||||||
|
|
||||||
road_features(
|
draw_road_features(
|
||||||
highway_tags, ROAD_LANES_AND_FEATURES, Path("out") / "lanes.svg"
|
highway_tags, ROAD_LANES_AND_FEATURES, out_path / "lanes.svg"
|
||||||
)
|
)
|
||||||
road_features(
|
draw_road_features(
|
||||||
highway_tags + RAILWAY_TAGS + aeroway_tags,
|
highway_tags + railway_tags + aeroway_tags,
|
||||||
ROAD_WIDTHS_AND_FEATURES,
|
ROAD_WIDTHS_AND_FEATURES,
|
||||||
Path("out") / "width.svg",
|
out_path / "width.svg",
|
||||||
)
|
)
|
||||||
road_features(
|
draw_road_features(
|
||||||
highway_tags,
|
highway_tags,
|
||||||
PLACEMENT_FEATURES_1 + [{"highway": "none"}] + PLACEMENT_FEATURES_2,
|
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")
|
|
@ -15,7 +15,6 @@ from map_machine.pictogram.icon import (
|
||||||
from map_machine.pictogram.icon_collection import IconCollection
|
from map_machine.pictogram.icon_collection import IconCollection
|
||||||
from map_machine.workspace import workspace
|
from map_machine.workspace import workspace
|
||||||
|
|
||||||
|
|
||||||
SKIP: bool = True
|
SKIP: bool = True
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,19 +9,19 @@ from typing import Optional
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import svgwrite
|
import svgwrite
|
||||||
|
|
||||||
from map_machine.geometry.boundary_box import BoundaryBox
|
|
||||||
from map_machine.constructor import Constructor
|
from map_machine.constructor import Constructor
|
||||||
|
from map_machine.geometry.boundary_box import BoundaryBox
|
||||||
from map_machine.geometry.flinger import Flinger
|
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 (
|
from map_machine.map_configuration import (
|
||||||
BuildingMode,
|
BuildingMode,
|
||||||
DrawingMode,
|
DrawingMode,
|
||||||
LabelMode,
|
LabelMode,
|
||||||
MapConfiguration,
|
MapConfiguration,
|
||||||
)
|
)
|
||||||
|
from map_machine.mapper import Map
|
||||||
from map_machine.osm.osm_getter import get_osm
|
from map_machine.osm.osm_getter import get_osm
|
||||||
from map_machine.osm.osm_reader import OSMData
|
from map_machine.osm.osm_reader import OSMData
|
||||||
|
from map_machine.pictogram.icon import ShapeExtractor
|
||||||
from map_machine.scheme import Scheme
|
from map_machine.scheme import Scheme
|
||||||
|
|
||||||
doc_path: Path = Path("doc")
|
doc_path: Path = Path("doc")
|
||||||
|
|
|
@ -6,6 +6,7 @@ from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from map_machine.doc.collections import Collection
|
from map_machine.doc.collections import Collection
|
||||||
|
|
||||||
from map_machine.map_configuration import MapConfiguration
|
from map_machine.map_configuration import MapConfiguration
|
||||||
from map_machine.osm.osm_reader import Tags
|
from map_machine.osm.osm_reader import Tags
|
||||||
from map_machine.pictogram.icon import Icon, ShapeExtractor
|
from map_machine.pictogram.icon import Icon, ShapeExtractor
|
||||||
|
|
|
@ -395,6 +395,13 @@ class Road(Tagged):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
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:
|
if "width:lanes" in tags:
|
||||||
try:
|
try:
|
||||||
widths: List[float] = list(
|
widths: List[float] = list(
|
||||||
|
|
|
@ -57,6 +57,7 @@ class MapConfiguration:
|
||||||
draw_roofs: bool = True
|
draw_roofs: bool = True
|
||||||
use_building_colors: bool = False
|
use_building_colors: bool = False
|
||||||
show_overlapped: bool = False
|
show_overlapped: bool = False
|
||||||
|
credit: Optional[str] = "© OpenStreetMap contributors"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_options(
|
def from_options(
|
||||||
|
|
|
@ -19,6 +19,7 @@ from map_machine.constructor import Constructor
|
||||||
from map_machine.drawing import draw_text
|
from map_machine.drawing import draw_text
|
||||||
from map_machine.feature.building import Building, draw_walls, BUILDING_SCALE
|
from map_machine.feature.building import Building, draw_walls, BUILDING_SCALE
|
||||||
from map_machine.feature.road import Intersection, Road, RoadPart
|
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.boundary_box import BoundaryBox
|
||||||
from map_machine.geometry.flinger import Flinger
|
from map_machine.geometry.flinger import Flinger
|
||||||
from map_machine.geometry.vector import Segment
|
from map_machine.geometry.vector import Segment
|
||||||
|
@ -34,7 +35,7 @@ from map_machine.workspace import workspace
|
||||||
__author__ = "Sergey Vartanov"
|
__author__ = "Sergey Vartanov"
|
||||||
__email__ = "me@enzet.ru"
|
__email__ = "me@enzet.ru"
|
||||||
|
|
||||||
OPENSTREETMAP_CREDIT: str = "© OpenStreetMap contributors"
|
ROAD_PRIORITY: float = 40.0
|
||||||
|
|
||||||
|
|
||||||
class Map:
|
class Map:
|
||||||
|
@ -63,7 +64,16 @@ class Map:
|
||||||
)
|
)
|
||||||
logging.info("Drawing ways...")
|
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)
|
path_commands: str = figure.get_path(self.flinger)
|
||||||
if path_commands:
|
if path_commands:
|
||||||
path: SVGPath = SVGPath(d=path_commands)
|
path: SVGPath = SVGPath(d=path_commands)
|
||||||
|
@ -72,6 +82,13 @@ class Map:
|
||||||
|
|
||||||
constructor.roads.draw(self.svg, self.flinger)
|
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:
|
for tree in constructor.trees:
|
||||||
tree.draw(self.svg, self.flinger, self.scheme)
|
tree.draw(self.svg, self.flinger, self.scheme)
|
||||||
for crater in constructor.craters:
|
for crater in constructor.craters:
|
||||||
|
@ -207,13 +224,17 @@ class Map:
|
||||||
text_color: Color = Color("#888888")
|
text_color: Color = Color("#888888")
|
||||||
outline_color: Color = Color("#FFFFFF")
|
outline_color: Color = Color("#FFFFFF")
|
||||||
|
|
||||||
for text, point in (
|
credit_list: list[tuple[str, tuple[float, float]]] = [
|
||||||
(
|
(f"Rendering: {__project__}", (right_margin, bottom_margin))
|
||||||
f"Data: {OPENSTREETMAP_CREDIT}",
|
]
|
||||||
|
if self.configuration.credit:
|
||||||
|
data_credit: tuple[str, tuple[float, float]] = (
|
||||||
|
f"Data: {self.configuration.credit}",
|
||||||
(right_margin, bottom_margin + font_size + vertical_spacing),
|
(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 (
|
for stroke_width, stroke, opacity in (
|
||||||
(3.0, outline_color, 0.7),
|
(3.0, outline_color, 0.7),
|
||||||
(1.0, None, 1.0),
|
(1.0, None, 1.0),
|
||||||
|
@ -255,12 +276,19 @@ def render_map(arguments: argparse.Namespace) -> None:
|
||||||
|
|
||||||
if arguments.boundary_box:
|
if arguments.boundary_box:
|
||||||
boundary_box = BoundaryBox.from_text(arguments.boundary_box)
|
boundary_box = BoundaryBox.from_text(arguments.boundary_box)
|
||||||
|
|
||||||
elif arguments.coordinates and arguments.size:
|
elif arguments.coordinates and arguments.size:
|
||||||
coordinates: np.ndarray = np.array(
|
coordinates: Optional[np.ndarray] = None
|
||||||
list(map(float, arguments.coordinates.split(",")))
|
|
||||||
)
|
for delimiter in ",", "/":
|
||||||
if len(coordinates) != 2:
|
if delimiter in arguments.coordinates:
|
||||||
fatal("Wrong number of coordinates.")
|
coordinates = np.array(
|
||||||
|
list(map(float, arguments.coordinates.split(delimiter)))
|
||||||
|
)
|
||||||
|
|
||||||
|
if coordinates is None or len(coordinates) != 2:
|
||||||
|
fatal("Wrong coordinates format.")
|
||||||
|
|
||||||
width, height = np.array(list(map(float, arguments.size.split(","))))
|
width, height = np.array(list(map(float, arguments.size.split(","))))
|
||||||
boundary_box = BoundaryBox.from_coordinates(
|
boundary_box = BoundaryBox.from_coordinates(
|
||||||
coordinates, configuration.zoom_level, width, height
|
coordinates, configuration.zoom_level, width, height
|
||||||
|
|
41
map_machine/osm/tags.py
Normal file
41
map_machine/osm/tags.py
Normal 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",
|
||||||
|
]
|
|
@ -1949,7 +1949,7 @@ node_icons:
|
||||||
|
|
||||||
- tags: {bus: "yes"}
|
- tags: {bus: "yes"}
|
||||||
add_shapes: [bus]
|
add_shapes: [bus]
|
||||||
- tags: {motocar: "yes"}
|
- tags: {motorcar: "yes"}
|
||||||
add_shapes: [car]
|
add_shapes: [car]
|
||||||
- tags: {car: "yes"}
|
- tags: {car: "yes"}
|
||||||
add_shapes: [car]
|
add_shapes: [car]
|
||||||
|
@ -2574,10 +2574,24 @@ ways:
|
||||||
stroke: "#BBBBBB"
|
stroke: "#BBBBBB"
|
||||||
priority: 42.0
|
priority: 42.0
|
||||||
- tags: {railway: construction}
|
- tags: {railway: construction}
|
||||||
|
style:
|
||||||
|
stroke-width: 2.0
|
||||||
|
stroke: "#000000"
|
||||||
|
stroke-dasharray: 6,3
|
||||||
|
opacity: 0.3
|
||||||
|
priority: 42.0
|
||||||
|
- tags: {railway: disused}
|
||||||
style:
|
style:
|
||||||
stroke-width: 3.0
|
stroke-width: 3.0
|
||||||
stroke: "#000000"
|
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
|
opacity: 0.3
|
||||||
priority: 42.0
|
priority: 42.0
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
Command-line user interface.
|
Command-line user interface.
|
||||||
"""
|
"""
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
from map_machine import __version__
|
from map_machine import __version__
|
||||||
|
@ -12,9 +11,6 @@ from map_machine.osm.osm_reader import STAGES_OF_DECAY
|
||||||
__author__ = "Sergey Vartanov"
|
__author__ = "Sergey Vartanov"
|
||||||
__email__ = "me@enzet.ru"
|
__email__ = "me@enzet.ru"
|
||||||
|
|
||||||
BOXES: str = " ▏▎▍▌▋▊▉"
|
|
||||||
BOXES_LENGTH: int = len(BOXES)
|
|
||||||
|
|
||||||
COMMAND_LINES: Dict[str, List[str]] = {
|
COMMAND_LINES: Dict[str, List[str]] = {
|
||||||
"render": ["render", "-b", "10.000,20.000,10.001,20.001"],
|
"render": ["render", "-b", "10.000,20.000,10.001,20.001"],
|
||||||
"render_with_tooltips": [
|
"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"number of node and area selectors by {len(STAGES_OF_DECAY) + 1} "
|
||||||
f"times",
|
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"
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
"""
|
"""
|
||||||
Test icon generation for nodes.
|
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 pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from colour import Color
|
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 import IconSet, ShapeSpecification, Icon
|
||||||
from map_machine.pictogram.icon_collection import IconCollection
|
from map_machine.pictogram.icon_collection import IconCollection
|
||||||
from tests import SCHEME, SHAPE_EXTRACTOR, workspace
|
from tests import SCHEME, SHAPE_EXTRACTOR, workspace
|
||||||
|
@ -43,7 +47,7 @@ def test_icons_by_name() -> None:
|
||||||
assert (path / "LICENSE").is_file()
|
assert (path / "LICENSE").is_file()
|
||||||
|
|
||||||
|
|
||||||
def get_icon(tags: Dict[str, str]) -> IconSet:
|
def get_icon(tags: Tags) -> IconSet:
|
||||||
"""Construct icon from tags."""
|
"""Construct icon from tags."""
|
||||||
processed: Set[str] = set()
|
processed: Set[str] = set()
|
||||||
icon, _ = SCHEME.get_icon(SHAPE_EXTRACTOR, tags, processed)
|
icon, _ = SCHEME.get_icon(SHAPE_EXTRACTOR, tags, processed)
|
||||||
|
@ -71,11 +75,13 @@ def test_no_icons_but_color() -> None:
|
||||||
|
|
||||||
|
|
||||||
def check_icon_set(
|
def check_icon_set(
|
||||||
icon: IconSet,
|
tags: Tags,
|
||||||
main_specification: List[Tuple[str, Optional[Color]]],
|
main_specification: List[Tuple[str, Optional[Color]]],
|
||||||
extra_specifications: List[List[Tuple[str, Optional[Color]]]],
|
extra_specifications: List[List[Tuple[str, Optional[Color]]]],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Check icon set using simple specification."""
|
"""Check icon set using simple specification."""
|
||||||
|
icon: IconSet = get_icon(tags)
|
||||||
|
|
||||||
if not main_specification:
|
if not main_specification:
|
||||||
assert icon.main_icon.is_default()
|
assert icon.main_icon.is_default()
|
||||||
else:
|
else:
|
||||||
|
@ -104,17 +110,17 @@ def test_icon() -> None:
|
||||||
Tags that should be visualized with single main icon and without extra
|
Tags that should be visualized with single main icon and without extra
|
||||||
icons.
|
icons.
|
||||||
"""
|
"""
|
||||||
icon: IconSet = get_icon({"natural": "tree"})
|
check_icon_set({"natural": "tree"}, [("tree", Color("#98AC64"))], [])
|
||||||
check_icon_set(icon, [("tree", Color("#98AC64"))], [])
|
|
||||||
|
|
||||||
|
|
||||||
def test_icon_1_extra() -> None:
|
def test_icon_1_extra() -> None:
|
||||||
"""
|
"""
|
||||||
Tags that should be visualized with single main icon and single extra icon.
|
Tags that should be visualized with single main icon and single extra icon.
|
||||||
"""
|
"""
|
||||||
icon: IconSet = get_icon({"barrier": "gate", "access": "private"})
|
|
||||||
check_icon_set(
|
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.
|
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(
|
check_icon_set(
|
||||||
icon,
|
{"barrier": "gate", "access": "private", "bicycle": "yes"},
|
||||||
[("gate", DEFAULT_COLOR)],
|
[("gate", DEFAULT_COLOR)],
|
||||||
[
|
[
|
||||||
[("bicycle", EXTRA_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.
|
Tags that should be visualized with default main icon and single extra icon.
|
||||||
"""
|
"""
|
||||||
icon: IconSet = get_icon({"access": "private"})
|
check_icon_set(
|
||||||
check_icon_set(icon, [], [[("lock_with_keyhole", EXTRA_COLOR)]])
|
{"access": "private"}, [], [[("lock_with_keyhole", EXTRA_COLOR)]]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_no_icon_2_extra() -> None:
|
def test_no_icon_2_extra() -> None:
|
||||||
"""
|
"""
|
||||||
Tags that should be visualized with default main icon and two extra icons.
|
Tags that should be visualized with default main icon and two extra icons.
|
||||||
"""
|
"""
|
||||||
icon: IconSet = get_icon({"access": "private", "bicycle": "yes"})
|
|
||||||
check_icon_set(
|
check_icon_set(
|
||||||
icon,
|
{"access": "private", "bicycle": "yes"},
|
||||||
[],
|
[],
|
||||||
[[("bicycle", EXTRA_COLOR)], [("lock_with_keyhole", EXTRA_COLOR)]],
|
[[("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.
|
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(
|
check_icon_set(
|
||||||
icon,
|
{"traffic_sign": "maxspeed", "maxspeed": "42"},
|
||||||
[("circle_11", DEFAULT_COLOR), ("digit_4", WHITE), ("digit_2", WHITE)],
|
[("circle_11", DEFAULT_COLOR), ("digit_4", WHITE), ("digit_2", WHITE)],
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
|
@ -169,22 +171,22 @@ def test_icon_regex() -> None:
|
||||||
|
|
||||||
def test_vending_machine() -> 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
|
See https://github.com/enzet/map-machine/issues/132
|
||||||
"""
|
"""
|
||||||
check_icon_set(
|
check_icon_set(
|
||||||
get_icon({"amenity": "vending_machine"}),
|
{"amenity": "vending_machine"},
|
||||||
[("vending_machine", DEFAULT_COLOR)],
|
[("vending_machine", DEFAULT_COLOR)],
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
check_icon_set(
|
check_icon_set(
|
||||||
get_icon({"amenity": "vending_machine", "vending": "drinks"}),
|
{"amenity": "vending_machine", "vending": "drinks"},
|
||||||
[("vending_bottle", DEFAULT_COLOR)],
|
[("vending_bottle", DEFAULT_COLOR)],
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
check_icon_set(
|
check_icon_set(
|
||||||
get_icon({"vending": "drinks"}),
|
{"vending": "drinks"},
|
||||||
[("vending_bottle", DEFAULT_COLOR)],
|
[("vending_bottle", DEFAULT_COLOR)],
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
import numpy as np
|
||||||
|
|
||||||
|
from map_machine.constructor import Constructor
|
||||||
from map_machine.figure import Figure
|
from map_machine.figure import Figure
|
||||||
from map_machine.geometry.boundary_box import BoundaryBox
|
from map_machine.geometry.boundary_box import BoundaryBox
|
||||||
from map_machine.geometry.flinger import Flinger
|
from map_machine.geometry.flinger import Flinger
|
||||||
from map_machine.map_configuration import MapConfiguration
|
from map_machine.map_configuration import MapConfiguration
|
||||||
from map_machine.constructor import Constructor
|
from map_machine.osm.osm_reader import OSMData, OSMWay, OSMNode, Tags
|
||||||
from map_machine.osm.osm_reader import OSMData, OSMWay, OSMNode
|
|
||||||
from tests import SCHEME, SHAPE_EXTRACTOR
|
from tests import SCHEME, SHAPE_EXTRACTOR
|
||||||
|
|
||||||
|
|
||||||
def get_constructor(osm_data: OSMData) -> Constructor:
|
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(
|
flinger: Flinger = Flinger(
|
||||||
BoundaryBox(-0.01, -0.01, 0.01, 0.01), 18, osm_data.equator_length
|
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
|
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:
|
def test_river_and_wood() -> None:
|
||||||
"""
|
"""
|
||||||
Check that river is above the wood.
|
Check that river is above the wood.
|
||||||
|
|
||||||
See https://github.com/enzet/map-machine/issues/126
|
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: OSMData = OSMData()
|
||||||
osm_data.add_way(OSMWay({"natural": "wood"}, 1, nodes_1))
|
create_way(osm_data, {"natural": "wood"}, 1)
|
||||||
osm_data.add_way(OSMWay({"waterway": "river"}, 2, nodes_2))
|
create_way(osm_data, {"waterway": "river"}, 2)
|
||||||
|
|
||||||
figures: list[Figure] = get_constructor(osm_data).get_sorted_figures()
|
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"
|
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:
|
def test_empty_ways() -> None:
|
||||||
"""Ways without nodes."""
|
"""Ways without nodes."""
|
||||||
osm_data: OSMData = OSMData()
|
osm_data: OSMData = OSMData()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue