mirror of
https://github.com/enzet/map-machine.git
synced 2025-06-06 21:01:53 +02:00
Issue #69: support scale range.
Now it is possible to use scale specification with ranges. E.g. "16-18", "16,17,18", or "16,18-20".
This commit is contained in:
parent
265660901d
commit
abb49c5719
2 changed files with 104 additions and 29 deletions
109
roentgen/tile.py
109
roentgen/tile.py
|
@ -51,7 +51,7 @@ class Tile:
|
||||||
:param coordinates: any coordinates inside tile
|
:param coordinates: any coordinates inside tile
|
||||||
:param scale: OpenStreetMap zoom level
|
:param scale: OpenStreetMap zoom level
|
||||||
"""
|
"""
|
||||||
lat_rad = np.radians(coordinates[0])
|
lat_rad: np.ndarray = np.radians(coordinates[0])
|
||||||
n: float = 2.0 ** scale
|
n: float = 2.0 ** scale
|
||||||
x: int = int((coordinates[1] + 180.0) / 360.0 * n)
|
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)
|
y: int = int((1.0 - np.arcsinh(np.tan(lat_rad)) / np.pi) / 2.0 * n)
|
||||||
|
@ -200,7 +200,7 @@ class Tiles:
|
||||||
cls, boundary_box: BoundaryBox, scale: int
|
cls, boundary_box: BoundaryBox, scale: int
|
||||||
) -> "Tiles":
|
) -> "Tiles":
|
||||||
"""
|
"""
|
||||||
Create minimal set of tiles that cover boundary box.
|
Create minimal set of tiles that covers boundary box.
|
||||||
|
|
||||||
:param boundary_box: area to be covered by tiles
|
:param boundary_box: area to be covered by tiles
|
||||||
:param scale: OpenStreetMap zoom level
|
:param scale: OpenStreetMap zoom level
|
||||||
|
@ -227,6 +227,15 @@ class Tiles:
|
||||||
|
|
||||||
return cls(tiles, tile_1, tile_2, scale, extended_boundary_box)
|
return cls(tiles, tile_1, tile_2, scale, extended_boundary_box)
|
||||||
|
|
||||||
|
def load_osm_data(self, cache_path: Path) -> OSMData:
|
||||||
|
"""Load OpenStreetMap data."""
|
||||||
|
cache_file_path: Path = (
|
||||||
|
cache_path / f"{self.boundary_box.get_format()}.osm"
|
||||||
|
)
|
||||||
|
get_osm(self.boundary_box, cache_file_path)
|
||||||
|
|
||||||
|
return OSMReader().parse_osm_file(cache_file_path)
|
||||||
|
|
||||||
def draw_separately(
|
def draw_separately(
|
||||||
self, directory: Path, cache_path: Path, configuration: MapConfiguration
|
self, directory: Path, cache_path: Path, configuration: MapConfiguration
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -238,12 +247,7 @@ class Tiles:
|
||||||
:param cache_path: directory for temporary OSM files
|
:param cache_path: directory for temporary OSM files
|
||||||
:param configuration: drawing configuration
|
:param configuration: drawing configuration
|
||||||
"""
|
"""
|
||||||
cache_file_path: Path = (
|
osm_data: OSMData = self.load_osm_data(cache_path)
|
||||||
cache_path / f"{self.boundary_box.get_format()}.osm"
|
|
||||||
)
|
|
||||||
get_osm(self.boundary_box, cache_file_path)
|
|
||||||
|
|
||||||
osm_data: OSMData = OSMReader().parse_osm_file(cache_file_path)
|
|
||||||
for tile in self.tiles:
|
for tile in self.tiles:
|
||||||
file_path: Path = tile.get_file_name(directory)
|
file_path: Path = tile.get_file_name(directory)
|
||||||
if not file_path.exists():
|
if not file_path.exists():
|
||||||
|
@ -266,7 +270,11 @@ class Tiles:
|
||||||
return all(x.exists(directory_name) for x in self.tiles)
|
return all(x.exists(directory_name) for x in self.tiles)
|
||||||
|
|
||||||
def draw(
|
def draw(
|
||||||
self, directory: Path, cache_path: Path, configuration: MapConfiguration
|
self,
|
||||||
|
directory: Path,
|
||||||
|
cache_path: Path,
|
||||||
|
configuration: MapConfiguration,
|
||||||
|
osm_data: OSMData
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Draw one PNG image with all tiles and split it into a set of separate
|
Draw one PNG image with all tiles and split it into a set of separate
|
||||||
|
@ -275,11 +283,12 @@ class Tiles:
|
||||||
:param directory: directory for tiles
|
:param directory: directory for tiles
|
||||||
:param cache_path: directory for temporary OSM files
|
:param cache_path: directory for temporary OSM files
|
||||||
:param configuration: drawing configuration
|
:param configuration: drawing configuration
|
||||||
|
:param osm_data: OpenStreetMap data
|
||||||
"""
|
"""
|
||||||
if self.tiles_exist(directory):
|
if self.tiles_exist(directory):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.draw_image(cache_path, configuration)
|
self.draw_image_from_osm_data(cache_path, configuration, osm_data)
|
||||||
|
|
||||||
input_path: Path = self.get_file_path(cache_path).with_suffix(".png")
|
input_path: Path = self.get_file_path(cache_path).with_suffix(".png")
|
||||||
with input_path.open("rb") as input_file:
|
with input_path.open("rb") as input_file:
|
||||||
|
@ -313,15 +322,19 @@ class Tiles:
|
||||||
:param cache_path: directory for temporary SVG file and OSM files
|
:param cache_path: directory for temporary SVG file and OSM files
|
||||||
:param configuration: drawing configuration
|
:param configuration: drawing configuration
|
||||||
"""
|
"""
|
||||||
|
osm_data: OSMData = self.load_osm_data(cache_path)
|
||||||
|
self.draw_image_from_osm_data(cache_path, configuration, osm_data)
|
||||||
|
|
||||||
|
def draw_image_from_osm_data(
|
||||||
|
self,
|
||||||
|
cache_path: Path,
|
||||||
|
configuration: MapConfiguration,
|
||||||
|
osm_data: OSMData
|
||||||
|
) -> None:
|
||||||
|
"""Draw all tiles using OSM data."""
|
||||||
output_path: Path = self.get_file_path(cache_path)
|
output_path: Path = self.get_file_path(cache_path)
|
||||||
|
|
||||||
if not output_path.exists():
|
if not output_path.exists():
|
||||||
cache_file_path: Path = (
|
|
||||||
cache_path / f"{self.boundary_box.get_format()}.osm"
|
|
||||||
)
|
|
||||||
get_osm(self.boundary_box, cache_file_path)
|
|
||||||
|
|
||||||
osm_data: OSMData = OSMReader().parse_osm_file(cache_file_path)
|
|
||||||
top, left = self.tile_1.get_coordinates()
|
top, left = self.tile_1.get_coordinates()
|
||||||
bottom, right = Tile(
|
bottom, right = Tile(
|
||||||
self.tile_2.x + 1, self.tile_2.y + 1, self.scale
|
self.tile_2.x + 1, self.tile_2.y + 1, self.scale
|
||||||
|
@ -360,20 +373,66 @@ class Tiles:
|
||||||
logging.debug(f"File {png_path} already exists.")
|
logging.debug(f"File {png_path} already exists.")
|
||||||
|
|
||||||
|
|
||||||
|
class ScaleConfigurationException(Exception):
|
||||||
|
"""Wrong configuration format."""
|
||||||
|
|
||||||
|
|
||||||
|
def parse_scale(scale_specification: str) -> list[int]:
|
||||||
|
"""Parse scale specification."""
|
||||||
|
parts: list[str]
|
||||||
|
if "," in scale_specification:
|
||||||
|
parts = scale_specification.split(",")
|
||||||
|
else:
|
||||||
|
parts = [scale_specification]
|
||||||
|
|
||||||
|
def parse(scale: str) -> int:
|
||||||
|
"""Parse scale."""
|
||||||
|
parsed_scale: int = int(scale)
|
||||||
|
if parsed_scale <= 0:
|
||||||
|
raise ScaleConfigurationException("Non positive scale.")
|
||||||
|
if parsed_scale > 20:
|
||||||
|
raise ScaleConfigurationException("Scale is too big.")
|
||||||
|
return parsed_scale
|
||||||
|
|
||||||
|
result: list[int] = []
|
||||||
|
for part in parts:
|
||||||
|
if "-" in part:
|
||||||
|
from_, to = part.split("-")
|
||||||
|
from_scale: int = parse(from_)
|
||||||
|
to_scale: int = parse(to)
|
||||||
|
if from_scale > to_scale:
|
||||||
|
raise ScaleConfigurationException("Wrong range.")
|
||||||
|
result += range(from_scale, to_scale + 1)
|
||||||
|
else:
|
||||||
|
result.append(parse(part))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def ui(options: argparse.Namespace) -> None:
|
def ui(options: argparse.Namespace) -> None:
|
||||||
"""Simple user interface for tile generation."""
|
"""Simple user interface for tile generation."""
|
||||||
directory: Path = workspace.get_tile_path()
|
directory: Path = workspace.get_tile_path()
|
||||||
configuration: MapConfiguration = MapConfiguration.from_options(options)
|
configuration: MapConfiguration = MapConfiguration.from_options(options)
|
||||||
|
|
||||||
|
scales: list[int] = parse_scale(options.scales)
|
||||||
|
min_scale: int = min(scales)
|
||||||
|
|
||||||
if options.coordinates:
|
if options.coordinates:
|
||||||
coordinates: list[float] = list(
|
coordinates: list[float] = list(
|
||||||
map(float, options.coordinates.strip().split(","))
|
map(float, options.coordinates.strip().split(","))
|
||||||
)
|
)
|
||||||
tile: Tile = Tile.from_coordinates(np.array(coordinates), options.scale)
|
min_tile: Tile = Tile.from_coordinates(np.array(coordinates), min_scale)
|
||||||
try:
|
try:
|
||||||
tile.draw(directory, Path(options.cache), configuration)
|
osm_data: OSMData = min_tile.load_osm_data(Path(options.cache))
|
||||||
except NetworkError as e:
|
except NetworkError as e:
|
||||||
logging.fatal(e.message)
|
raise NetworkError(f"Map is not loaded. {e.message}")
|
||||||
|
|
||||||
|
for scale in scales:
|
||||||
|
tile: Tile = Tile.from_coordinates(np.array(coordinates), scale)
|
||||||
|
try:
|
||||||
|
tile.draw_with_osm_data(osm_data, directory, configuration)
|
||||||
|
except NetworkError as e:
|
||||||
|
logging.fatal(e.message)
|
||||||
elif options.tile:
|
elif options.tile:
|
||||||
scale, x, y = map(int, options.tile.split("/"))
|
scale, x, y = map(int, options.tile.split("/"))
|
||||||
tile: Tile = Tile(x, y, scale)
|
tile: Tile = Tile(x, y, scale)
|
||||||
|
@ -384,8 +443,16 @@ def ui(options: argparse.Namespace) -> None:
|
||||||
)
|
)
|
||||||
if boundary_box is None:
|
if boundary_box is None:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
tiles: Tiles = Tiles.from_boundary_box(boundary_box, options.scale)
|
min_tiles: Tiles = Tiles.from_boundary_box(boundary_box, min_scale)
|
||||||
tiles.draw(directory, Path(options.cache), configuration)
|
extended_boundary_box: BoundaryBox = min_tiles.boundary_box
|
||||||
|
try:
|
||||||
|
osm_data: OSMData = min_tiles.load_osm_data(Path(options.cache))
|
||||||
|
except NetworkError as e:
|
||||||
|
raise NetworkError(f"Map is not loaded. {e.message}")
|
||||||
|
|
||||||
|
for scale in scales:
|
||||||
|
tiles: Tiles = Tiles.from_boundary_box(extended_boundary_box, scale)
|
||||||
|
tiles.draw(directory, Path(options.cache), configuration, osm_data)
|
||||||
else:
|
else:
|
||||||
logging.fatal(
|
logging.fatal(
|
||||||
"Specify either --coordinates, --boundary-box, or --tile."
|
"Specify either --coordinates, --boundary-box, or --tile."
|
||||||
|
|
|
@ -74,14 +74,6 @@ def add_map_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
choices=(x.value for x in LabelMode),
|
choices=(x.value for x in LabelMode),
|
||||||
help="label drawing mode: " + ", ".join(x.value for x in LabelMode),
|
help="label drawing mode: " + ", ".join(x.value for x in LabelMode),
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"-s",
|
|
||||||
"--scale",
|
|
||||||
type=int,
|
|
||||||
metavar="<integer>",
|
|
||||||
help="OSM zoom level",
|
|
||||||
default=18,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--level",
|
"--level",
|
||||||
default="overground",
|
default="overground",
|
||||||
|
@ -122,6 +114,14 @@ def add_tile_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
"boundary box",
|
"boundary box",
|
||||||
metavar="<lon1>,<lat1>,<lon2>,<lat2>",
|
metavar="<lon1>,<lat1>,<lon2>,<lat2>",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-s",
|
||||||
|
"--scales",
|
||||||
|
type=str,
|
||||||
|
metavar="<integer>",
|
||||||
|
help="OSM zoom level",
|
||||||
|
default="18",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def add_server_arguments(parser: argparse.ArgumentParser) -> None:
|
def add_server_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
|
@ -173,6 +173,14 @@ def add_render_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
default="cache",
|
default="cache",
|
||||||
metavar="<path>",
|
metavar="<path>",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-s",
|
||||||
|
"--scale",
|
||||||
|
type=int,
|
||||||
|
metavar="<integer>",
|
||||||
|
help="OSM zoom level",
|
||||||
|
default=18,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def add_mapcss_arguments(parser: argparse.ArgumentParser) -> None:
|
def add_mapcss_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue