From 3f5b88fd20f1f6fada3da23c32363aaae5ec5b41 Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Sun, 22 Aug 2021 00:55:55 +0300 Subject: [PATCH] Issue #69: support PNG image splitting. --- roentgen/constructor.py | 4 +-- roentgen/drawing.py | 2 +- roentgen/figure.py | 2 +- roentgen/server.py | 2 +- roentgen/tile.py | 77 ++++++++++++++++++++++++++++------------- 5 files changed, 58 insertions(+), 29 deletions(-) diff --git a/roentgen/constructor.py b/roentgen/constructor.py index 5980b21..5cf441b 100644 --- a/roentgen/constructor.py +++ b/roentgen/constructor.py @@ -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 diff --git a/roentgen/drawing.py b/roentgen/drawing.py index bfc6c95..3853586 100644 --- a/roentgen/drawing.py +++ b/roentgen/drawing.py @@ -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 diff --git a/roentgen/figure.py b/roentgen/figure.py index 8dc4207..c599263 100644 --- a/roentgen/figure.py +++ b/roentgen/figure.py @@ -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 diff --git a/roentgen/server.py b/roentgen/server.py index f9bdacd..cf81645 100644 --- a/roentgen/server.py +++ b/roentgen/server.py @@ -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 diff --git a/roentgen/tile.py b/roentgen/tile.py index 63a8528..983d004 100644 --- a/roentgen/tile.py +++ b/roentgen/tile.py @@ -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."