mirror of
https://github.com/enzet/map-machine.git
synced 2025-06-07 13:21:49 +02:00
Issue #74: rename scale to zoom level.
This commit is contained in:
parent
00f97131f2
commit
9c4873c5ae
9 changed files with 121 additions and 104 deletions
20
README.md
20
README.md
|
@ -131,7 +131,7 @@ Command `render` is used to generates SVG map from OpenStreetMap data. You can r
|
||||||
roentgen render \
|
roentgen render \
|
||||||
-b <min longitude>,<min latitude>,<max longitude>,<max latitude> \
|
-b <min longitude>,<min latitude>,<max longitude>,<max latitude> \
|
||||||
-o <output file name> \
|
-o <output file name> \
|
||||||
-s <osm zoom level> \
|
-z <OSM zoom level> \
|
||||||
<other arguments>
|
<other arguments>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ will download OSM data to `cache/2.284,48.860,2.290,48.865.osm` and write output
|
||||||
| <span style="white-space: nowrap;">`-o`</span>, <span style="white-space: nowrap;">`--output`</span> `<path>` | output SVG file name, default value: `out/map.svg` |
|
| <span style="white-space: nowrap;">`-o`</span>, <span style="white-space: nowrap;">`--output`</span> `<path>` | output SVG file name, default value: `out/map.svg` |
|
||||||
| <span style="white-space: nowrap;">`-b`</span>, <span style="white-space: nowrap;">`--boundary-box`</span> `<lon1>,<lat1>,<lon2>,<lat2>` | geo boundary box; if first value is negative, enclose the value with quotes and use space before `-` |
|
| <span style="white-space: nowrap;">`-b`</span>, <span style="white-space: nowrap;">`--boundary-box`</span> `<lon1>,<lat1>,<lon2>,<lat2>` | geo boundary box; if first value is negative, enclose the value with quotes and use space before `-` |
|
||||||
| <span style="white-space: nowrap;">`--cache`</span> `<path>` | path for temporary OSM files, default value: `cache` |
|
| <span style="white-space: nowrap;">`--cache`</span> `<path>` | path for temporary OSM files, default value: `cache` |
|
||||||
| <span style="white-space: nowrap;">`-s`</span>, <span style="white-space: nowrap;">`--scale`</span> `<integer>` | OSM zoom level, default value: 18 |
|
| <span style="white-space: nowrap;">`-z`</span>, <span style="white-space: nowrap;">`--zoom`</span> `<integer>` | OSM zoom level, default value: 18 |
|
||||||
|
|
||||||
+ see [map configuration options](#map-options)
|
+ see [map configuration options](#map-options)
|
||||||
|
|
||||||
|
@ -165,10 +165,10 @@ Command `tile` is used to generate PNG tiles for [slippy maps](https://wiki.open
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
|---|---|
|
|---|---|
|
||||||
| <span style="white-space: nowrap;">`-c`</span>, <span style="white-space: nowrap;">`--coordinates`</span> `<latitude>,<longitude>` | coordinates of any location inside the tile |
|
| <span style="white-space: nowrap;">`-c`</span>, <span style="white-space: nowrap;">`--coordinates`</span> `<latitude>,<longitude>` | coordinates of any location inside the tile |
|
||||||
| <span style="white-space: nowrap;">`-t`</span>, <span style="white-space: nowrap;">`--tile`</span> `<scale>/<x>/<y>` | tile specification |
|
| <span style="white-space: nowrap;">`-t`</span>, <span style="white-space: nowrap;">`--tile`</span> `<zoom level>/<x>/<y>` | tile specification |
|
||||||
| <span style="white-space: nowrap;">`--cache`</span> `<path>` | path for temporary OSM files, default value: `cache` |
|
| <span style="white-space: nowrap;">`--cache`</span> `<path>` | path for temporary OSM files, default value: `cache` |
|
||||||
| <span style="white-space: nowrap;">`-b`</span>, <span style="white-space: nowrap;">`--boundary-box`</span> `<lon1>,<lat1>,<lon2>,<lat2>` | construct the minimum amount of tiles that cover requested boundary box |
|
| <span style="white-space: nowrap;">`-b`</span>, <span style="white-space: nowrap;">`--boundary-box`</span> `<lon1>,<lat1>,<lon2>,<lat2>` | construct the minimum amount of tiles that cover requested boundary box |
|
||||||
| <span style="white-space: nowrap;">`-s`</span>, <span style="white-space: nowrap;">`--scales`</span> `<integer>` | OSM zoom levels; can be list of numbers or ranges, e.g. `16-18`, `16,17,18`, or `16,18-20`, default value: `18` |
|
| <span style="white-space: nowrap;">`-z`</span>, <span style="white-space: nowrap;">`--zoom`</span> `<integer>` | OSM zoom levels; can be list of numbers or ranges, e.g. `16-18`, `16,17,18`, or `16,18-20`, default value: `18` |
|
||||||
|
|
||||||
+ see [map configuration options](#map-options)
|
+ see [map configuration options](#map-options)
|
||||||
|
|
||||||
|
@ -185,15 +185,15 @@ or specify any geographical coordinates inside a tile:
|
||||||
```bash
|
```bash
|
||||||
roentgen tile \
|
roentgen tile \
|
||||||
--coordinates <latitude>,<longitude> \
|
--coordinates <latitude>,<longitude> \
|
||||||
--scales <OSM zoom levels>
|
--zoom <OSM zoom levels>
|
||||||
```
|
```
|
||||||
|
|
||||||
Tile will be stored as SVG file `out/tiles/tile_<zoom level>_<x>_<y>.svg` and PNG file `out/tiles/tile_<zoom level>_<x>_<y>.svg`, where `x` and `y` are tile coordinates. `--scales` option will be ignored if it is used with `--tile` option.
|
Tile will be stored as SVG file `out/tiles/tile_<zoom level>_<x>_<y>.svg` and PNG file `out/tiles/tile_<zoom level>_<x>_<y>.svg`, where `x` and `y` are tile coordinates. `--zoom` option will be ignored if it is used with `--tile` option.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
roentgen tile -c 55.7510637,37.6270761 -s 18
|
roentgen tile -c 55.7510637,37.6270761 -z 18
|
||||||
```
|
```
|
||||||
|
|
||||||
will generate SVG file `out/tiles/tile_18_158471_81953.svg` and PNG file `out/tiles/tile_18_158471_81953.png`.
|
will generate SVG file `out/tiles/tile_18_158471_81953.svg` and PNG file `out/tiles/tile_18_158471_81953.png`.
|
||||||
|
@ -205,7 +205,7 @@ Specify boundary box to get the minimal set of tiles that covers the area:
|
||||||
```bash
|
```bash
|
||||||
roentgen tile \
|
roentgen tile \
|
||||||
--boundary-box <min longitude>,<min latitude>,<max longitude>,<max latitude> \
|
--boundary-box <min longitude>,<min latitude>,<max longitude>,<max latitude> \
|
||||||
--scales <OSM zoom levels>
|
--zoom <OSM zoom levels>
|
||||||
```
|
```
|
||||||
|
|
||||||
Boundary box will be extended to the boundaries of the minimal tile set that covers the area, then it will be extended a bit more to avoid some artifacts on the edges rounded to 3 digits after the decimal point. Map with new boundary box coordinates will be written to the cache directory as SVG and PNG files. All tiles will be stored as SVG files `out/tiles/tile_<zoom level>_<x>_<y>.svg` and PNG files `out/tiles/tile_<zoom level>_<x>_<y>.svg`, where `x` and `y` are tile coordinates.
|
Boundary box will be extended to the boundaries of the minimal tile set that covers the area, then it will be extended a bit more to avoid some artifacts on the edges rounded to 3 digits after the decimal point. Map with new boundary box coordinates will be written to the cache directory as SVG and PNG files. All tiles will be stored as SVG files `out/tiles/tile_<zoom level>_<x>_<y>.svg` and PNG files `out/tiles/tile_<zoom level>_<x>_<y>.svg`, where `x` and `y` are tile coordinates.
|
||||||
|
@ -216,7 +216,7 @@ Example:
|
||||||
roentgen tile -b 2.361,48.871,2.368,48.875
|
roentgen tile -b 2.361,48.871,2.368,48.875
|
||||||
```
|
```
|
||||||
|
|
||||||
will generate 36 PNG tiles at scale 18 from tile 18/132791/90164 all the way to 18/132796/90169 and two cached files `cache/2.360,48.869,2.370,48.877_18.svg` and `cache/2.360,48.869,2.370,48.877_18.png`.
|
will generate 36 PNG tiles at zoom level 18 from tile 18/132791/90164 all the way to 18/132796/90169 and two cached files `cache/2.360,48.869,2.370,48.877_18.svg` and `cache/2.360,48.869,2.370,48.877_18.png`.
|
||||||
|
|
||||||
Tile server
|
Tile server
|
||||||
-----------
|
-----------
|
||||||
|
@ -234,7 +234,7 @@ Stop server interrupting process with <kbd>Ctrl</kbd> + <kbd>C</kbd>.
|
||||||
Create a minimal amount of tiles that cover specified boundary box for zoom levels 16, 17, 18, and 19:
|
Create a minimal amount of tiles that cover specified boundary box for zoom levels 16, 17, 18, and 19:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
roentgen tile -b 2.364,48.854,2.367,48.857 -s 16-19
|
roentgen tile -b 2.364,48.854,2.367,48.857 -z 16-19
|
||||||
```
|
```
|
||||||
|
|
||||||
Run tile server on 127.0.0.1:8080:
|
Run tile server on 127.0.0.1:8080:
|
||||||
|
|
|
@ -175,7 +175,7 @@ Command \m {render} is used to generates SVG map from OpenStreetMap data. You c
|
||||||
\code {roentgen render \\
|
\code {roentgen render \\
|
||||||
-b \formal {min longitude},\formal {min latitude},\formal {max longitude},\formal {max latitude} \\
|
-b \formal {min longitude},\formal {min latitude},\formal {max longitude},\formal {max latitude} \\
|
||||||
-o \formal {output file name} \\
|
-o \formal {output file name} \\
|
||||||
-s \formal {osm zoom level} \\
|
-z \formal {OSM zoom level} \\
|
||||||
\formal {other arguments}} {bash}
|
\formal {other arguments}} {bash}
|
||||||
|
|
||||||
\3 {Example} {example-2}
|
\3 {Example} {example-2}
|
||||||
|
@ -210,13 +210,13 @@ or specify any geographical coordinates inside a tile\:
|
||||||
|
|
||||||
\code {roentgen tile \\
|
\code {roentgen tile \\
|
||||||
--coordinates \formal {latitude},\formal {longitude} \\
|
--coordinates \formal {latitude},\formal {longitude} \\
|
||||||
--scales \formal {OSM zoom levels}} {bash}
|
--zoom \formal {OSM zoom levels}} {bash}
|
||||||
|
|
||||||
Tile will be stored as SVG file \m {out/tiles/tile_<zoom level>_<x>_<y>.svg} and PNG file \m {out/tiles/tile_<zoom level>_<x>_<y>.svg}, where \m {x} and \m {y} are tile coordinates. \m {--scales} option will be ignored if it is used with \m {--tile} option.
|
Tile will be stored as SVG file \m {out/tiles/tile_<zoom level>_<x>_<y>.svg} and PNG file \m {out/tiles/tile_<zoom level>_<x>_<y>.svg}, where \m {x} and \m {y} are tile coordinates. \m {--zoom} option will be ignored if it is used with \m {--tile} option.
|
||||||
|
|
||||||
Example\:
|
Example\:
|
||||||
|
|
||||||
\code {roentgen tile -c 55.7510637,37.6270761 -s 18} {bash}
|
\code {roentgen tile -c 55.7510637,37.6270761 -z 18} {bash}
|
||||||
|
|
||||||
will generate SVG file \m {out/tiles/tile_18_158471_81953.svg} and PNG file \m {out/tiles/tile_18_158471_81953.png}.
|
will generate SVG file \m {out/tiles/tile_18_158471_81953.svg} and PNG file \m {out/tiles/tile_18_158471_81953.png}.
|
||||||
|
|
||||||
|
@ -226,7 +226,7 @@ Specify boundary box to get the minimal set of tiles that covers the area\:
|
||||||
|
|
||||||
\code {roentgen tile \\
|
\code {roentgen tile \\
|
||||||
--boundary-box \formal {min longitude},\formal {min latitude},\formal {max longitude},\formal {max latitude} \\
|
--boundary-box \formal {min longitude},\formal {min latitude},\formal {max longitude},\formal {max latitude} \\
|
||||||
--scales \formal {OSM zoom levels}} {bash}
|
--zoom \formal {OSM zoom levels}} {bash}
|
||||||
|
|
||||||
Boundary box will be extended to the boundaries of the minimal tile set that covers the area, then it will be extended a bit more to avoid some artifacts on the edges rounded to 3 digits after the decimal point. Map with new boundary box coordinates will be written to the cache directory as SVG and PNG files. All tiles will be stored as SVG files \m {out/tiles/tile_<zoom level>_<x>_<y>.svg} and PNG files \m {out/tiles/tile_<zoom level>_<x>_<y>.svg}, where \m {x} and \m {y} are tile coordinates.
|
Boundary box will be extended to the boundaries of the minimal tile set that covers the area, then it will be extended a bit more to avoid some artifacts on the edges rounded to 3 digits after the decimal point. Map with new boundary box coordinates will be written to the cache directory as SVG and PNG files. All tiles will be stored as SVG files \m {out/tiles/tile_<zoom level>_<x>_<y>.svg} and PNG files \m {out/tiles/tile_<zoom level>_<x>_<y>.svg}, where \m {x} and \m {y} are tile coordinates.
|
||||||
|
|
||||||
|
@ -234,7 +234,7 @@ Example\:
|
||||||
|
|
||||||
\code {roentgen tile -b 2.361,48.871,2.368,48.875} {bash}
|
\code {roentgen tile -b 2.361,48.871,2.368,48.875} {bash}
|
||||||
|
|
||||||
will generate 36 PNG tiles at scale 18 from tile 18/132791/90164 all the way to 18/132796/90169 and two cached files \m {cache/2.360,48.869,2.370,48.877_18.svg} and \m {cache/2.360,48.869,2.370,48.877_18.png}.
|
will generate 36 PNG tiles at zoom level 18 from tile 18/132791/90164 all the way to 18/132796/90169 and two cached files \m {cache/2.360,48.869,2.370,48.877_18.svg} and \m {cache/2.360,48.869,2.370,48.877_18.png}.
|
||||||
|
|
||||||
\2 {Tile server} {tile-server}
|
\2 {Tile server} {tile-server}
|
||||||
|
|
||||||
|
@ -248,7 +248,7 @@ Stop server interrupting process with \kbd {Ctrl} + \kbd {C}.
|
||||||
|
|
||||||
Create a minimal amount of tiles that cover specified boundary box for zoom levels 16, 17, 18, and 19\:
|
Create a minimal amount of tiles that cover specified boundary box for zoom levels 16, 17, 18, and 19\:
|
||||||
|
|
||||||
\code {roentgen tile -b 2.364,48.854,2.367,48.857 -s 16-19} {bash}
|
\code {roentgen tile -b 2.364,48.854,2.367,48.857 -z 16-19} {bash}
|
||||||
|
|
||||||
Run tile server on 127.0.0.1\:8080\:
|
Run tile server on 127.0.0.1\:8080\:
|
||||||
|
|
||||||
|
|
|
@ -46,25 +46,28 @@ class Flinger:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
geo_boundaries: BoundaryBox,
|
geo_boundaries: BoundaryBox,
|
||||||
scale: float = 18,
|
zoom_level: float = 18,
|
||||||
border: np.ndarray = np.array((0, 0)),
|
border: np.ndarray = np.array((0, 0)),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
:param geo_boundaries: minimum and maximum latitude and longitude
|
:param geo_boundaries: minimum and maximum latitude and longitude
|
||||||
:param scale: OSM zoom level
|
:param zoom_level: zoom level in OpenStreetMap terminology
|
||||||
:param border: size of padding in pixels
|
:param border: size of padding in pixels
|
||||||
"""
|
"""
|
||||||
self.geo_boundaries: BoundaryBox = geo_boundaries
|
self.geo_boundaries: BoundaryBox = geo_boundaries
|
||||||
self.border: np.ndarray = border
|
self.border: np.ndarray = border
|
||||||
self.ratio: float = (
|
self.ratio: float = (
|
||||||
osm_zoom_level_to_pixels_per_meter(scale) * EQUATOR_LENGTH / 360
|
osm_zoom_level_to_pixels_per_meter(zoom_level)
|
||||||
|
* EQUATOR_LENGTH
|
||||||
|
/ 360
|
||||||
)
|
)
|
||||||
self.size: np.ndarray = border * 2 + self.ratio * (
|
self.size: np.ndarray = border * 2 + self.ratio * (
|
||||||
pseudo_mercator(self.geo_boundaries.max_())
|
pseudo_mercator(self.geo_boundaries.max_())
|
||||||
- pseudo_mercator(self.geo_boundaries.min_())
|
- pseudo_mercator(self.geo_boundaries.min_())
|
||||||
)
|
)
|
||||||
self.pixels_per_meter: float = osm_zoom_level_to_pixels_per_meter(scale)
|
self.pixels_per_meter: float = osm_zoom_level_to_pixels_per_meter(
|
||||||
|
zoom_level
|
||||||
|
)
|
||||||
self.size: np.ndarray = self.size.astype(int).astype(float)
|
self.size: np.ndarray = self.size.astype(int).astype(float)
|
||||||
|
|
||||||
def fling(self, coordinates: np.ndarray) -> np.ndarray:
|
def fling(self, coordinates: np.ndarray) -> np.ndarray:
|
||||||
|
|
|
@ -273,7 +273,7 @@ def ui(options: argparse.Namespace) -> None:
|
||||||
else:
|
else:
|
||||||
view_box = osm_data.view_box
|
view_box = osm_data.view_box
|
||||||
|
|
||||||
flinger: Flinger = Flinger(view_box, options.scale)
|
flinger: Flinger = Flinger(view_box, options.zoom)
|
||||||
size: np.ndarray = flinger.size
|
size: np.ndarray = flinger.size
|
||||||
|
|
||||||
svg: svgwrite.Drawing = svgwrite.Drawing(
|
svg: svgwrite.Drawing = svgwrite.Drawing(
|
||||||
|
|
|
@ -39,17 +39,17 @@ class _Handler(SimpleHTTPRequestHandler):
|
||||||
if not (len(parts) == 5 and not parts[0] and parts[1] == "tiles"):
|
if not (len(parts) == 5 and not parts[0] and parts[1] == "tiles"):
|
||||||
return
|
return
|
||||||
|
|
||||||
scale: int = int(parts[2])
|
zoom_level: int = int(parts[2])
|
||||||
x: int = int(parts[3])
|
x: int = int(parts[3])
|
||||||
y: int = int(parts[4])
|
y: int = int(parts[4])
|
||||||
tile_path: Path = workspace.get_tile_path()
|
tile_path: Path = workspace.get_tile_path()
|
||||||
png_path: Path = tile_path / f"tile_{scale}_{x}_{y}.png"
|
png_path: Path = tile_path / f"tile_{zoom_level}_{x}_{y}.png"
|
||||||
|
|
||||||
if self.update_cache:
|
if self.update_cache:
|
||||||
svg_path: Path = png_path.with_suffix(".svg")
|
svg_path: Path = png_path.with_suffix(".svg")
|
||||||
if not png_path.exists():
|
if not png_path.exists():
|
||||||
if not svg_path.exists():
|
if not svg_path.exists():
|
||||||
tile = Tile(x, y, scale)
|
tile = Tile(x, y, zoom_level)
|
||||||
tile.draw(tile_path, self.cache, self.options)
|
tile.draw(tile_path, self.cache, self.options)
|
||||||
with svg_path.open() as input_file:
|
with svg_path.open() as input_file:
|
||||||
cairosvg.svg2png(
|
cairosvg.svg2png(
|
||||||
|
|
114
roentgen/tile.py
114
roentgen/tile.py
|
@ -41,21 +41,23 @@ class Tile:
|
||||||
|
|
||||||
x: int
|
x: int
|
||||||
y: int
|
y: int
|
||||||
scale: int
|
zoom_level: int
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_coordinates(cls, coordinates: np.ndarray, scale: int) -> "Tile":
|
def from_coordinates(
|
||||||
|
cls, coordinates: np.ndarray, zoom_level: int
|
||||||
|
) -> "Tile":
|
||||||
"""
|
"""
|
||||||
Code from https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
|
Code from https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
|
||||||
|
|
||||||
:param coordinates: any coordinates inside tile, (latitude, longitude)
|
:param coordinates: any coordinates inside tile, (latitude, longitude)
|
||||||
:param scale: OpenStreetMap zoom level
|
:param zoom_level: zoom level in OpenStreetMap terminology
|
||||||
"""
|
"""
|
||||||
lat_rad: np.ndarray = np.radians(coordinates[0])
|
lat_rad: np.ndarray = np.radians(coordinates[0])
|
||||||
n: float = 2.0 ** scale
|
n: float = 2.0 ** zoom_level
|
||||||
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)
|
||||||
return cls(x, y, scale)
|
return cls(x, y, zoom_level)
|
||||||
|
|
||||||
def get_coordinates(self) -> np.ndarray:
|
def get_coordinates(self) -> np.ndarray:
|
||||||
"""
|
"""
|
||||||
|
@ -63,7 +65,7 @@ class Tile:
|
||||||
|
|
||||||
Code from https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
|
Code from https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
|
||||||
"""
|
"""
|
||||||
n: float = 2.0 ** self.scale
|
n: float = 2.0 ** self.zoom_level
|
||||||
lon_deg: float = self.x / n * 360.0 - 180.0
|
lon_deg: float = self.x / n * 360.0 - 180.0
|
||||||
lat_rad: float = np.arctan(np.sinh(np.pi * (1 - 2 * self.y / n)))
|
lat_rad: float = np.arctan(np.sinh(np.pi * (1 - 2 * self.y / n)))
|
||||||
lat_deg: np.ndarray = np.degrees(lat_rad)
|
lat_deg: np.ndarray = np.degrees(lat_rad)
|
||||||
|
@ -76,14 +78,14 @@ class Tile:
|
||||||
"""
|
"""
|
||||||
return (
|
return (
|
||||||
self.get_coordinates(),
|
self.get_coordinates(),
|
||||||
Tile(self.x + 1, self.y + 1, self.scale).get_coordinates(),
|
Tile(self.x + 1, self.y + 1, self.zoom_level).get_coordinates(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_extended_boundary_box(self) -> BoundaryBox:
|
def get_extended_boundary_box(self) -> BoundaryBox:
|
||||||
"""Same as get_boundary_box, but with extended boundaries."""
|
"""Same as get_boundary_box, but with extended boundaries."""
|
||||||
point_1: np.ndarray = self.get_coordinates()
|
point_1: np.ndarray = self.get_coordinates()
|
||||||
point_2: np.ndarray = Tile(
|
point_2: np.ndarray = Tile(
|
||||||
self.x + 1, self.y + 1, self.scale
|
self.x + 1, self.y + 1, self.zoom_level
|
||||||
).get_coordinates()
|
).get_coordinates()
|
||||||
|
|
||||||
return BoundaryBox(
|
return BoundaryBox(
|
||||||
|
@ -105,7 +107,7 @@ class Tile:
|
||||||
|
|
||||||
def get_file_name(self, directory_name: Path) -> Path:
|
def get_file_name(self, directory_name: Path) -> Path:
|
||||||
"""Get tile output SVG file path."""
|
"""Get tile output SVG file path."""
|
||||||
return directory_name / f"tile_{self.scale}_{self.x}_{self.y}.svg"
|
return directory_name / f"tile_{self.zoom_level}_{self.x}_{self.y}.svg"
|
||||||
|
|
||||||
def exists(self, directory_name: Path) -> bool:
|
def exists(self, directory_name: Path) -> bool:
|
||||||
"""Check whether the tile is drawn."""
|
"""Check whether the tile is drawn."""
|
||||||
|
@ -114,7 +116,8 @@ class Tile:
|
||||||
def get_carto_address(self) -> str:
|
def get_carto_address(self) -> str:
|
||||||
"""Get URL of this tile from the OpenStreetMap server."""
|
"""Get URL of this tile from the OpenStreetMap server."""
|
||||||
return (
|
return (
|
||||||
f"https://tile.openstreetmap.org/{self.scale}/{self.x}/{self.y}.png"
|
f"https://tile.openstreetmap.org/"
|
||||||
|
f"{self.zoom_level}/{self.x}/{self.y}.png"
|
||||||
)
|
)
|
||||||
|
|
||||||
def draw(
|
def draw(
|
||||||
|
@ -146,11 +149,11 @@ class Tile:
|
||||||
"""Draw SVG and PNG tile using OpenStreetMap data."""
|
"""Draw SVG and PNG tile using OpenStreetMap data."""
|
||||||
top, left = self.get_coordinates()
|
top, left = self.get_coordinates()
|
||||||
bottom, right = Tile(
|
bottom, right = Tile(
|
||||||
self.x + 1, self.y + 1, self.scale
|
self.x + 1, self.y + 1, self.zoom_level
|
||||||
).get_coordinates()
|
).get_coordinates()
|
||||||
|
|
||||||
flinger: Flinger = Flinger(
|
flinger: Flinger = Flinger(
|
||||||
BoundaryBox(left, bottom, right, top), self.scale
|
BoundaryBox(left, bottom, right, top), self.zoom_level
|
||||||
)
|
)
|
||||||
size: np.ndarray = flinger.size
|
size: np.ndarray = flinger.size
|
||||||
|
|
||||||
|
@ -192,31 +195,33 @@ class Tiles:
|
||||||
tiles: list[Tile]
|
tiles: list[Tile]
|
||||||
tile_1: Tile # Left top tile.
|
tile_1: Tile # Left top tile.
|
||||||
tile_2: Tile # Right bottom tile.
|
tile_2: Tile # Right bottom tile.
|
||||||
scale: int # OpenStreetMap zoom level.
|
zoom_level: int # OpenStreetMap zoom level.
|
||||||
boundary_box: BoundaryBox
|
boundary_box: BoundaryBox
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_boundary_box(
|
def from_boundary_box(
|
||||||
cls, boundary_box: BoundaryBox, scale: int
|
cls, boundary_box: BoundaryBox, zoom_level: int
|
||||||
) -> "Tiles":
|
) -> "Tiles":
|
||||||
"""
|
"""
|
||||||
Create minimal set of tiles that covers 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 zoom_level: zoom level in OpenStreetMap terminology
|
||||||
"""
|
"""
|
||||||
tiles: list[Tile] = []
|
tiles: list[Tile] = []
|
||||||
tile_1: Tile = Tile.from_coordinates(boundary_box.get_left_top(), scale)
|
tile_1: Tile = Tile.from_coordinates(
|
||||||
|
boundary_box.get_left_top(), zoom_level
|
||||||
|
)
|
||||||
tile_2: Tile = Tile.from_coordinates(
|
tile_2: Tile = Tile.from_coordinates(
|
||||||
boundary_box.get_right_bottom(), scale
|
boundary_box.get_right_bottom(), zoom_level
|
||||||
)
|
)
|
||||||
for x in range(tile_1.x, tile_2.x + 1):
|
for x in range(tile_1.x, tile_2.x + 1):
|
||||||
for y in range(tile_1.y, tile_2.y + 1):
|
for y in range(tile_1.y, tile_2.y + 1):
|
||||||
tiles.append(Tile(x, y, scale))
|
tiles.append(Tile(x, y, zoom_level))
|
||||||
|
|
||||||
latitude_2, longitude_1 = tile_1.get_coordinates()
|
latitude_2, longitude_1 = tile_1.get_coordinates()
|
||||||
latitude_1, longitude_2 = Tile(
|
latitude_1, longitude_2 = Tile(
|
||||||
tile_2.x + 1, tile_2.y + 1, scale
|
tile_2.x + 1, tile_2.y + 1, zoom_level
|
||||||
).get_coordinates()
|
).get_coordinates()
|
||||||
assert longitude_2 > longitude_1
|
assert longitude_2 > longitude_1
|
||||||
assert latitude_2 > latitude_1
|
assert latitude_2 > latitude_1
|
||||||
|
@ -225,7 +230,7 @@ class Tiles:
|
||||||
longitude_1, latitude_1, longitude_2, latitude_2
|
longitude_1, latitude_1, longitude_2, latitude_2
|
||||||
).round()
|
).round()
|
||||||
|
|
||||||
return cls(tiles, tile_1, tile_2, scale, extended_boundary_box)
|
return cls(tiles, tile_1, tile_2, zoom_level, extended_boundary_box)
|
||||||
|
|
||||||
def load_osm_data(self, cache_path: Path) -> OSMData:
|
def load_osm_data(self, cache_path: Path) -> OSMData:
|
||||||
"""Load OpenStreetMap data."""
|
"""Load OpenStreetMap data."""
|
||||||
|
@ -307,11 +312,16 @@ class Tiles:
|
||||||
cropped.crop((0, 0, TILE_WIDTH, TILE_HEIGHT)).save(
|
cropped.crop((0, 0, TILE_WIDTH, TILE_HEIGHT)).save(
|
||||||
tile.get_file_name(directory).with_suffix(".png")
|
tile.get_file_name(directory).with_suffix(".png")
|
||||||
)
|
)
|
||||||
logging.info(f"Tile {tile.scale}/{tile.x}/{tile.y} is created.")
|
logging.info(
|
||||||
|
f"Tile {tile.zoom_level}/{tile.x}/{tile.y} is created."
|
||||||
|
)
|
||||||
|
|
||||||
def get_file_path(self, cache_path: Path) -> Path:
|
def get_file_path(self, cache_path: Path) -> Path:
|
||||||
"""Get path of the output SVG file."""
|
"""Get path of the output SVG file."""
|
||||||
return cache_path / f"{self.boundary_box.get_format()}_{self.scale}.svg"
|
return (
|
||||||
|
cache_path
|
||||||
|
/ f"{self.boundary_box.get_format()}_{self.zoom_level}.svg"
|
||||||
|
)
|
||||||
|
|
||||||
def draw_image(
|
def draw_image(
|
||||||
self, cache_path: Path, configuration: MapConfiguration
|
self, cache_path: Path, configuration: MapConfiguration
|
||||||
|
@ -337,11 +347,11 @@ class Tiles:
|
||||||
if not output_path.exists():
|
if not output_path.exists():
|
||||||
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.zoom_level
|
||||||
).get_coordinates()
|
).get_coordinates()
|
||||||
|
|
||||||
flinger: Flinger = Flinger(
|
flinger: Flinger = Flinger(
|
||||||
BoundaryBox(left, bottom, right, top), self.scale
|
BoundaryBox(left, bottom, right, top), self.zoom_level
|
||||||
)
|
)
|
||||||
extractor: ShapeExtractor = ShapeExtractor(
|
extractor: ShapeExtractor = ShapeExtractor(
|
||||||
workspace.ICONS_PATH, workspace.ICONS_CONFIG_PATH
|
workspace.ICONS_PATH, workspace.ICONS_CONFIG_PATH
|
||||||
|
@ -377,32 +387,32 @@ class ScaleConfigurationException(Exception):
|
||||||
"""Wrong configuration format."""
|
"""Wrong configuration format."""
|
||||||
|
|
||||||
|
|
||||||
def parse_scale(scale_specification: str) -> list[int]:
|
def parse_zoom_level(zoom_level_specification: str) -> list[int]:
|
||||||
"""Parse scale specification."""
|
"""Parse zoom level specification."""
|
||||||
parts: list[str]
|
parts: list[str]
|
||||||
if "," in scale_specification:
|
if "," in zoom_level_specification:
|
||||||
parts = scale_specification.split(",")
|
parts = zoom_level_specification.split(",")
|
||||||
else:
|
else:
|
||||||
parts = [scale_specification]
|
parts = [zoom_level_specification]
|
||||||
|
|
||||||
def parse(scale: str) -> int:
|
def parse(zoom_level: str) -> int:
|
||||||
"""Parse scale."""
|
"""Parse zoom level."""
|
||||||
parsed_scale: int = int(scale)
|
parsed_zoom_level: int = int(zoom_level)
|
||||||
if parsed_scale <= 0:
|
if parsed_zoom_level <= 0:
|
||||||
raise ScaleConfigurationException("Non positive scale.")
|
raise ScaleConfigurationException("Non positive zoom level.")
|
||||||
if parsed_scale > 20:
|
if parsed_zoom_level > 20:
|
||||||
raise ScaleConfigurationException("Scale is too big.")
|
raise ScaleConfigurationException("Scale is too big.")
|
||||||
return parsed_scale
|
return parsed_zoom_level
|
||||||
|
|
||||||
result: list[int] = []
|
result: list[int] = []
|
||||||
for part in parts:
|
for part in parts:
|
||||||
if "-" in part:
|
if "-" in part:
|
||||||
from_, to = part.split("-")
|
from_, to = part.split("-")
|
||||||
from_scale: int = parse(from_)
|
from_zoom_level: int = parse(from_)
|
||||||
to_scale: int = parse(to)
|
to_zoom_level: int = parse(to)
|
||||||
if from_scale > to_scale:
|
if from_zoom_level > to_zoom_level:
|
||||||
raise ScaleConfigurationException("Wrong range.")
|
raise ScaleConfigurationException("Wrong range.")
|
||||||
result += range(from_scale, to_scale + 1)
|
result += range(from_zoom_level, to_zoom_level + 1)
|
||||||
else:
|
else:
|
||||||
result.append(parse(part))
|
result.append(parse(part))
|
||||||
|
|
||||||
|
@ -414,28 +424,32 @@ def ui(options: argparse.Namespace) -> None:
|
||||||
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)
|
zoom_levels: list[int] = parse_zoom_level(options.zoom)
|
||||||
min_scale: int = min(scales)
|
min_zoom_level: int = min(zoom_levels)
|
||||||
|
|
||||||
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(","))
|
||||||
)
|
)
|
||||||
min_tile: Tile = Tile.from_coordinates(np.array(coordinates), min_scale)
|
min_tile: Tile = Tile.from_coordinates(
|
||||||
|
np.array(coordinates), min_zoom_level
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
osm_data: OSMData = min_tile.load_osm_data(Path(options.cache))
|
osm_data: OSMData = min_tile.load_osm_data(Path(options.cache))
|
||||||
except NetworkError as e:
|
except NetworkError as e:
|
||||||
raise NetworkError(f"Map is not loaded. {e.message}")
|
raise NetworkError(f"Map is not loaded. {e.message}")
|
||||||
|
|
||||||
for scale in scales:
|
for zoom_level in zoom_levels:
|
||||||
tile: Tile = Tile.from_coordinates(np.array(coordinates), scale)
|
tile: Tile = Tile.from_coordinates(
|
||||||
|
np.array(coordinates), zoom_level
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
tile.draw_with_osm_data(osm_data, directory, configuration)
|
tile.draw_with_osm_data(osm_data, directory, configuration)
|
||||||
except NetworkError as e:
|
except NetworkError as e:
|
||||||
logging.fatal(e.message)
|
logging.fatal(e.message)
|
||||||
elif options.tile:
|
elif options.tile:
|
||||||
scale, x, y = map(int, options.tile.split("/"))
|
zoom_level, x, y = map(int, options.tile.split("/"))
|
||||||
tile: Tile = Tile(x, y, scale)
|
tile: Tile = Tile(x, y, zoom_level)
|
||||||
tile.draw(directory, Path(options.cache), configuration)
|
tile.draw(directory, Path(options.cache), configuration)
|
||||||
elif options.boundary_box:
|
elif options.boundary_box:
|
||||||
boundary_box: Optional[BoundaryBox] = BoundaryBox.from_text(
|
boundary_box: Optional[BoundaryBox] = BoundaryBox.from_text(
|
||||||
|
@ -443,14 +457,14 @@ def ui(options: argparse.Namespace) -> None:
|
||||||
)
|
)
|
||||||
if boundary_box is None:
|
if boundary_box is None:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
min_tiles: Tiles = Tiles.from_boundary_box(boundary_box, min_scale)
|
min_tiles: Tiles = Tiles.from_boundary_box(boundary_box, min_zoom_level)
|
||||||
try:
|
try:
|
||||||
osm_data: OSMData = min_tiles.load_osm_data(Path(options.cache))
|
osm_data: OSMData = min_tiles.load_osm_data(Path(options.cache))
|
||||||
except NetworkError as e:
|
except NetworkError as e:
|
||||||
raise NetworkError(f"Map is not loaded. {e.message}")
|
raise NetworkError(f"Map is not loaded. {e.message}")
|
||||||
|
|
||||||
for scale in scales:
|
for zoom_level in zoom_levels:
|
||||||
tiles: Tiles = Tiles.from_boundary_box(boundary_box, scale)
|
tiles: Tiles = Tiles.from_boundary_box(boundary_box, zoom_level)
|
||||||
tiles.draw(directory, Path(options.cache), configuration, osm_data)
|
tiles.draw(directory, Path(options.cache), configuration, osm_data)
|
||||||
else:
|
else:
|
||||||
logging.fatal(
|
logging.fatal(
|
||||||
|
|
|
@ -98,7 +98,7 @@ def add_tile_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-t",
|
"-t",
|
||||||
"--tile",
|
"--tile",
|
||||||
metavar="<scale>/<x>/<y>",
|
metavar="<zoom level>/<x>/<y>",
|
||||||
help="tile specification",
|
help="tile specification",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
@ -115,8 +115,8 @@ def add_tile_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
metavar="<lon1>,<lat1>,<lon2>,<lat2>",
|
metavar="<lon1>,<lat1>,<lon2>,<lat2>",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-s",
|
"-z",
|
||||||
"--scales",
|
"--zoom",
|
||||||
type=str,
|
type=str,
|
||||||
metavar="<integer>",
|
metavar="<integer>",
|
||||||
help="OSM zoom levels; can be list of numbers or ranges, e.g. `16-18`, "
|
help="OSM zoom levels; can be list of numbers or ranges, e.g. `16-18`, "
|
||||||
|
@ -175,8 +175,8 @@ def add_render_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
metavar="<path>",
|
metavar="<path>",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-s",
|
"-z",
|
||||||
"--scale",
|
"--zoom",
|
||||||
type=int,
|
type=int,
|
||||||
metavar="<integer>",
|
metavar="<integer>",
|
||||||
help="OSM zoom level",
|
help="OSM zoom level",
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
"""
|
|
||||||
Test scale specification parsing.
|
|
||||||
"""
|
|
||||||
from roentgen.tile import parse_scale
|
|
||||||
|
|
||||||
|
|
||||||
def test_scale_1() -> None:
|
|
||||||
assert parse_scale("18") == [18]
|
|
||||||
|
|
||||||
|
|
||||||
def test_scale_list() -> None:
|
|
||||||
assert parse_scale("17,18") == [17, 18]
|
|
||||||
assert parse_scale("16,17,18") == [16, 17, 18]
|
|
||||||
|
|
||||||
|
|
||||||
def test_scale_range() -> None:
|
|
||||||
assert parse_scale("16-18") == [16, 17, 18]
|
|
||||||
assert parse_scale("18-18") == [18]
|
|
||||||
|
|
||||||
|
|
||||||
def test_scale_mixed() -> None:
|
|
||||||
assert parse_scale("15,16-18") == [15, 16, 17, 18]
|
|
||||||
assert parse_scale("15,16-18,20") == [15, 16, 17, 18, 20]
|
|
23
test/test_zoom_level.py
Normal file
23
test/test_zoom_level.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
"""
|
||||||
|
Test zoom level specification parsing.
|
||||||
|
"""
|
||||||
|
from roentgen.tile import parse_zoom_level
|
||||||
|
|
||||||
|
|
||||||
|
def test_zoom_level_1() -> None:
|
||||||
|
assert parse_zoom_level("18") == [18]
|
||||||
|
|
||||||
|
|
||||||
|
def test_zoom_level_list() -> None:
|
||||||
|
assert parse_zoom_level("17,18") == [17, 18]
|
||||||
|
assert parse_zoom_level("16,17,18") == [16, 17, 18]
|
||||||
|
|
||||||
|
|
||||||
|
def test_zoom_level_range() -> None:
|
||||||
|
assert parse_zoom_level("16-18") == [16, 17, 18]
|
||||||
|
assert parse_zoom_level("18-18") == [18]
|
||||||
|
|
||||||
|
|
||||||
|
def test_zoom_level_mixed() -> None:
|
||||||
|
assert parse_zoom_level("15,16-18") == [15, 16, 17, 18]
|
||||||
|
assert parse_zoom_level("15,16-18,20") == [15, 16, 17, 18, 20]
|
Loading…
Add table
Add a link
Reference in a new issue