mirror of
https://github.com/enzet/map-machine.git
synced 2025-08-03 00:29:53 +02:00
Add equator length parsing; remove option.
This commit is contained in:
parent
a7d4e27819
commit
38c4e00de3
7 changed files with 49 additions and 73 deletions
|
@ -11,4 +11,4 @@ __url__ = "https://github.com/enzet/Roentgen"
|
|||
__doc_url__ = f"{__url__}/blob/main/README.md"
|
||||
__author__ = "Sergey Vartanov"
|
||||
__email__ = "me@enzet.ru"
|
||||
__version__ = "0.1.2"
|
||||
__version__ = "0.1.3"
|
||||
|
|
|
@ -26,7 +26,7 @@ def pseudo_mercator(coordinates: np.ndarray) -> np.ndarray:
|
|||
|
||||
|
||||
def osm_zoom_level_to_pixels_per_meter(
|
||||
zoom_level: float, equator_length: float = 40_075_017.0
|
||||
zoom_level: float, equator_length: float
|
||||
) -> float:
|
||||
"""
|
||||
Convert OSM zoom level to pixels per meter on Equator. See
|
||||
|
@ -34,8 +34,7 @@ def osm_zoom_level_to_pixels_per_meter(
|
|||
|
||||
:param zoom_level: integer number usually not bigger than 20, but this
|
||||
function allows any non-negative float value
|
||||
:param equator_length: celestial body equator length in meters, default
|
||||
value is set for Earth
|
||||
:param equator_length: celestial body equator length in meters
|
||||
"""
|
||||
return 2 ** zoom_level / equator_length * 256
|
||||
|
||||
|
@ -48,14 +47,13 @@ class Flinger:
|
|||
def __init__(
|
||||
self,
|
||||
geo_boundaries: BoundaryBox,
|
||||
zoom_level: float = 18,
|
||||
equator_length: float = 40_075_017.0,
|
||||
zoom_level: float,
|
||||
equator_length: float,
|
||||
) -> None:
|
||||
"""
|
||||
:param geo_boundaries: minimum and maximum latitude and longitude
|
||||
:param zoom_level: zoom level in OpenStreetMap terminology
|
||||
:param equator_length: celestial body equator length in meters, default
|
||||
value is set for Earth
|
||||
:param equator_length: celestial body equator length in meters
|
||||
"""
|
||||
self.geo_boundaries: BoundaryBox = geo_boundaries
|
||||
self.ratio: float = 2 ** zoom_level * 256 / 360
|
||||
|
|
|
@ -276,7 +276,7 @@ def ui(options: argparse.Namespace) -> None:
|
|||
else:
|
||||
view_box = osm_data.view_box
|
||||
|
||||
flinger: Flinger = Flinger(view_box, options.zoom, options.equator_length)
|
||||
flinger: Flinger = Flinger(view_box, options.zoom, osm_data.equator_length)
|
||||
size: np.ndarray = flinger.size
|
||||
|
||||
svg: svgwrite.Drawing = svgwrite.Drawing(
|
||||
|
|
|
@ -307,6 +307,7 @@ class OSMData:
|
|||
self.authors: set[str] = set()
|
||||
self.time: MinMax = MinMax()
|
||||
self.view_box: Optional[BoundaryBox] = None
|
||||
self.equator_length: float = 40_075_017.0
|
||||
|
||||
def add_node(self, node: OSMNode) -> None:
|
||||
"""Add node and update map parameters."""
|
||||
|
@ -426,14 +427,16 @@ class OSMReader:
|
|||
for element in root:
|
||||
if element.tag == "bounds":
|
||||
self.parse_bounds(element)
|
||||
if element.tag == "node" and self.parse_nodes:
|
||||
elif element.tag == "object":
|
||||
self.parse_object(element)
|
||||
elif element.tag == "node" and self.parse_nodes:
|
||||
node = OSMNode.from_xml_structure(element)
|
||||
self.osm_data.add_node(node)
|
||||
if element.tag == "way" and self.parse_ways:
|
||||
elif element.tag == "way" and self.parse_ways:
|
||||
self.osm_data.add_way(
|
||||
OSMWay.from_xml_structure(element, self.osm_data.nodes)
|
||||
)
|
||||
if element.tag == "relation" and self.parse_relations:
|
||||
elif element.tag == "relation" and self.parse_relations:
|
||||
self.osm_data.add_relation(
|
||||
OSMRelation.from_xml_structure(element)
|
||||
)
|
||||
|
@ -448,3 +451,7 @@ class OSMReader:
|
|||
float(attributes["maxlon"]),
|
||||
float(attributes["maxlat"]),
|
||||
)
|
||||
|
||||
def parse_object(self, element: Element) -> None:
|
||||
"""Parse astronomical object properties from XML element."""
|
||||
self.osm_data.equator_length = float(element.get("equator"))
|
||||
|
|
|
@ -43,24 +43,22 @@ class Tile:
|
|||
x: int
|
||||
y: int
|
||||
zoom_level: int
|
||||
equator_length: float
|
||||
|
||||
@classmethod
|
||||
def from_coordinates(
|
||||
cls, coordinates: np.ndarray, zoom_level: int, equator_length: float
|
||||
cls, coordinates: np.ndarray, zoom_level: int
|
||||
) -> "Tile":
|
||||
"""
|
||||
Code from https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
|
||||
|
||||
:param coordinates: any coordinates inside tile, (latitude, longitude)
|
||||
:param zoom_level: zoom level in OpenStreetMap terminology
|
||||
:param equator_length: celestial body equator length in meters
|
||||
"""
|
||||
lat_rad: np.ndarray = np.radians(coordinates[0])
|
||||
n: float = 2.0 ** zoom_level
|
||||
x: int = int((coordinates[1] + 180.0) / 360.0 * n)
|
||||
y: int = int((1.0 - np.arcsinh(np.tan(lat_rad)) / np.pi) / 2.0 * n)
|
||||
return cls(x, y, zoom_level, equator_length)
|
||||
return cls(x, y, zoom_level)
|
||||
|
||||
def get_coordinates(self) -> np.ndarray:
|
||||
"""
|
||||
|
@ -81,16 +79,14 @@ class Tile:
|
|||
"""
|
||||
return (
|
||||
self.get_coordinates(),
|
||||
Tile(
|
||||
self.x + 1, self.y + 1, self.zoom_level, self.equator_length
|
||||
).get_coordinates(),
|
||||
Tile(self.x + 1, self.y + 1, self.zoom_level).get_coordinates(),
|
||||
)
|
||||
|
||||
def get_extended_boundary_box(self) -> BoundaryBox:
|
||||
"""Same as get_boundary_box, but with extended boundaries."""
|
||||
point_1: np.ndarray = self.get_coordinates()
|
||||
point_2: np.ndarray = Tile(
|
||||
self.x + 1, self.y + 1, self.zoom_level, self.equator_length
|
||||
self.x + 1, self.y + 1, self.zoom_level
|
||||
).get_coordinates()
|
||||
|
||||
return BoundaryBox(
|
||||
|
@ -154,13 +150,13 @@ class Tile:
|
|||
"""Draw SVG and PNG tile using OpenStreetMap data."""
|
||||
top, left = self.get_coordinates()
|
||||
bottom, right = Tile(
|
||||
self.x + 1, self.y + 1, self.zoom_level, self.equator_length
|
||||
self.x + 1, self.y + 1, self.zoom_level
|
||||
).get_coordinates()
|
||||
|
||||
flinger: Flinger = Flinger(
|
||||
BoundaryBox(left, bottom, right, top),
|
||||
self.zoom_level,
|
||||
self.equator_length,
|
||||
osm_data.equator_length,
|
||||
)
|
||||
size: np.ndarray = flinger.size
|
||||
|
||||
|
@ -204,7 +200,6 @@ class Tile:
|
|||
n * self.x + i,
|
||||
n * self.y + j,
|
||||
zoom_level,
|
||||
self.equator_length,
|
||||
)
|
||||
tiles.append(tile)
|
||||
return tiles
|
||||
|
@ -221,33 +216,31 @@ class Tiles:
|
|||
tile_2: Tile # Right bottom tile.
|
||||
zoom_level: int # OpenStreetMap zoom level.
|
||||
boundary_box: BoundaryBox
|
||||
equator_length: float
|
||||
|
||||
@classmethod
|
||||
def from_boundary_box(
|
||||
cls, boundary_box: BoundaryBox, zoom_level: int, equator_length: float
|
||||
cls, boundary_box: BoundaryBox, zoom_level: int
|
||||
) -> "Tiles":
|
||||
"""
|
||||
Create minimal set of tiles that covers boundary box.
|
||||
|
||||
:param boundary_box: area to be covered by tiles
|
||||
:param zoom_level: zoom level in OpenStreetMap terminology
|
||||
:param equator_length: celestial body equator length in meters
|
||||
"""
|
||||
tiles: list[Tile] = []
|
||||
tile_1: Tile = Tile.from_coordinates(
|
||||
boundary_box.get_left_top(), zoom_level, equator_length
|
||||
boundary_box.get_left_top(), zoom_level
|
||||
)
|
||||
tile_2: Tile = Tile.from_coordinates(
|
||||
boundary_box.get_right_bottom(), zoom_level, equator_length
|
||||
boundary_box.get_right_bottom(), zoom_level
|
||||
)
|
||||
for x in range(tile_1.x, tile_2.x + 1):
|
||||
for y in range(tile_1.y, tile_2.y + 1):
|
||||
tiles.append(Tile(x, y, zoom_level, equator_length))
|
||||
tiles.append(Tile(x, y, zoom_level))
|
||||
|
||||
latitude_2, longitude_1 = tile_1.get_coordinates()
|
||||
latitude_1, longitude_2 = Tile(
|
||||
tile_2.x + 1, tile_2.y + 1, zoom_level, equator_length
|
||||
tile_2.x + 1, tile_2.y + 1, zoom_level
|
||||
).get_coordinates()
|
||||
assert longitude_2 > longitude_1
|
||||
assert latitude_2 > latitude_1
|
||||
|
@ -256,14 +249,7 @@ class Tiles:
|
|||
longitude_1, latitude_1, longitude_2, latitude_2
|
||||
).round()
|
||||
|
||||
return cls(
|
||||
tiles,
|
||||
tile_1,
|
||||
tile_2,
|
||||
zoom_level,
|
||||
extended_boundary_box,
|
||||
equator_length,
|
||||
)
|
||||
return cls(tiles, tile_1, tile_2, zoom_level, extended_boundary_box)
|
||||
|
||||
def load_osm_data(self, cache_path: Path) -> OSMData:
|
||||
"""Load OpenStreetMap data."""
|
||||
|
@ -383,13 +369,12 @@ class Tiles:
|
|||
self.tile_2.x + 1,
|
||||
self.tile_2.y + 1,
|
||||
self.zoom_level,
|
||||
self.equator_length,
|
||||
).get_coordinates()
|
||||
|
||||
flinger: Flinger = Flinger(
|
||||
BoundaryBox(left, bottom, right, top),
|
||||
self.zoom_level,
|
||||
self.equator_length,
|
||||
osm_data.equator_length,
|
||||
)
|
||||
extractor: ShapeExtractor = ShapeExtractor(
|
||||
workspace.ICONS_PATH, workspace.ICONS_CONFIG_PATH
|
||||
|
@ -431,7 +416,6 @@ class Tiles:
|
|||
tiles[-1],
|
||||
zoom_level,
|
||||
self.boundary_box,
|
||||
self.equator_length,
|
||||
)
|
||||
|
||||
|
||||
|
@ -473,16 +457,27 @@ def ui(options: argparse.Namespace) -> None:
|
|||
"""Simple user interface for tile generation."""
|
||||
directory: Path = workspace.get_tile_path()
|
||||
|
||||
equator_length: float = options.equator_length
|
||||
zoom_levels: list[int] = parse_zoom_level(options.zoom)
|
||||
min_zoom_level: int = min(zoom_levels)
|
||||
|
||||
if options.coordinates:
|
||||
if options.input_file_name:
|
||||
osm_reader: OSMReader = OSMReader()
|
||||
osm_reader.parse_osm_file(Path(options.input_file_name))
|
||||
osm_data: OSMData = osm_reader.osm_data
|
||||
for zoom_level in zoom_levels:
|
||||
configuration: MapConfiguration = MapConfiguration.from_options(
|
||||
options, zoom_level
|
||||
)
|
||||
tiles: Tiles = Tiles.from_boundary_box(
|
||||
osm_data.view_box, zoom_level
|
||||
)
|
||||
tiles.draw(directory, Path(options.cache), configuration, osm_data)
|
||||
elif options.coordinates:
|
||||
coordinates: list[float] = list(
|
||||
map(float, options.coordinates.strip().split(","))
|
||||
)
|
||||
min_tile: Tile = Tile.from_coordinates(
|
||||
np.array(coordinates), min_zoom_level, equator_length
|
||||
np.array(coordinates), min_zoom_level
|
||||
)
|
||||
try:
|
||||
osm_data: OSMData = min_tile.load_osm_data(Path(options.cache))
|
||||
|
@ -491,7 +486,7 @@ def ui(options: argparse.Namespace) -> None:
|
|||
|
||||
for zoom_level in zoom_levels:
|
||||
tile: Tile = Tile.from_coordinates(
|
||||
np.array(coordinates), zoom_level, equator_length
|
||||
np.array(coordinates), zoom_level
|
||||
)
|
||||
try:
|
||||
configuration: MapConfiguration = MapConfiguration.from_options(
|
||||
|
@ -502,7 +497,7 @@ def ui(options: argparse.Namespace) -> None:
|
|||
logging.fatal(e.message)
|
||||
elif options.tile:
|
||||
zoom_level, x, y = map(int, options.tile.split("/"))
|
||||
tile: Tile = Tile(x, y, zoom_level, equator_length)
|
||||
tile: Tile = Tile(x, y, zoom_level)
|
||||
configuration: MapConfiguration = MapConfiguration.from_options(
|
||||
options, zoom_level
|
||||
)
|
||||
|
@ -513,9 +508,7 @@ def ui(options: argparse.Namespace) -> None:
|
|||
)
|
||||
if boundary_box is None:
|
||||
sys.exit(1)
|
||||
min_tiles: Tiles = Tiles.from_boundary_box(
|
||||
boundary_box, min_zoom_level, equator_length
|
||||
)
|
||||
min_tiles: Tiles = Tiles.from_boundary_box(boundary_box, min_zoom_level)
|
||||
try:
|
||||
osm_data: OSMData = min_tiles.load_osm_data(Path(options.cache))
|
||||
except NetworkError as e:
|
||||
|
@ -525,26 +518,11 @@ def ui(options: argparse.Namespace) -> None:
|
|||
if EXTEND_TO_BIGGER_TILE:
|
||||
tiles: Tiles = min_tiles.subdivide(zoom_level)
|
||||
else:
|
||||
tiles: Tiles = Tiles.from_boundary_box(
|
||||
boundary_box, zoom_level, equator_length
|
||||
)
|
||||
tiles: Tiles = Tiles.from_boundary_box(boundary_box, zoom_level)
|
||||
configuration: MapConfiguration = MapConfiguration.from_options(
|
||||
options, zoom_level
|
||||
)
|
||||
tiles.draw(directory, Path(options.cache), configuration, osm_data)
|
||||
elif options.input_file_name:
|
||||
osm_reader = OSMReader()
|
||||
osm_reader.parse_osm_file(Path(options.input_file_name))
|
||||
osm_data = osm_reader.osm_data
|
||||
boundary_box = osm_data.view_box
|
||||
for zoom_level in zoom_levels:
|
||||
configuration: MapConfiguration = MapConfiguration.from_options(
|
||||
options, zoom_level
|
||||
)
|
||||
tiles: Tiles = Tiles.from_boundary_box(
|
||||
boundary_box, zoom_level, equator_length
|
||||
)
|
||||
tiles.draw(directory, Path(options.cache), configuration, osm_data)
|
||||
else:
|
||||
logging.fatal(
|
||||
"Specify either --coordinates, --boundary-box, --tile, or --input."
|
||||
|
|
|
@ -100,13 +100,6 @@ def add_map_arguments(parser: argparse.ArgumentParser) -> None:
|
|||
help="seed for random",
|
||||
metavar="<string>",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--equator-length",
|
||||
default=40_075_017.0,
|
||||
help="equator length",
|
||||
type=float,
|
||||
metavar="<float>",
|
||||
)
|
||||
|
||||
|
||||
def add_tile_arguments(parser: argparse.ArgumentParser) -> None:
|
||||
|
|
|
@ -21,5 +21,5 @@ def test_pseudo_mercator() -> None:
|
|||
def test_osm_zoom_level_to_pixels_per_meter() -> None:
|
||||
"""Test scale computation."""
|
||||
assert np.allclose(
|
||||
osm_zoom_level_to_pixels_per_meter(18), 1.6745810488364858
|
||||
osm_zoom_level_to_pixels_per_meter(18, 40_075_017.0), 1.6745810488364858
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue