Issue #69: support PNG image splitting.

This commit is contained in:
Sergey Vartanov 2021-08-22 00:55:55 +03:00
parent a8dd8390f7
commit 3f5b88fd20
5 changed files with 58 additions and 29 deletions

View file

@ -11,7 +11,7 @@ from colour import Color
from roentgen import ui
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
# fmt: off
@ -21,7 +21,7 @@ from roentgen.icon import (
from roentgen.osm_reader import OSMData, OSMNode, OSMRelation, OSMWay
from roentgen.point import Point
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
# fmt: on

View file

@ -3,7 +3,7 @@ Drawing utility.
"""
from dataclasses import dataclass
from pathlib import Path
from typing import Optional, List, Union
from typing import List, Optional, Union
import cairo
import numpy as np

View file

@ -8,7 +8,7 @@ from colour import Color
from svgwrite import Drawing
from svgwrite.path import Path
from roentgen.direction import Sector, DirectionSet
from roentgen.direction import DirectionSet, Sector
from roentgen.flinger import Flinger
from roentgen.osm_reader import OSMNode, Tagged
from roentgen.road import Lane

View file

@ -2,7 +2,7 @@
Röntgen tile server for sloppy maps.
"""
import logging
from http.server import SimpleHTTPRequestHandler, HTTPServer
from http.server import HTTPServer, SimpleHTTPRequestHandler
from pathlib import Path
from typing import Optional

View file

@ -12,6 +12,7 @@ from typing import Optional
import cairosvg
import numpy as np
import svgwrite
from PIL import Image
from roentgen.constructor import Constructor
from roentgen.flinger import Flinger
@ -35,14 +36,19 @@ class Tiles:
"""
tiles: list["Tile"]
tile_1: "Tile"
tile_2: "Tile"
scale: int
tile_1: "Tile" # Left top tile.
tile_2: "Tile" # Right bottom tile.
scale: int # OpenStreetMap zoom level.
boundary_box: BoundaryBox
@classmethod
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"] = []
tile_1 = Tile.from_coordinates(boundary_box.get_left_top(), 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):
tiles.append(Tile(x, y, scale))
lat_2, lon_1 = tile_1.get_coordinates()
lat_1, lon_2 = Tile(tile_2.x + 1, tile_2.y + 1, scale).get_coordinates()
assert lon_2 > lon_1
assert lat_2 > lat_1
latitude_2, longitude_1 = tile_1.get_coordinates()
latitude_1, longitude_2 = Tile(
tile_2.x + 1, tile_2.y + 1, scale
).get_coordinates()
assert longitude_2 > longitude_1
assert latitude_2 > latitude_1
extended_boundary_box: BoundaryBox = BoundaryBox(
lon_1, lat_1, lon_2, lat_2
longitude_1, latitude_1, longitude_2, latitude_2
).round()
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 cache_path: directory for temporary OSM files
@ -92,6 +101,35 @@ class Tiles:
else:
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:
"""
Draw all tiles as one picture.
@ -159,9 +197,7 @@ class Tile:
@classmethod
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])
n: float = 2.0 ** scale
x: int = int((coordinates[1] + 180.0) / 360.0 * n)
@ -191,9 +227,7 @@ class Tile:
)
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_2: np.array = Tile(
self.x + 1, self.y + 1, self.scale
@ -217,15 +251,11 @@ class Tile:
return OSMReader().parse_osm_file(cache_file_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"
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 (
f"https://tile.openstreetmap.org/{self.scale}/{self.x}/{self.y}.png"
)
@ -302,7 +332,6 @@ def ui(options) -> None:
sys.exit(1)
tiles: Tiles = Tiles.from_boundary_box(boundary_box, options.scale)
tiles.draw(directory, Path(options.cache))
tiles.draw_image(Path(options.cache))
else:
logging.fatal(
"Specify either --coordinates, --boundary-box, or --tile."