tilegrab 1.1.0__py3-none-any.whl → 1.2.0b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tilegrab/__init__.py +1 -1
- tilegrab/cli.py +95 -121
- tilegrab/dataset.py +12 -9
- tilegrab/downloader.py +32 -28
- tilegrab/images.py +178 -57
- tilegrab/logs.py +84 -0
- tilegrab/sources.py +14 -8
- tilegrab/tiles.py +166 -80
- {tilegrab-1.1.0.dist-info → tilegrab-1.2.0b2.dist-info}/METADATA +65 -100
- tilegrab-1.2.0b2.dist-info/RECORD +15 -0
- tilegrab/mosaic.py +0 -75
- tilegrab-1.1.0.dist-info/RECORD +0 -15
- {tilegrab-1.1.0.dist-info → tilegrab-1.2.0b2.dist-info}/WHEEL +0 -0
- {tilegrab-1.1.0.dist-info → tilegrab-1.2.0b2.dist-info}/entry_points.txt +0 -0
- {tilegrab-1.1.0.dist-info → tilegrab-1.2.0b2.dist-info}/licenses/LICENSE +0 -0
- {tilegrab-1.1.0.dist-info → tilegrab-1.2.0b2.dist-info}/top_level.txt +0 -0
tilegrab/__init__.py
CHANGED
tilegrab/cli.py
CHANGED
|
@@ -1,95 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
import logging
|
|
3
3
|
import argparse
|
|
4
|
-
import random
|
|
5
|
-
import sys
|
|
6
4
|
from pathlib import Path
|
|
5
|
+
from typing import List
|
|
7
6
|
from tilegrab.downloader import Downloader
|
|
8
|
-
from tilegrab.images import TileImageCollection
|
|
7
|
+
from tilegrab.images import TileImageCollection, ExportType
|
|
8
|
+
from tilegrab.logs import setup_logging
|
|
9
9
|
from tilegrab.tiles import TilesByShape, TilesByBBox
|
|
10
10
|
from tilegrab.dataset import GeoDataset
|
|
11
|
-
from tilegrab import
|
|
12
|
-
|
|
13
|
-
# Configure root logger
|
|
14
|
-
# logging.basicConfig(
|
|
15
|
-
# level=logging.INFO,
|
|
16
|
-
# format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
17
|
-
# handlers=[
|
|
18
|
-
# logging.StreamHandler(sys.stdout),
|
|
19
|
-
# logging.FileHandler('tilegrab.log')
|
|
20
|
-
# ]
|
|
21
|
-
# )
|
|
22
|
-
|
|
23
|
-
# Normal colors
|
|
24
|
-
BLACK = "\033[30m"
|
|
25
|
-
RED = "\033[31m"
|
|
26
|
-
GREEN = "\033[32m"
|
|
27
|
-
YELLOW = "\033[33m"
|
|
28
|
-
BLUE = "\033[34m"
|
|
29
|
-
MAGENTA = "\033[35m"
|
|
30
|
-
CYAN = "\033[36m"
|
|
31
|
-
GRAY = "\033[90m"
|
|
32
|
-
WHITE = "\033[37m"
|
|
33
|
-
|
|
34
|
-
# Bright colors
|
|
35
|
-
BBLACK = "\033[90m"
|
|
36
|
-
BRED = "\033[91m"
|
|
37
|
-
BGREEN = "\033[92m"
|
|
38
|
-
BYELLOW = "\033[93m"
|
|
39
|
-
BBLUE = "\033[94m"
|
|
40
|
-
BMAGENTA = "\033[95m"
|
|
41
|
-
BCYAN = "\033[96m"
|
|
42
|
-
BGRAY = "\033[97m"
|
|
43
|
-
BWHITE = "\033[97m"
|
|
44
|
-
|
|
45
|
-
RESET = "\033[0m"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
class LogFormatter(logging.Formatter):
|
|
49
|
-
NAME_WIDTH = 14
|
|
50
|
-
|
|
51
|
-
LEVEL_MAP = {
|
|
52
|
-
logging.CRITICAL: f'{RED}‼ {RESET}',
|
|
53
|
-
logging.ERROR: f'{RED}✖ {RESET}',
|
|
54
|
-
logging.WARNING: f'{YELLOW}⚠ {RESET}',
|
|
55
|
-
logging.INFO: f'{BLUE}• {RESET}',
|
|
56
|
-
logging.DEBUG: f'{GRAY}· {RESET}',
|
|
57
|
-
logging.NOTSET: f'{CYAN}- {RESET}',
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
def format(self, record):
|
|
61
|
-
record.level_letter = self.LEVEL_MAP.get(record.levelno, '?')
|
|
62
|
-
|
|
63
|
-
short = record.name.rsplit('.', 1)[-1]
|
|
64
|
-
record.short_name = f"{short:<{self.NAME_WIDTH}}"
|
|
65
|
-
|
|
66
|
-
return super().format(record)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
console_formatter = LogFormatter(
|
|
70
|
-
f' %(level_letter)s %(message)s'
|
|
71
|
-
)
|
|
72
|
-
file_formatter = logging.Formatter(
|
|
73
|
-
'%(asctime)s %(levelname)s %(name)s - %(message)s'
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
console = logging.StreamHandler(sys.stdout)
|
|
77
|
-
console.setFormatter(console_formatter )
|
|
78
|
-
|
|
79
|
-
file = logging.FileHandler('tilegrab.log')
|
|
80
|
-
file.setFormatter(file_formatter)
|
|
81
|
-
|
|
82
|
-
logging.basicConfig(
|
|
83
|
-
level=logging.INFO,
|
|
84
|
-
handlers=[console, file],
|
|
85
|
-
)
|
|
11
|
+
from tilegrab import __version__
|
|
86
12
|
|
|
87
|
-
logger = logging.getLogger(__name__)
|
|
88
13
|
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
89
15
|
|
|
90
16
|
def parse_args() -> argparse.Namespace:
|
|
91
17
|
p = argparse.ArgumentParser(
|
|
92
|
-
prog="tilegrab",
|
|
18
|
+
prog="tilegrab",
|
|
19
|
+
description="Download and mosaic map tiles"
|
|
93
20
|
)
|
|
94
21
|
|
|
95
22
|
# Create a named group for the vector polygon source
|
|
@@ -127,13 +54,23 @@ def parse_args() -> argparse.Namespace:
|
|
|
127
54
|
"--key", type=str, default=None, help="API key where required by source"
|
|
128
55
|
)
|
|
129
56
|
|
|
57
|
+
# Create a named group for merged output format
|
|
58
|
+
mosaic_out_group = p.add_argument_group(
|
|
59
|
+
title="Mosaic export formats", description="Formats for the output mosaic image"
|
|
60
|
+
)
|
|
61
|
+
mosaic_group = mosaic_out_group.add_mutually_exclusive_group(required=False)
|
|
62
|
+
mosaic_group.add_argument("--jpg", action="store_true", help="JPG image; no geo-reference")
|
|
63
|
+
mosaic_group.add_argument("--png", action="store_true", help="PNG image; no geo-reference")
|
|
64
|
+
mosaic_group.add_argument("--tiff", action="store_true", help="GeoTiff image; with geo-reference")
|
|
65
|
+
mosaic_group.set_defaults(tiff=True)
|
|
66
|
+
|
|
130
67
|
# other options
|
|
131
|
-
p.add_argument("--zoom", type=int, required=True, help="Zoom level (integer)")
|
|
68
|
+
p.add_argument("--zoom", type=int, required=True, help="Zoom level (integer between 1 and 22)")
|
|
132
69
|
p.add_argument(
|
|
133
|
-
"--out",
|
|
70
|
+
"--tiles-out",
|
|
134
71
|
type=Path,
|
|
135
72
|
default=Path.cwd() / "saved_tiles",
|
|
136
|
-
help="Output directory (default: ./saved_tiles)",
|
|
73
|
+
help="Output directory for downloaded tiles (default: ./saved_tiles)",
|
|
137
74
|
)
|
|
138
75
|
p.add_argument(
|
|
139
76
|
"--download-only",
|
|
@@ -146,63 +83,69 @@ def parse_args() -> argparse.Namespace:
|
|
|
146
83
|
help="Only mosaic tiles; do not download",
|
|
147
84
|
)
|
|
148
85
|
p.add_argument(
|
|
149
|
-
"--
|
|
86
|
+
"--group-tiles", type=str, default=None, help="Mosaic tiles but according to given WxH into ./grouped_tiles"
|
|
150
87
|
)
|
|
151
88
|
p.add_argument(
|
|
152
|
-
"--
|
|
89
|
+
"--group-overlap", action="store_true", help="Overlap with the next consecutive tile when grouping"
|
|
153
90
|
)
|
|
154
91
|
p.add_argument(
|
|
155
|
-
"--
|
|
92
|
+
"--tile-limit", type=int, default=250, help="Override maximum tile limit that can download (use with caution)"
|
|
156
93
|
)
|
|
157
94
|
p.add_argument(
|
|
158
|
-
"--
|
|
95
|
+
"--workers", type=int, default=None, help="Max number of threads to use when parallel downloading"
|
|
159
96
|
)
|
|
160
|
-
|
|
97
|
+
p.add_argument(
|
|
98
|
+
"--parallel",
|
|
99
|
+
action=argparse.BooleanOptionalAction,
|
|
100
|
+
default=False,
|
|
101
|
+
help="Download tiles sequentially, no parallel downloading"
|
|
102
|
+
)
|
|
103
|
+
p.add_argument(
|
|
104
|
+
"--progress",
|
|
105
|
+
action=argparse.BooleanOptionalAction,
|
|
106
|
+
default=False,
|
|
107
|
+
help="Hide tile download progress bar"
|
|
108
|
+
)
|
|
109
|
+
p.add_argument("--quiet", action="store_true", help="Hide all prints")
|
|
110
|
+
p.add_argument("--debug", action="store_true", help="Enable debug logging")
|
|
161
111
|
return p.parse_args()
|
|
162
112
|
|
|
163
113
|
|
|
164
114
|
def main():
|
|
115
|
+
LOG_LEVEL = logging.INFO
|
|
116
|
+
ENABLE_CLI_LOG = True
|
|
117
|
+
ENABLE_FILE_LOG = True
|
|
118
|
+
|
|
165
119
|
args = parse_args()
|
|
166
|
-
|
|
167
|
-
# Adjust logging level
|
|
168
120
|
if args.debug:
|
|
169
|
-
logging.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
console.close()
|
|
173
|
-
|
|
121
|
+
LOG_LEVEL = logging.DEBUG
|
|
122
|
+
if args.quiet:
|
|
123
|
+
ENABLE_CLI_LOG = False
|
|
174
124
|
|
|
175
|
-
|
|
176
|
-
BANNER_NORMAL, BANNER_BRIGHT = random.choice([
|
|
177
|
-
(RED, BRED),
|
|
178
|
-
(GREEN, BGREEN),
|
|
179
|
-
(YELLOW, BYELLOW),
|
|
180
|
-
(BLUE, BBLUE),
|
|
181
|
-
(MAGENTA, BMAGENTA),
|
|
182
|
-
(CYAN, BCYAN),
|
|
183
|
-
(GRAY, BGRAY),
|
|
184
|
-
])
|
|
125
|
+
setup_logging(ENABLE_CLI_LOG, ENABLE_FILE_LOG, LOG_LEVEL)
|
|
185
126
|
|
|
186
127
|
if not args.quiet:
|
|
187
128
|
print()
|
|
188
|
-
print(f"
|
|
189
|
-
print(f"
|
|
190
|
-
print(f"
|
|
191
|
-
|
|
129
|
+
print(f"\033[37m " + ("-" * 60) + "\033[0m")
|
|
130
|
+
print(f"\033[97m TileGrab v{__version__}\033[0m".rjust(50))
|
|
131
|
+
print(f"\033[37m " + ("-" * 60) + "\033[0m")
|
|
132
|
+
|
|
192
133
|
try:
|
|
193
134
|
dataset = GeoDataset(args.source)
|
|
194
135
|
logger.info(f"Dataset loaded successfully from {args.source}")
|
|
195
136
|
|
|
196
137
|
_tmp = "bbox" if args.bbox else "shape" if args.shape else "DnE"
|
|
197
|
-
logger.info(
|
|
138
|
+
logger.info(
|
|
139
|
+
f"""Downloading tiles using {_tmp}
|
|
198
140
|
- minX: {dataset.bbox.minx:.4f} - minY: {dataset.bbox.miny:.4f}
|
|
199
141
|
- maxX: {dataset.bbox.maxx:.4f} - maxY: {dataset.bbox.maxy:.4f}
|
|
200
|
-
- zoom: {args.zoom}"""
|
|
142
|
+
- zoom: {args.zoom}"""
|
|
143
|
+
)
|
|
201
144
|
|
|
202
145
|
if args.shape:
|
|
203
|
-
tiles = TilesByShape(dataset, zoom=args.zoom)
|
|
146
|
+
tiles = TilesByShape(geo_dataset=dataset, zoom=args.zoom, SAFE_LIMIT=args.tile_limit)
|
|
204
147
|
elif args.bbox:
|
|
205
|
-
tiles = TilesByBBox(dataset, zoom=args.zoom)
|
|
148
|
+
tiles = TilesByBBox(geo_dataset=dataset, zoom=args.zoom, SAFE_LIMIT=args.tile_limit)
|
|
206
149
|
else:
|
|
207
150
|
logger.error("No extent selector selected")
|
|
208
151
|
raise SystemExit("No extent selector selected")
|
|
@@ -210,34 +153,65 @@ def main():
|
|
|
210
153
|
# Choose source provider
|
|
211
154
|
if args.osm:
|
|
212
155
|
from tilegrab.sources import OSM
|
|
156
|
+
|
|
213
157
|
logger.info("Using OpenStreetMap (OSM) as tile source")
|
|
214
158
|
source = OSM(api_key=args.key) if args.key else OSM()
|
|
215
159
|
elif args.google_sat:
|
|
216
160
|
from tilegrab.sources import GoogleSat
|
|
161
|
+
|
|
217
162
|
logger.info("Using Google Satellite as tile source")
|
|
218
163
|
source = GoogleSat(api_key=args.key) if args.key else GoogleSat()
|
|
219
164
|
elif args.esri_sat:
|
|
220
165
|
from tilegrab.sources import ESRIWorldImagery
|
|
166
|
+
|
|
221
167
|
logger.info("Using ESRI World Imagery as tile source")
|
|
222
|
-
source =
|
|
168
|
+
source = (
|
|
169
|
+
ESRIWorldImagery(api_key=args.key) if args.key else ESRIWorldImagery()
|
|
170
|
+
)
|
|
223
171
|
else:
|
|
224
172
|
logger.error("No tile source selected")
|
|
225
173
|
raise SystemExit("No tile source selected")
|
|
174
|
+
|
|
175
|
+
downloader = Downloader(
|
|
176
|
+
tile_collection=tiles,
|
|
177
|
+
tile_source=source,
|
|
178
|
+
temp_tile_dir=args.tiles_out)
|
|
226
179
|
|
|
227
|
-
downloader = Downloader(tiles, source, args.out)
|
|
228
180
|
result: TileImageCollection
|
|
229
|
-
|
|
230
181
|
if args.mosaic_only:
|
|
231
|
-
result = TileImageCollection(args.
|
|
232
|
-
result.load(tiles)
|
|
182
|
+
result = TileImageCollection(path=args.tiles_out)
|
|
183
|
+
result.load(tile_collection=tiles)
|
|
233
184
|
else:
|
|
234
|
-
result = downloader.run(
|
|
185
|
+
result = downloader.run(
|
|
186
|
+
workers=args.workers,
|
|
187
|
+
show_progress=args.no_progress,
|
|
188
|
+
parallel_download=args.no_parallel)
|
|
235
189
|
logger.info(f"Download result: {result}")
|
|
190
|
+
|
|
191
|
+
ex_types: List[int] = []
|
|
236
192
|
|
|
237
|
-
if not args.download_only:
|
|
193
|
+
if not args.download_only:
|
|
194
|
+
if args.tiff:
|
|
195
|
+
ex_types.append(ExportType.TIFF)
|
|
196
|
+
if args.png:
|
|
197
|
+
ex_types.append(ExportType.PNG)
|
|
198
|
+
if args.jpg:
|
|
199
|
+
ex_types.append(ExportType.JPG)
|
|
200
|
+
|
|
201
|
+
if args.group_tiles:
|
|
202
|
+
w,h = args.group_tiles.lower().split("x")
|
|
203
|
+
result.group(
|
|
204
|
+
width=int(w),
|
|
205
|
+
height=int(h),
|
|
206
|
+
export_types=ex_types,
|
|
207
|
+
overlap=args.group_overlap)
|
|
208
|
+
else:
|
|
209
|
+
result.mosaic(export_types=ex_types)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
238
213
|
logger.info("Done")
|
|
239
214
|
|
|
240
|
-
|
|
241
215
|
except Exception as e:
|
|
242
216
|
logger.exception("Fatal error during execution")
|
|
243
217
|
raise SystemExit(1)
|
tilegrab/dataset.py
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
1
2
|
import logging
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
from box import Box
|
|
4
5
|
from typing import Union
|
|
5
|
-
|
|
6
|
+
from functools import cache
|
|
6
7
|
|
|
7
8
|
logger = logging.getLogger(__name__)
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
TILE_EPSG = 4326 #Web Mercator - 3857 | 4326 - WGS84
|
|
10
11
|
|
|
12
|
+
class GeoDataset:
|
|
11
13
|
@property
|
|
14
|
+
@cache
|
|
12
15
|
def bbox(self):
|
|
13
16
|
minx, miny, maxx, maxy = self.source.total_bounds
|
|
14
17
|
bbox_dict = Box({"minx": minx, "miny": miny, "maxx": maxx, "maxy": maxy})
|
|
@@ -21,14 +24,14 @@ class GeoDataset:
|
|
|
21
24
|
|
|
22
25
|
@property
|
|
23
26
|
def x_extent(self):
|
|
24
|
-
minx, maxx = self.source.total_bounds
|
|
27
|
+
minx, _, maxx, _ = self.source.total_bounds
|
|
25
28
|
extent = (maxx - minx) + 1
|
|
26
29
|
logger.debug(f"X extent calculated: {extent}")
|
|
27
30
|
return extent
|
|
28
31
|
|
|
29
32
|
@property
|
|
30
33
|
def y_extent(self):
|
|
31
|
-
miny, maxy = self.source.total_bounds
|
|
34
|
+
_, miny, _, maxy = self.source.total_bounds
|
|
32
35
|
extent = (maxy - miny) + 1
|
|
33
36
|
logger.debug(f"Y extent calculated: {extent}")
|
|
34
37
|
return extent
|
|
@@ -63,14 +66,14 @@ class GeoDataset:
|
|
|
63
66
|
logger.critical("Dataset has no CRS defined")
|
|
64
67
|
raise RuntimeError("Missing CRS")
|
|
65
68
|
|
|
66
|
-
if epsg !=
|
|
67
|
-
logger.info(f"Reprojecting dataset from EPSG:{epsg} to EPSG:
|
|
68
|
-
gdf = gdf.to_crs(epsg=
|
|
69
|
+
if epsg != TILE_EPSG: #Web Mercator
|
|
70
|
+
logger.info(f"Reprojecting dataset from EPSG:{epsg} to EPSG:{TILE_EPSG}")
|
|
71
|
+
gdf = gdf.to_crs(epsg=TILE_EPSG)
|
|
69
72
|
else:
|
|
70
|
-
logger.debug("Dataset already in EPSG:
|
|
73
|
+
logger.debug(f"Dataset already in EPSG:{TILE_EPSG}")
|
|
71
74
|
|
|
72
75
|
self.original_epsg = epsg
|
|
73
|
-
self.current_epsg =
|
|
76
|
+
self.current_epsg = TILE_EPSG
|
|
74
77
|
self.source = gdf
|
|
75
78
|
self.source_path = source_path
|
|
76
79
|
logger.info(f"GeoDataset initialized successfully: {len(gdf)} features")
|
tilegrab/downloader.py
CHANGED
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
|
-
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
4
3
|
from dataclasses import dataclass
|
|
5
4
|
from typing import List, Optional, Union
|
|
6
5
|
import requests
|
|
7
|
-
from requests.adapters import HTTPAdapter, Retry
|
|
8
|
-
from tqdm import tqdm
|
|
9
|
-
import tempfile
|
|
10
6
|
from pathlib import Path
|
|
11
7
|
|
|
12
8
|
from tilegrab.sources import TileSource
|
|
13
9
|
from tilegrab.tiles import TileCollection, Tile
|
|
14
10
|
from tilegrab.images import TileImageCollection, TileImage
|
|
15
|
-
|
|
11
|
+
|
|
16
12
|
|
|
17
13
|
logger = logging.getLogger(__name__)
|
|
18
14
|
|
|
19
15
|
@dataclass
|
|
20
16
|
class Downloader:
|
|
21
|
-
|
|
17
|
+
tile_collection: TileCollection
|
|
22
18
|
tile_source: TileSource
|
|
23
19
|
temp_tile_dir: Optional[Union[str, Path]] = None
|
|
24
20
|
session: Optional[requests.Session] = None
|
|
@@ -29,6 +25,7 @@ class Downloader:
|
|
|
29
25
|
|
|
30
26
|
def __post_init__(self):
|
|
31
27
|
if not self.temp_tile_dir:
|
|
28
|
+
import tempfile
|
|
32
29
|
tmpdir = tempfile.mkdtemp()
|
|
33
30
|
self.temp_tile_dir = Path(tmpdir)
|
|
34
31
|
logger.debug(f"Created temporary directory: {tmpdir}")
|
|
@@ -41,6 +38,8 @@ class Downloader:
|
|
|
41
38
|
logger.info(f"Downloader initialized: source={self.tile_source.name}, timeout={self.REQUEST_TIMEOUT}s, max_retries={self.MAX_RETRIES}")
|
|
42
39
|
|
|
43
40
|
def _init_session(self) -> requests.Session:
|
|
41
|
+
from requests.adapters import HTTPAdapter, Retry
|
|
42
|
+
|
|
44
43
|
logger.debug("Initializing HTTP session with retry strategy")
|
|
45
44
|
session = requests.Session()
|
|
46
45
|
retries = Retry(
|
|
@@ -90,36 +89,41 @@ class Downloader:
|
|
|
90
89
|
|
|
91
90
|
def run(
|
|
92
91
|
self,
|
|
93
|
-
workers: int =
|
|
92
|
+
workers: Union[int, None] = None,
|
|
94
93
|
show_progress: bool = True,
|
|
94
|
+
parallel_download: bool = True
|
|
95
95
|
) -> TileImageCollection:
|
|
96
|
-
logger.info(f"Starting download run: {len(self.
|
|
96
|
+
logger.info(f"Starting download run: {len(self.tile_collection)} tiles, workers={workers}, show_progress={show_progress}")
|
|
97
97
|
|
|
98
98
|
results = []
|
|
99
99
|
|
|
100
100
|
if show_progress:
|
|
101
|
-
|
|
101
|
+
from tqdm import tqdm
|
|
102
|
+
pbar = tqdm(total=len(self.tile_collection), desc=f" Downloading", unit="tile")
|
|
102
103
|
else:
|
|
103
104
|
pbar = None
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
106
|
+
|
|
107
|
+
if parallel_download:
|
|
108
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
109
|
+
with ThreadPoolExecutor(max_workers=workers) as exe:
|
|
110
|
+
future_to_tile = {
|
|
111
|
+
exe.submit(self.download_tile, tile): tile for tile in self.tile_collection.to_list
|
|
112
|
+
}
|
|
113
|
+
for fut in as_completed(future_to_tile):
|
|
114
|
+
try:
|
|
115
|
+
results.append(fut.result())
|
|
116
|
+
except Exception:
|
|
117
|
+
results.append(False)
|
|
118
|
+
|
|
119
|
+
if pbar:
|
|
120
|
+
pbar.update(1)
|
|
121
|
+
else:
|
|
122
|
+
for tile in self.tile_collection.to_list:
|
|
123
|
+
res = self.download_tile(tile)
|
|
124
|
+
results.append(res)
|
|
125
|
+
if pbar:
|
|
126
|
+
pbar.update(1)
|
|
123
127
|
|
|
124
128
|
if pbar:
|
|
125
129
|
pbar.close()
|
|
@@ -129,7 +133,7 @@ class Downloader:
|
|
|
129
133
|
|
|
130
134
|
def _evaluate_result(self, result: List):
|
|
131
135
|
success = sum(1 for v in result if v)
|
|
132
|
-
total = len(self.
|
|
136
|
+
total = len(self.tile_collection)
|
|
133
137
|
logger.info(f"Download completed: {success}/{total} successful ({100*success/total:.1f}%)")
|
|
134
138
|
if success < total:
|
|
135
139
|
logger.warning(f"Failed to download {total - success} tiles")
|