aletrn commited on
Commit
c6a47bd
·
1 Parent(s): 3209d49

[refactor] refactor tms2geotiff functions

Browse files
Files changed (1) hide show
  1. src/io/tms2geotiff.py +49 -52
src/io/tms2geotiff.py CHANGED
@@ -35,13 +35,12 @@ import math
35
  import re
36
  import time
37
  from typing import Tuple, Callable
38
- import PIL
39
  from PIL import Image
40
 
41
  from src import app_logger
42
  from src.utilities.constants import EARTH_EQUATORIAL_RADIUS, RETRY_DOWNLOAD, TIMEOUT_DOWNLOAD, TILE_SIZE, \
43
  CALLBACK_INTERVAL_DOWNLOAD
44
- from src.utilities.type_hints import PIL_Image
45
 
46
  Image.MAX_IMAGE_PIXELS = None
47
 
@@ -63,21 +62,21 @@ SESSION.headers.update({
63
  re_coords_split = re.compile('[ ,;]+')
64
 
65
 
66
- def from4326_to3857(lat, lon):
67
- x_tile = math.radians(lon) * EARTH_EQUATORIAL_RADIUS
68
- y_tile = math.log(math.tan(math.radians(45 + lat / 2.0))) * EARTH_EQUATORIAL_RADIUS
69
  return x_tile, y_tile
70
 
71
 
72
- def deg2num(lat, lon, zoom):
73
  n = 2 ** zoom
74
  x_tile = ((lon + 180) / 360 * n)
75
  y_tile = (1 - math.asinh(math.tan(math.radians(lat))) / math.pi) * n / 2
76
  return x_tile, y_tile
77
 
78
 
79
- def is_empty(im):
80
- extrema = im.getextrema()
81
  if len(extrema) >= 3:
82
  if len(extrema) > 3 and extrema[-1] == (0, 0):
83
  return True
@@ -85,39 +84,36 @@ def is_empty(im):
85
  if ext != (0, 0):
86
  return False
87
  return True
88
- else:
89
- return extrema[0] == (0, 0)
90
 
91
 
92
- def paste_tile(big_im, base_size, tile, corner_xy, bbox):
93
  if tile is None:
94
- return big_im
95
- im = Image.open(io.BytesIO(tile))
96
- mode = 'RGB' if im.mode == 'RGB' else 'RGBA'
97
- size = im.size
98
- if big_im is None:
99
- base_size[0] = size[0]
100
- base_size[1] = size[1]
101
- new_im = Image.new(mode, (
102
- size[0] * (bbox[2] - bbox[0]), size[1] * (bbox[3] - bbox[1])))
103
- else:
104
- new_im = big_im
105
-
106
- dx = abs(corner_xy[0] - bbox[0])
107
- dy = abs(corner_xy[1] - bbox[1])
108
- xy0 = (size[0] * dx, size[1] * dy)
109
- if mode == 'RGB':
110
- new_im.paste(im, xy0)
111
- else:
112
- if im.mode != mode:
113
- im = im.convert(mode)
114
- if not is_empty(im):
115
- new_im.paste(im, xy0)
116
- im.close()
117
- return new_im
118
-
119
-
120
- def get_tile(url):
121
  retry = RETRY_DOWNLOAD
122
  while 1:
123
  try:
@@ -135,14 +131,15 @@ def get_tile(url):
135
  return r.content
136
 
137
 
138
- def print_progress(progress, total, done=False):
 
139
  if done:
140
  app_logger.info('Downloaded image %d/%d, %.2f%%' % (progress, total, progress * 100 / total))
141
 
142
 
143
  def download_extent(
144
  source: str, lat0: float, lon0: float, lat1: float, lon1: float, zoom: int,
145
- save_image: bool = True, progress_callback: Callable = print_progress,
146
  callback_interval: float = CALLBACK_INTERVAL_DOWNLOAD
147
  ) -> Tuple[PIL_Image, Tuple[float]] or Tuple[None]:
148
  """
@@ -162,8 +159,8 @@ def download_extent(
162
  Returns:
163
  parsed request input
164
  """
165
- x0, y0 = deg2num(lat0, lon0, zoom)
166
- x1, y1 = deg2num(lat1, lon1, zoom)
167
  if x0 > x1:
168
  x0, x1 = x1, x0
169
  if y0 > y1:
@@ -181,13 +178,13 @@ def download_extent(
181
  cancelled = False
182
  with concurrent.futures.ThreadPoolExecutor(5) as executor:
183
  for x, y in corners:
184
- future = executor.submit(get_tile, source.format(z=zoom, x=x, y=y))
185
  futures[future] = (x, y)
186
  bbox = (math.floor(x0), math.floor(y0), math.ceil(x1), math.ceil(y1))
187
- big_im = None
188
  base_size = [TILE_SIZE, TILE_SIZE]
189
- big_im, cancelled, done_num = run_future_tile_download(
190
- base_size, bbox, big_im, callback_interval, cancelled, done_num, futures, last_callback, last_done_num,
191
  progress_callback, save_image, total_num
192
  )
193
  if cancelled:
@@ -203,19 +200,19 @@ def download_extent(
203
  y2 = round(base_size[1] * y_frac)
204
  img_w = round(base_size[0] * (x1 - x0))
205
  img_h = round(base_size[1] * (y1 - y0))
206
- final_image = big_im.crop((x2, y2, x2 + img_w, y2 + img_h))
207
  if final_image.mode == 'RGBA' and final_image.getextrema()[3] == (255, 255):
208
  final_image = final_image.convert('RGB')
209
- big_im.close()
210
- xp0, yp0 = from4326_to3857(lat0, lon0)
211
- xp1, yp1 = from4326_to3857(lat1, lon1)
212
  p_width = abs(xp1 - xp0) / final_image.size[0]
213
  p_height = abs(yp1 - yp0) / final_image.size[1]
214
  matrix = min(xp0, xp1), p_width, 0, max(yp0, yp1), 0, -p_height
215
  return final_image, matrix
216
 
217
 
218
- def run_future_tile_download(
219
  base_size, bbox, big_im, callback_interval, cancelled, done_num, futures, last_callback, last_done_num,
220
  progress_callback, save_image, total_num
221
  ):
@@ -228,7 +225,7 @@ def run_future_tile_download(
228
  img_data = fut.result()
229
  xy = futures[fut]
230
  if save_image:
231
- big_im = paste_tile(big_im, base_size, img_data, xy, bbox)
232
  del futures[fut]
233
  done_num += 1
234
  if time.monotonic() > last_callback + callback_interval:
 
35
  import re
36
  import time
37
  from typing import Tuple, Callable
 
38
  from PIL import Image
39
 
40
  from src import app_logger
41
  from src.utilities.constants import EARTH_EQUATORIAL_RADIUS, RETRY_DOWNLOAD, TIMEOUT_DOWNLOAD, TILE_SIZE, \
42
  CALLBACK_INTERVAL_DOWNLOAD
43
+ from src.utilities.type_hints import PIL_Image, tuple_float, tuple_float_any, list_int, tuple_int
44
 
45
  Image.MAX_IMAGE_PIXELS = None
46
 
 
62
  re_coords_split = re.compile('[ ,;]+')
63
 
64
 
65
+ def _from4326_to3857(lat: float, lon: float) -> tuple_float or tuple_float_any:
66
+ x_tile: float = math.radians(lon) * EARTH_EQUATORIAL_RADIUS
67
+ y_tile: float = math.log(math.tan(math.radians(45 + lat / 2.0))) * EARTH_EQUATORIAL_RADIUS
68
  return x_tile, y_tile
69
 
70
 
71
+ def _deg2num(lat: float, lon: float, zoom: int):
72
  n = 2 ** zoom
73
  x_tile = ((lon + 180) / 360 * n)
74
  y_tile = (1 - math.asinh(math.tan(math.radians(lat))) / math.pi) * n / 2
75
  return x_tile, y_tile
76
 
77
 
78
+ def _is_empty(image):
79
+ extrema = image.getextrema()
80
  if len(extrema) >= 3:
81
  if len(extrema) > 3 and extrema[-1] == (0, 0):
82
  return True
 
84
  if ext != (0, 0):
85
  return False
86
  return True
87
+ return extrema[0] == (0, 0)
 
88
 
89
 
90
+ def _paste_tile(big_image: PIL_Image or None, base_size: list_int, tile: bytes, corner_xy: tuple_int, bbox: tuple_int):
91
  if tile is None:
92
+ return big_image
93
+ with Image.open(io.BytesIO(tile)) as tmp_image:
94
+ mode = 'RGB' if tmp_image.mode == 'RGB' else 'RGBA'
95
+ size = tmp_image.size
96
+ new_image = big_image
97
+ if big_image is None:
98
+ base_size[0] = size[0]
99
+ base_size[1] = size[1]
100
+ new_image = Image.new(mode, (
101
+ size[0] * (bbox[2] - bbox[0]), size[1] * (bbox[3] - bbox[1])))
102
+
103
+ dx = abs(corner_xy[0] - bbox[0])
104
+ dy = abs(corner_xy[1] - bbox[1])
105
+ xy0 = (size[0] * dx, size[1] * dy)
106
+ if mode == 'RGB':
107
+ new_image.paste(tmp_image, xy0)
108
+ else:
109
+ if tmp_image.mode != mode:
110
+ tmp_image = tmp_image.convert(mode)
111
+ if not _is_empty(tmp_image):
112
+ new_image.paste(tmp_image, xy0)
113
+ return new_image
114
+
115
+
116
+ def _get_tile(url: str) -> bytes or None:
 
 
117
  retry = RETRY_DOWNLOAD
118
  while 1:
119
  try:
 
131
  return r.content
132
 
133
 
134
+ def log_progress(progress, total, done=False):
135
+ """log the progress download"""
136
  if done:
137
  app_logger.info('Downloaded image %d/%d, %.2f%%' % (progress, total, progress * 100 / total))
138
 
139
 
140
  def download_extent(
141
  source: str, lat0: float, lon0: float, lat1: float, lon1: float, zoom: int,
142
+ save_image: bool = True, progress_callback: Callable = log_progress,
143
  callback_interval: float = CALLBACK_INTERVAL_DOWNLOAD
144
  ) -> Tuple[PIL_Image, Tuple[float]] or Tuple[None]:
145
  """
 
159
  Returns:
160
  parsed request input
161
  """
162
+ x0, y0 = _deg2num(lat0, lon0, zoom)
163
+ x1, y1 = _deg2num(lat1, lon1, zoom)
164
  if x0 > x1:
165
  x0, x1 = x1, x0
166
  if y0 > y1:
 
178
  cancelled = False
179
  with concurrent.futures.ThreadPoolExecutor(5) as executor:
180
  for x, y in corners:
181
+ future = executor.submit(_get_tile, source.format(z=zoom, x=x, y=y))
182
  futures[future] = (x, y)
183
  bbox = (math.floor(x0), math.floor(y0), math.ceil(x1), math.ceil(y1))
184
+ big_image = None
185
  base_size = [TILE_SIZE, TILE_SIZE]
186
+ big_image, cancelled, done_num = _run_future_tile_download(
187
+ base_size, bbox, big_image, callback_interval, cancelled, done_num, futures, last_callback, last_done_num,
188
  progress_callback, save_image, total_num
189
  )
190
  if cancelled:
 
200
  y2 = round(base_size[1] * y_frac)
201
  img_w = round(base_size[0] * (x1 - x0))
202
  img_h = round(base_size[1] * (y1 - y0))
203
+ final_image = big_image.crop((x2, y2, x2 + img_w, y2 + img_h))
204
  if final_image.mode == 'RGBA' and final_image.getextrema()[3] == (255, 255):
205
  final_image = final_image.convert('RGB')
206
+ big_image.close()
207
+ xp0, yp0 = _from4326_to3857(lat0, lon0)
208
+ xp1, yp1 = _from4326_to3857(lat1, lon1)
209
  p_width = abs(xp1 - xp0) / final_image.size[0]
210
  p_height = abs(yp1 - yp0) / final_image.size[1]
211
  matrix = min(xp0, xp1), p_width, 0, max(yp0, yp1), 0, -p_height
212
  return final_image, matrix
213
 
214
 
215
+ def _run_future_tile_download(
216
  base_size, bbox, big_im, callback_interval, cancelled, done_num, futures, last_callback, last_done_num,
217
  progress_callback, save_image, total_num
218
  ):
 
225
  img_data = fut.result()
226
  xy = futures[fut]
227
  if save_image:
228
+ big_im = _paste_tile(big_im, base_size, img_data, xy, bbox)
229
  del futures[fut]
230
  done_num += 1
231
  if time.monotonic() > last_callback + callback_interval: