mirror of
https://github.com/enzet/map-machine.git
synced 2025-08-06 10:09:52 +02:00
Refactor flinger.
Element drawing module was using flinger for Mercator projection, which is not exactly what is wanted.
This commit is contained in:
parent
77c8ec0044
commit
c0879bff36
9 changed files with 90 additions and 62 deletions
|
@ -55,7 +55,7 @@ TIME_COLOR_SCALE: list[Color] = [
|
|||
|
||||
def line_center(
|
||||
nodes: list[OSMNode], flinger: Flinger
|
||||
) -> (np.ndarray, np.ndarray):
|
||||
) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""
|
||||
Get geometric center of nodes set.
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import svgwrite
|
|||
|
||||
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 MercatorFlinger
|
||||
from map_machine.map_configuration import (
|
||||
BuildingMode,
|
||||
DrawingMode,
|
||||
|
@ -21,16 +21,16 @@ 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
|
||||
from map_machine.workspace import workspace
|
||||
|
||||
doc_path: Path = Path("doc")
|
||||
|
||||
cache: Path = Path("cache")
|
||||
cache.mkdir(exist_ok=True)
|
||||
|
||||
SCHEME: Scheme = Scheme.from_file(Path("map_machine/scheme/default.yml"))
|
||||
SCHEME: Scheme = Scheme.from_file(workspace.DEFAULT_SCHEME_PATH)
|
||||
EXTRACTOR: ShapeExtractor = ShapeExtractor(
|
||||
Path("map_machine/icons/icons.svg"),
|
||||
Path("map_machine/icons/config.json"),
|
||||
workspace.ICONS_PATH, workspace.ICONS_CONFIG_PATH
|
||||
)
|
||||
|
||||
|
||||
|
@ -46,7 +46,7 @@ def draw(
|
|||
|
||||
osm_data: OSMData = OSMData()
|
||||
osm_data.parse_osm_file(input_file_name)
|
||||
flinger: Flinger = Flinger(
|
||||
flinger: MercatorFlinger = MercatorFlinger(
|
||||
boundary_box, configuration.zoom_level, osm_data.equator_length
|
||||
)
|
||||
constructor: Constructor = Constructor(
|
||||
|
|
|
@ -6,7 +6,7 @@ from map_machine.osm.osm_reader import Tags, OSMNode
|
|||
|
||||
|
||||
def draw_node(tags: Tags, path: Path):
|
||||
grid: Grid = Grid(x_step=0.0003, show_credit=False, margin=0.5)
|
||||
grid: Grid = Grid(show_credit=False, margin=0.5)
|
||||
grid.add_node(tags, 0, 0)
|
||||
grid.draw(path)
|
||||
|
||||
|
@ -16,7 +16,7 @@ def draw_way():
|
|||
|
||||
|
||||
def draw_area(tags: Tags, path: Path):
|
||||
grid: Grid = Grid(x_step=0.0003, show_credit=False, margin=0.5)
|
||||
grid: Grid = Grid(show_credit=False, margin=0.5)
|
||||
node: OSMNode = grid.add_node({}, 0, 0)
|
||||
nodes: list[OSMNode] = [
|
||||
node,
|
||||
|
|
|
@ -6,8 +6,7 @@ from svgwrite import Drawing
|
|||
from svgwrite.text import Text
|
||||
|
||||
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, TranslateFlinger
|
||||
from map_machine.map_configuration import MapConfiguration
|
||||
from map_machine.mapper import Map
|
||||
from map_machine.osm.osm_reader import (
|
||||
|
@ -36,8 +35,8 @@ class Grid:
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
x_step: float = 0.0002,
|
||||
y_step: float = 0.0003,
|
||||
x_step: float = 20.0,
|
||||
y_step: float = 20.0,
|
||||
show_credit: bool = True,
|
||||
margin: float = 1.5,
|
||||
) -> None:
|
||||
|
@ -49,8 +48,8 @@ class Grid:
|
|||
self.index: int = 0
|
||||
self.nodes: dict[OSMNode, tuple[int, int]] = {}
|
||||
|
||||
self.max_j: float = 0
|
||||
self.max_i: float = 0
|
||||
self.max_j: float = 0.0
|
||||
self.max_i: float = 0.0
|
||||
|
||||
self.way_id: int = 0
|
||||
self.relation_id: int = 0
|
||||
|
@ -61,15 +60,11 @@ class Grid:
|
|||
def add_node(self, tags: Tags, i: int, j: int) -> OSMNode:
|
||||
"""Add OSM node to the grid."""
|
||||
self.index += 1
|
||||
node: OSMNode = OSMNode(
|
||||
tags,
|
||||
self.index,
|
||||
np.array((-i * self.y_step, j * self.x_step)),
|
||||
)
|
||||
node: OSMNode = OSMNode(tags, self.index, np.array((i, j)))
|
||||
self.nodes[node] = (j, i)
|
||||
self.osm_data.add_node(node)
|
||||
self.max_j = max(self.max_j, j * self.x_step)
|
||||
self.max_i = max(self.max_i, i * self.y_step)
|
||||
self.max_j = max(self.max_j, j)
|
||||
self.max_i = max(self.max_i, i)
|
||||
return node
|
||||
|
||||
def add_way(self, tags: Tags, nodes: list[OSMNode]) -> OSMWay:
|
||||
|
@ -90,24 +85,22 @@ class Grid:
|
|||
"""Add simple text label to the grid."""
|
||||
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.margin,
|
||||
-self.max_i - self.y_step * self.margin,
|
||||
self.max_j + self.x_step * self.margin,
|
||||
self.y_step * self.margin,
|
||||
)
|
||||
|
||||
def draw(self, output_path: Path, zoom: float = DEFAULT_ZOOM) -> None:
|
||||
def draw(self, output_path: Path) -> None:
|
||||
"""Draw grid."""
|
||||
configuration: MapConfiguration = MapConfiguration(
|
||||
SCHEME, level="all", credit=None, show_credit=self.show_credit
|
||||
)
|
||||
flinger: Flinger = Flinger(
|
||||
self.get_boundary_box(), zoom, self.osm_data.equator_length
|
||||
size = (
|
||||
(self.max_i + self.margin * 2.0) * self.x_step,
|
||||
(self.max_j + self.margin * 2.0) * self.y_step,
|
||||
)
|
||||
svg: Drawing = Drawing(output_path.name, flinger.size)
|
||||
|
||||
flinger: Flinger = TranslateFlinger(
|
||||
size,
|
||||
np.array((self.x_step, self.y_step)),
|
||||
np.array((self.margin, self.margin)),
|
||||
)
|
||||
svg: Drawing = Drawing(output_path.name, size)
|
||||
constructor: Constructor = Constructor(
|
||||
self.osm_data, flinger, SHAPE_EXTRACTOR, configuration
|
||||
)
|
||||
|
@ -119,7 +112,7 @@ class Grid:
|
|||
svg.add(
|
||||
Text(
|
||||
text,
|
||||
flinger.fling((-i * self.y_step, j * self.x_step)) + (0, 3),
|
||||
flinger.fling((i, j)) + (0, 3),
|
||||
font_family="JetBrains Mono",
|
||||
font_size=12,
|
||||
)
|
||||
|
|
|
@ -67,17 +67,17 @@ def draw_overlapped_ways(types: list[dict[str, str]], path: Path) -> None:
|
|||
|
||||
The goal is to show check priority.
|
||||
"""
|
||||
grid: Grid = Grid(0.00012, 0.00012)
|
||||
grid: Grid = Grid()
|
||||
|
||||
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)
|
||||
node_1: OSMNode = grid.add_node({}, 8, index + 1)
|
||||
node_2: OSMNode = grid.add_node({}, len(types) + 9, index + 1)
|
||||
grid.add_way(tags, [node_1, node_2])
|
||||
grid.add_text(", ".join(f"{k}={tags[k]}" for k in tags), index + 1, 0)
|
||||
grid.add_text(", ".join(f"{k}={tags[k]}" for k in tags), 0, index + 1)
|
||||
|
||||
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)
|
||||
node_1: OSMNode = grid.add_node({}, index + 9, 0)
|
||||
node_2: OSMNode = grid.add_node({}, index + 9, len(types) + 1)
|
||||
grid.add_way(tags, [node_1, node_2])
|
||||
|
||||
grid.draw(path)
|
||||
|
@ -106,7 +106,7 @@ def draw_road_features(
|
|||
|
||||
def draw_multipolygon(path: Path) -> None:
|
||||
"""Draw simple multipolygon with one outer and one inner way."""
|
||||
grid: Grid = Grid(y_step=0.0002)
|
||||
grid: Grid = Grid()
|
||||
|
||||
outer_node: OSMNode = grid.add_node({}, 0, 0)
|
||||
outer_nodes: list[OSMNode] = [
|
||||
|
|
|
@ -13,15 +13,19 @@ def pseudo_mercator(coordinates: np.ndarray) -> np.ndarray:
|
|||
"""
|
||||
Use spherical pseudo-Mercator projection to convert geo coordinates.
|
||||
|
||||
The result is (x, y), where x is a longitude value, so x is in [-180, 180],
|
||||
and y is a stretched latitude and may have any real value:
|
||||
(-infinity, +infinity).
|
||||
|
||||
:param coordinates: geo positional in the form of (latitude, longitude)
|
||||
:return: position on the plane in the form of (x, y)
|
||||
"""
|
||||
latitude, longitude = coordinates
|
||||
|
||||
y: float = (
|
||||
180.0
|
||||
/ np.pi
|
||||
* np.log(np.tan(np.pi / 4.0 + coordinates[0] * np.pi / 360.0))
|
||||
180.0 / np.pi * np.log(np.tan(np.pi / 4.0 + latitude * np.pi / 360.0))
|
||||
)
|
||||
return np.array((coordinates[1], y))
|
||||
return np.array((longitude, y))
|
||||
|
||||
|
||||
def osm_zoom_level_to_pixels_per_meter(
|
||||
|
@ -40,7 +44,21 @@ def osm_zoom_level_to_pixels_per_meter(
|
|||
|
||||
|
||||
class Flinger:
|
||||
"""Convert geo coordinates into SVG position points."""
|
||||
"""Interface for flinger that converts coordinates."""
|
||||
|
||||
def __init__(self, size: np.ndarray) -> None:
|
||||
self.size: np.ndarray = size
|
||||
|
||||
def fling(self, coordinates: np.ndarray) -> np.ndarray:
|
||||
"""Do nothing but return coordinates unchanged."""
|
||||
return coordinates
|
||||
|
||||
def get_scale(self, coordinates: Optional[np.ndarray] = None) -> float:
|
||||
return 1.0
|
||||
|
||||
|
||||
class MercatorFlinger(Flinger):
|
||||
"""Convert geographical coordinates into (x, y) points on the plane."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -57,24 +75,28 @@ class Flinger:
|
|||
"""
|
||||
self.geo_boundaries: BoundaryBox = geo_boundaries
|
||||
self.ratio: float = 2.0**zoom_level * 256.0 / 360.0
|
||||
self.size: np.ndarray = self.ratio * (
|
||||
size: np.ndarray = self.ratio * (
|
||||
pseudo_mercator(self.geo_boundaries.max_())
|
||||
- pseudo_mercator(self.geo_boundaries.min_())
|
||||
)
|
||||
self.pixels_per_meter: float = osm_zoom_level_to_pixels_per_meter(
|
||||
zoom_level, equator_length
|
||||
)
|
||||
self.size: np.ndarray = self.size.astype(int).astype(float)
|
||||
size = size.astype(int).astype(float)
|
||||
|
||||
super().__init__(size)
|
||||
|
||||
self.min_ = self.ratio * pseudo_mercator(self.geo_boundaries.min_())
|
||||
|
||||
def fling(self, coordinates: np.ndarray) -> np.ndarray:
|
||||
"""
|
||||
Convert geo coordinates into SVG position points.
|
||||
Convert geo coordinates into (x, y) position points on the plane.
|
||||
|
||||
:param coordinates: vector to fling
|
||||
:param coordinates: geographical coordinates to fling in the form of
|
||||
(latitude, longitude)
|
||||
"""
|
||||
result: np.ndarray = self.ratio * (
|
||||
pseudo_mercator(coordinates)
|
||||
- pseudo_mercator(self.geo_boundaries.min_())
|
||||
result: np.ndarray = (
|
||||
self.ratio * pseudo_mercator(coordinates) - self.min_
|
||||
)
|
||||
|
||||
# Invert y axis on coordinate plane.
|
||||
|
@ -86,7 +108,8 @@ class Flinger:
|
|||
"""
|
||||
Return pixels per meter ratio for the given geo coordinates.
|
||||
|
||||
:param coordinates: geo coordinates
|
||||
:param coordinates: geographical coordinates in the form of
|
||||
(latitude, longitude)
|
||||
"""
|
||||
if coordinates is None:
|
||||
# Get pixels per meter ratio for the center of the boundary box.
|
||||
|
@ -94,3 +117,15 @@ class Flinger:
|
|||
|
||||
scale_factor: float = abs(1.0 / np.cos(coordinates[0] / 180.0 * np.pi))
|
||||
return self.pixels_per_meter * scale_factor
|
||||
|
||||
|
||||
class TranslateFlinger(Flinger):
|
||||
def __init__(
|
||||
self, size: np.ndarray, scale: np.ndarray, offset: np.ndarray
|
||||
) -> None:
|
||||
super().__init__(size)
|
||||
self.scale: np.ndarray = scale
|
||||
self.offset: np.ndarray = offset
|
||||
|
||||
def fling(self, coordinates: np.ndarray) -> np.ndarray:
|
||||
return self.scale * (coordinates + self.offset)
|
||||
|
|
|
@ -19,7 +19,7 @@ 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.flinger import Flinger, MercatorFlinger
|
||||
from map_machine.geometry.vector import Segment
|
||||
from map_machine.map_configuration import LabelMode, MapConfiguration
|
||||
from map_machine.osm.osm_getter import NetworkError, get_osm
|
||||
|
@ -345,7 +345,7 @@ def render_map(arguments: argparse.Namespace) -> None:
|
|||
|
||||
# Render the map.
|
||||
|
||||
flinger: Flinger = Flinger(
|
||||
flinger: MercatorFlinger = MercatorFlinger(
|
||||
boundary_box, arguments.zoom, osm_data.equator_length
|
||||
)
|
||||
size: np.ndarray = flinger.size
|
||||
|
|
|
@ -17,7 +17,7 @@ from PIL import Image
|
|||
|
||||
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 MercatorFlinger
|
||||
from map_machine.map_configuration import MapConfiguration
|
||||
from map_machine.mapper import Map
|
||||
from map_machine.osm.osm_getter import NetworkError, get_osm
|
||||
|
@ -158,7 +158,7 @@ class Tile:
|
|||
self.x + 1, self.y + 1, self.zoom_level
|
||||
).get_coordinates()
|
||||
|
||||
flinger: Flinger = Flinger(
|
||||
flinger: MercatorFlinger = MercatorFlinger(
|
||||
BoundaryBox(left, bottom, right, top),
|
||||
self.zoom_level,
|
||||
osm_data.equator_length,
|
||||
|
@ -381,7 +381,7 @@ class Tiles:
|
|||
self.zoom_level,
|
||||
).get_coordinates()
|
||||
|
||||
flinger: Flinger = Flinger(
|
||||
flinger: MercatorFlinger = MercatorFlinger(
|
||||
BoundaryBox(left, bottom, right, top),
|
||||
self.zoom_level,
|
||||
osm_data.equator_length,
|
||||
|
|
|
@ -9,7 +9,7 @@ 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.geometry.flinger import MercatorFlinger
|
||||
from map_machine.map_configuration import MapConfiguration
|
||||
from map_machine.osm.osm_reader import OSMData, OSMWay, OSMNode, Tags
|
||||
from tests import SCHEME, SHAPE_EXTRACTOR
|
||||
|
@ -22,7 +22,7 @@ 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: MercatorFlinger = MercatorFlinger(
|
||||
BoundaryBox(-0.01, -0.01, 0.01, 0.01), 18, osm_data.equator_length
|
||||
)
|
||||
constructor: Constructor = Constructor(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue