mirror of
https://github.com/enzet/map-machine.git
synced 2025-05-21 21:16:24 +02:00
Issue #69: support PNG image splitting.
This commit is contained in:
parent
a8dd8390f7
commit
3f5b88fd20
5 changed files with 58 additions and 29 deletions
|
@ -11,7 +11,7 @@ from colour import Color
|
||||||
|
|
||||||
from roentgen import ui
|
from roentgen import ui
|
||||||
from roentgen.color import get_gradient_color
|
from roentgen.color import get_gradient_color
|
||||||
from roentgen.figure import Building, Road, StyledFigure, Tree, DirectionSector
|
from roentgen.figure import Building, DirectionSector, Road, StyledFigure, Tree
|
||||||
from roentgen.flinger import Flinger
|
from roentgen.flinger import Flinger
|
||||||
|
|
||||||
# fmt: off
|
# fmt: off
|
||||||
|
@ -21,7 +21,7 @@ from roentgen.icon import (
|
||||||
from roentgen.osm_reader import OSMData, OSMNode, OSMRelation, OSMWay
|
from roentgen.osm_reader import OSMData, OSMNode, OSMRelation, OSMWay
|
||||||
from roentgen.point import Point
|
from roentgen.point import Point
|
||||||
from roentgen.scheme import DEFAULT_COLOR, LineStyle, Scheme
|
from roentgen.scheme import DEFAULT_COLOR, LineStyle, Scheme
|
||||||
from roentgen.ui import TIME_MODE, AUTHOR_MODE
|
from roentgen.ui import AUTHOR_MODE, TIME_MODE
|
||||||
from roentgen.util import MinMax
|
from roentgen.util import MinMax
|
||||||
|
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
|
@ -3,7 +3,7 @@ Drawing utility.
|
||||||
"""
|
"""
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, List, Union
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
import cairo
|
import cairo
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
|
@ -8,7 +8,7 @@ from colour import Color
|
||||||
from svgwrite import Drawing
|
from svgwrite import Drawing
|
||||||
from svgwrite.path import Path
|
from svgwrite.path import Path
|
||||||
|
|
||||||
from roentgen.direction import Sector, DirectionSet
|
from roentgen.direction import DirectionSet, Sector
|
||||||
from roentgen.flinger import Flinger
|
from roentgen.flinger import Flinger
|
||||||
from roentgen.osm_reader import OSMNode, Tagged
|
from roentgen.osm_reader import OSMNode, Tagged
|
||||||
from roentgen.road import Lane
|
from roentgen.road import Lane
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Röntgen tile server for sloppy maps.
|
Röntgen tile server for sloppy maps.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from http.server import SimpleHTTPRequestHandler, HTTPServer
|
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ from typing import Optional
|
||||||
import cairosvg
|
import cairosvg
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import svgwrite
|
import svgwrite
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
from roentgen.constructor import Constructor
|
from roentgen.constructor import Constructor
|
||||||
from roentgen.flinger import Flinger
|
from roentgen.flinger import Flinger
|
||||||
|
@ -35,14 +36,19 @@ class Tiles:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tiles: list["Tile"]
|
tiles: list["Tile"]
|
||||||
tile_1: "Tile"
|
tile_1: "Tile" # Left top tile.
|
||||||
tile_2: "Tile"
|
tile_2: "Tile" # Right bottom tile.
|
||||||
scale: int
|
scale: int # OpenStreetMap zoom level.
|
||||||
boundary_box: BoundaryBox
|
boundary_box: BoundaryBox
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_boundary_box(cls, boundary_box: BoundaryBox, scale: int):
|
def from_boundary_box(cls, boundary_box: BoundaryBox, scale: int):
|
||||||
"""Create minimal set of tiles that cover boundary box."""
|
"""
|
||||||
|
Create minimal set of tiles that cover boundary box.
|
||||||
|
|
||||||
|
:param boundary_box: area to be covered by tiles
|
||||||
|
:param scale: OpenStreetMap zoom level
|
||||||
|
"""
|
||||||
tiles: list["Tile"] = []
|
tiles: list["Tile"] = []
|
||||||
tile_1 = Tile.from_coordinates(boundary_box.get_left_top(), scale)
|
tile_1 = Tile.from_coordinates(boundary_box.get_left_top(), scale)
|
||||||
tile_2 = Tile.from_coordinates(boundary_box.get_right_bottom(), scale)
|
tile_2 = Tile.from_coordinates(boundary_box.get_right_bottom(), scale)
|
||||||
|
@ -51,20 +57,23 @@ class Tiles:
|
||||||
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, scale))
|
||||||
|
|
||||||
lat_2, lon_1 = tile_1.get_coordinates()
|
latitude_2, longitude_1 = tile_1.get_coordinates()
|
||||||
lat_1, lon_2 = Tile(tile_2.x + 1, tile_2.y + 1, scale).get_coordinates()
|
latitude_1, longitude_2 = Tile(
|
||||||
assert lon_2 > lon_1
|
tile_2.x + 1, tile_2.y + 1, scale
|
||||||
assert lat_2 > lat_1
|
).get_coordinates()
|
||||||
|
assert longitude_2 > longitude_1
|
||||||
|
assert latitude_2 > latitude_1
|
||||||
|
|
||||||
extended_boundary_box: BoundaryBox = BoundaryBox(
|
extended_boundary_box: BoundaryBox = BoundaryBox(
|
||||||
lon_1, lat_1, lon_2, lat_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, scale, extended_boundary_box)
|
||||||
|
|
||||||
def draw(self, directory: Path, cache_path: Path) -> None:
|
def draw_separately(self, directory: Path, cache_path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
Draw set of tiles.
|
Draw set of tiles as SVG file separately and rasterize them into a set
|
||||||
|
of PNG files with cairosvg.
|
||||||
|
|
||||||
: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
|
||||||
|
@ -92,6 +101,35 @@ class Tiles:
|
||||||
else:
|
else:
|
||||||
logging.info(f"File {output_path} already exists.")
|
logging.info(f"File {output_path} already exists.")
|
||||||
|
|
||||||
|
def draw(self, directory: Path, cache_path: Path) -> None:
|
||||||
|
"""
|
||||||
|
Draw one PNG image with all tiles and split it into a set of separate
|
||||||
|
PNG file with Pillow.
|
||||||
|
|
||||||
|
:param directory: directory for tiles
|
||||||
|
:param cache_path: directory for temporary OSM files
|
||||||
|
"""
|
||||||
|
input_path: Path = cache_path / (
|
||||||
|
self.boundary_box.get_format() + ".png"
|
||||||
|
)
|
||||||
|
self.draw_image(cache_path)
|
||||||
|
width, height = 256, 256
|
||||||
|
|
||||||
|
with input_path.open("rb") as input_file:
|
||||||
|
image = Image.open(input_file)
|
||||||
|
|
||||||
|
for tile in self.tiles:
|
||||||
|
x = tile.x - self.tile_1.x
|
||||||
|
y = tile.y - self.tile_1.y
|
||||||
|
cropped = image.crop(
|
||||||
|
(x * width, y * height, (x + 1) * width, (y + 1) * height)
|
||||||
|
)
|
||||||
|
print(x * width, y * height, (x + 1) * width, (y + 1) * height)
|
||||||
|
cropped.crop((0, 0, width, height)).save(
|
||||||
|
tile.get_file_name(directory).with_suffix(".png")
|
||||||
|
)
|
||||||
|
logging.info(f"Tile 18/{tile.x}/{tile.y} is created.")
|
||||||
|
|
||||||
def draw_image(self, cache_path: Path) -> None:
|
def draw_image(self, cache_path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
Draw all tiles as one picture.
|
Draw all tiles as one picture.
|
||||||
|
@ -159,9 +197,7 @@ class Tile:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_coordinates(cls, coordinates: np.array, scale: int):
|
def from_coordinates(cls, coordinates: np.array, scale: int):
|
||||||
"""
|
"""Code from https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames"""
|
||||||
Code from https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
|
|
||||||
"""
|
|
||||||
lat_rad = np.radians(coordinates[0])
|
lat_rad = 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)
|
||||||
|
@ -191,9 +227,7 @@ class Tile:
|
||||||
)
|
)
|
||||||
|
|
||||||
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.array = self.get_coordinates()
|
point_1: np.array = self.get_coordinates()
|
||||||
point_2: np.array = Tile(
|
point_2: np.array = Tile(
|
||||||
self.x + 1, self.y + 1, self.scale
|
self.x + 1, self.y + 1, self.scale
|
||||||
|
@ -217,15 +251,11 @@ class Tile:
|
||||||
return OSMReader().parse_osm_file(cache_file_path)
|
return OSMReader().parse_osm_file(cache_file_path)
|
||||||
|
|
||||||
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.scale}_{self.x}_{self.y}.svg"
|
||||||
|
|
||||||
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/{self.scale}/{self.x}/{self.y}.png"
|
||||||
)
|
)
|
||||||
|
@ -302,7 +332,6 @@ def ui(options) -> None:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
tiles: Tiles = Tiles.from_boundary_box(boundary_box, options.scale)
|
tiles: Tiles = Tiles.from_boundary_box(boundary_box, options.scale)
|
||||||
tiles.draw(directory, Path(options.cache))
|
tiles.draw(directory, Path(options.cache))
|
||||||
tiles.draw_image(Path(options.cache))
|
|
||||||
else:
|
else:
|
||||||
logging.fatal(
|
logging.fatal(
|
||||||
"Specify either --coordinates, --boundary-box, or --tile."
|
"Specify either --coordinates, --boundary-box, or --tile."
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue