Add equator length parsing; remove option.

This commit is contained in:
Sergey Vartanov 2021-09-06 04:53:00 +03:00
parent a7d4e27819
commit 38c4e00de3
7 changed files with 49 additions and 73 deletions

View file

@ -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"

View file

@ -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

View file

@ -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(

View file

@ -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"))

View file

@ -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."

View file

@ -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:

View file

@ -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
)