topo_map_processor 0.1.9__tar.gz → 0.1.10__tar.gz

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.
Files changed (23) hide show
  1. {topo_map_processor-0.1.9 → topo_map_processor-0.1.10}/PKG-INFO +10 -9
  2. {topo_map_processor-0.1.9 → topo_map_processor-0.1.10}/README.md +8 -7
  3. {topo_map_processor-0.1.9 → topo_map_processor-0.1.10}/pyproject.toml +3 -1
  4. {topo_map_processor-0.1.9 → topo_map_processor-0.1.10}/topo_map_processor/_version.py +2 -2
  5. topo_map_processor-0.1.10/topo_map_processor/tools/download_mosaic.py +375 -0
  6. {topo_map_processor-0.1.9 → topo_map_processor-0.1.10}/topo_map_processor/tools/execute_bash.py +2 -0
  7. topo_map_processor-0.1.10/topo_map_processor/tools/partition.py +604 -0
  8. topo_map_processor-0.1.10/topo_map_processor/tools/partition_basic.py +514 -0
  9. {topo_map_processor-0.1.9 → topo_map_processor-0.1.10}/topo_map_processor/tools/retile.py +18 -11
  10. topo_map_processor-0.1.10/topo_map_processor/tools/retile_e2e.py +181 -0
  11. {topo_map_processor-0.1.9 → topo_map_processor-0.1.10}/topo_map_processor/tools/tile.py +5 -8
  12. {topo_map_processor-0.1.9 → topo_map_processor-0.1.10}/topo_map_processor/tools/tile_sources.py +79 -26
  13. topo_map_processor-0.1.9/topo_map_processor/tools/download_mosaic.py +0 -256
  14. topo_map_processor-0.1.9/topo_map_processor/tools/partition.py +0 -438
  15. {topo_map_processor-0.1.9 → topo_map_processor-0.1.10}/.gitignore +0 -0
  16. {topo_map_processor-0.1.9 → topo_map_processor-0.1.10}/LICENSE +0 -0
  17. {topo_map_processor-0.1.9 → topo_map_processor-0.1.10}/topo_map_processor/__init__.py +0 -0
  18. {topo_map_processor-0.1.9 → topo_map_processor-0.1.10}/topo_map_processor/bash_scripts/__init__.py +0 -0
  19. {topo_map_processor-0.1.9 → topo_map_processor-0.1.10}/topo_map_processor/bash_scripts/generate_lists.sh +0 -0
  20. {topo_map_processor-0.1.9 → topo_map_processor-0.1.10}/topo_map_processor/bash_scripts/upload_to_release.sh +0 -0
  21. {topo_map_processor-0.1.9 → topo_map_processor-0.1.10}/topo_map_processor/processor.py +0 -0
  22. {topo_map_processor-0.1.9 → topo_map_processor-0.1.10}/topo_map_processor/tools/__init__.py +0 -0
  23. {topo_map_processor-0.1.9 → topo_map_processor-0.1.10}/topo_map_processor/tools/collect_bounds.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: topo_map_processor
3
- Version: 0.1.9
3
+ Version: 0.1.10
4
4
  Summary: Library for processing topographic maps
5
5
  Project-URL: homepage, https://github.com/ramSeraph/topo_map_processor
6
6
  Project-URL: repository, https://github.com/ramSeraph/topo_map_processor.git
@@ -32,7 +32,7 @@ License: This is free and unencumbered software released into the public domain.
32
32
  License-File: LICENSE
33
33
  Requires-Python: >=3.12
34
34
  Requires-Dist: mercantile>=1.2.1
35
- Requires-Dist: pmtiles>=3.4.1
35
+ Requires-Dist: pmtiles==3.4.1
36
36
  Requires-Dist: requests>=2.32.4
37
37
  Requires-Dist: shapely>=2.1.1
38
38
  Provides-Extra: easyocr
@@ -235,14 +235,15 @@ collect-bounds --bounds-dir <directory> --output-file <file> [--preexisting-file
235
235
 
236
236
  #### `download-mosaic`
237
237
 
238
- Downloads a PMTiles mosaic and converts it to a single MBTiles file. A mosaic is a JSON file that lists multiple PMTiles files.
238
+ Downloads a PMTiles mosaic and converts it to a single MBTiles or PMTiles file. A mosaic is a JSON file that lists multiple PMTiles files.
239
239
 
240
240
  ```bash
241
- download-mosaic --mosaic_url <url> [--output_file <file>] [--request-timeout-secs <seconds>] [--num-http-retries <retries>]
241
+ download-mosaic --mosaic-url <url> [--output-file <file>] [--archive-type <type>] [--request-timeout-secs <seconds>] [--num-http-retries <retries>]
242
242
  ```
243
243
 
244
- - `--mosaic_url`, `-u`: URL of the mosaic JSON file. (Required)
245
- - `--output_file`, `-o`: Output MBTiles file name. If not specified, it is derived from the mosaic URL. (Optional)
244
+ - `--mosaic-url`, `-u`: URL of the mosaic JSON file. (Required)
245
+ - `--output-file`, `-o`: Output MBTiles/PMTiles file name. The format is inferred from the extension (`.mbtiles` or `.pmtiles`). If not specified, it is derived from the mosaic URL. (Optional)
246
+ - `--archive-type`: Type of archive to create (`mbtiles` or `pmtiles`). Required if `--output-file` is not provided or its extension is not recognized.
246
247
  - `--request-timeout-secs`, `-t`: Timeout for HTTP requests in seconds (default: 60).
247
248
  - `--num-http-retries`, `-r`: Number of retries for HTTP requests (default: 3).
248
249
 
@@ -285,7 +286,7 @@ retile --retile-list-file <file> --bounds-file <file> [--max-zoom <zoom>] [--tif
285
286
  - `--from-source`: Location of the source from which to pull existing tiles. Can be a glob pattern to match a group of pmtiles. Required if not in pull-list generation mode.
286
287
  - `--sheets-to-pull-list-outfile`: If provided, the script will only calculate the full list of sheets that need to be processed (including adjacent ones) and write it to this file, then exit.
287
288
  - `--num-parallel`: Number of parallel processes to use for tiling (default: number of CPU cores).
288
- - `--tile-quality`: Quality of compression for webp and jpg (default: 75).
289
+ - `--tile-quality`: Quality of compression for webp and jpeg (default: 75).
289
290
 
290
291
  #### `tile`
291
292
 
@@ -303,8 +304,8 @@ tile --tiles-dir <dir> --tiffs-dir <dir> --max-zoom <zoom> --name <name> --descr
303
304
  - `--attribution`: Attribution text for the mosaic. Required if `--attribution-file` is not used.
304
305
  - `--attribution-file`: File containing attribution text for the mosaic. Required if `--attribution` is not used.
305
306
  - `--min-zoom`: Minimum zoom level for tiling (default: 0).
306
- - `--tile-extension`: Tile file extension (default: webp). Choices: `webp`, `jpg`, `jpeg`, `png`.
307
- - `--tile-quality`: Compression quality of the tiles for `webp` and `jpg` (default: 75).
307
+ - `--tile-extension`: Tile file extension (default: webp). Choices: `webp`, `jpeg`, `png`.
308
+ - `--tile-quality`: Compression quality of the tiles for `webp` and `jpeg` (default: 75).
308
309
  - `--num-parallel`: Number of parallel processes to use for tiling (default: number of CPU cores).
309
310
 
310
311
  ### Running Tools Directly with `uvx`
@@ -186,14 +186,15 @@ collect-bounds --bounds-dir <directory> --output-file <file> [--preexisting-file
186
186
 
187
187
  #### `download-mosaic`
188
188
 
189
- Downloads a PMTiles mosaic and converts it to a single MBTiles file. A mosaic is a JSON file that lists multiple PMTiles files.
189
+ Downloads a PMTiles mosaic and converts it to a single MBTiles or PMTiles file. A mosaic is a JSON file that lists multiple PMTiles files.
190
190
 
191
191
  ```bash
192
- download-mosaic --mosaic_url <url> [--output_file <file>] [--request-timeout-secs <seconds>] [--num-http-retries <retries>]
192
+ download-mosaic --mosaic-url <url> [--output-file <file>] [--archive-type <type>] [--request-timeout-secs <seconds>] [--num-http-retries <retries>]
193
193
  ```
194
194
 
195
- - `--mosaic_url`, `-u`: URL of the mosaic JSON file. (Required)
196
- - `--output_file`, `-o`: Output MBTiles file name. If not specified, it is derived from the mosaic URL. (Optional)
195
+ - `--mosaic-url`, `-u`: URL of the mosaic JSON file. (Required)
196
+ - `--output-file`, `-o`: Output MBTiles/PMTiles file name. The format is inferred from the extension (`.mbtiles` or `.pmtiles`). If not specified, it is derived from the mosaic URL. (Optional)
197
+ - `--archive-type`: Type of archive to create (`mbtiles` or `pmtiles`). Required if `--output-file` is not provided or its extension is not recognized.
197
198
  - `--request-timeout-secs`, `-t`: Timeout for HTTP requests in seconds (default: 60).
198
199
  - `--num-http-retries`, `-r`: Number of retries for HTTP requests (default: 3).
199
200
 
@@ -236,7 +237,7 @@ retile --retile-list-file <file> --bounds-file <file> [--max-zoom <zoom>] [--tif
236
237
  - `--from-source`: Location of the source from which to pull existing tiles. Can be a glob pattern to match a group of pmtiles. Required if not in pull-list generation mode.
237
238
  - `--sheets-to-pull-list-outfile`: If provided, the script will only calculate the full list of sheets that need to be processed (including adjacent ones) and write it to this file, then exit.
238
239
  - `--num-parallel`: Number of parallel processes to use for tiling (default: number of CPU cores).
239
- - `--tile-quality`: Quality of compression for webp and jpg (default: 75).
240
+ - `--tile-quality`: Quality of compression for webp and jpeg (default: 75).
240
241
 
241
242
  #### `tile`
242
243
 
@@ -254,8 +255,8 @@ tile --tiles-dir <dir> --tiffs-dir <dir> --max-zoom <zoom> --name <name> --descr
254
255
  - `--attribution`: Attribution text for the mosaic. Required if `--attribution-file` is not used.
255
256
  - `--attribution-file`: File containing attribution text for the mosaic. Required if `--attribution` is not used.
256
257
  - `--min-zoom`: Minimum zoom level for tiling (default: 0).
257
- - `--tile-extension`: Tile file extension (default: webp). Choices: `webp`, `jpg`, `jpeg`, `png`.
258
- - `--tile-quality`: Compression quality of the tiles for `webp` and `jpg` (default: 75).
258
+ - `--tile-extension`: Tile file extension (default: webp). Choices: `webp`, `jpeg`, `png`.
259
+ - `--tile-quality`: Compression quality of the tiles for `webp` and `jpeg` (default: 75).
259
260
  - `--num-parallel`: Number of parallel processes to use for tiling (default: number of CPU cores).
260
261
 
261
262
  ### Running Tools Directly with `uvx`
@@ -9,7 +9,7 @@ authors = [
9
9
  ]
10
10
  dependencies = [
11
11
  "mercantile>=1.2.1",
12
- "pmtiles>=3.4.1",
12
+ "pmtiles==3.4.1",
13
13
  "requests>=2.32.4",
14
14
  "shapely>=2.1.1",
15
15
  ]
@@ -40,10 +40,12 @@ repository = "https://github.com/ramSeraph/topo_map_processor.git"
40
40
  collect-bounds = "topo_map_processor.tools.collect_bounds:cli"
41
41
  download-mosaic = "topo_map_processor.tools.download_mosaic:cli"
42
42
  partition = "topo_map_processor.tools.partition:cli"
43
+ partition-basic = "topo_map_processor.tools.partition_basic:cli"
43
44
  retile = "topo_map_processor.tools.retile:cli"
44
45
  tile = "topo_map_processor.tools.tile:cli"
45
46
  generate-lists = "topo_map_processor.tools.execute_bash:generate_lists"
46
47
  upload-to-release = "topo_map_processor.tools.execute_bash:upload_to_release"
48
+ retile-e2e = "topo_map_processor.tools.retile_e2e:cli"
47
49
 
48
50
 
49
51
  [build-system]
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.1.9'
21
- __version_tuple__ = version_tuple = (0, 1, 9)
20
+ __version__ = version = '0.1.10'
21
+ __version_tuple__ = version_tuple = (0, 1, 10)
@@ -0,0 +1,375 @@
1
+ #!/usr/bin/env -S uv run
2
+ # /// script
3
+ # requires-python = ">=3.12"
4
+ # dependencies = [
5
+ # "mercantile",
6
+ # "pmtiles",
7
+ # "requests",
8
+ # ]
9
+ # ///
10
+
11
+ import json
12
+ import sqlite3
13
+
14
+ from pathlib import Path, PurePosixPath
15
+ from urllib.parse import urlparse, urljoin, unquote
16
+
17
+ import requests
18
+ from requests.adapters import HTTPAdapter
19
+ from requests.packages.urllib3.util.retry import Retry
20
+
21
+ from pmtiles.reader import Reader, MmapSource, all_tiles
22
+ from pmtiles.writer import Writer
23
+ from pmtiles.tile import zxy_to_tileid, Compression, TileType
24
+
25
+ # heavily copied from https://github.com/protomaps/PMTiles/blob/main/python/pmtiles/convert.py
26
+ # and https://github.com/mapbox/mbutil/blob/master/mbutil/util.py
27
+
28
+ class ArchiveWriter:
29
+ def __init__(self, output_file):
30
+ self.output_file = output_file
31
+
32
+ def init(self):
33
+ raise NotImplementedError("This method should be overridden by subclasses")
34
+
35
+ def commit(self):
36
+ raise NotImplementedError("This method should be overridden by subclasses")
37
+
38
+ def add_to_archive(self, zxy, tile_data):
39
+ raise NotImplementedError("This method should be overridden by subclasses")
40
+
41
+ def add(self, pmtiles_fname):
42
+ print(f'adding {pmtiles_fname} to archive')
43
+ with open(pmtiles_fname, "r+b") as f:
44
+ source = MmapSource(f)
45
+ reader = Reader(source)
46
+
47
+ for zxy, tile_data in all_tiles(reader.get_bytes):
48
+ self.add_to_archive(zxy, tile_data)
49
+
50
+ self.commit()
51
+
52
+ def finalize(self, metadata=None, header=None):
53
+ raise NotImplementedError("This method should be overridden by subclasses")
54
+
55
+
56
+ class PmtilesArchiveWriter(ArchiveWriter):
57
+ def __init__(self, output_file):
58
+ super().__init__(output_file)
59
+ self.pmtiles_writer = None
60
+
61
+ def init(self):
62
+ self.pmtiles_writer = Writer(open(self.output_file, 'wb'))
63
+
64
+ def add_to_archive(self, zxy, tile_data):
65
+ tile_id = zxy_to_tileid(zxy[0], zxy[1], zxy[2])
66
+ self.pmtiles_writer.write_tile(tile_id, tile_data)
67
+
68
+ def commit(self):
69
+ pass
70
+
71
+ def enhance_header(self, h):
72
+ h['tile_compression'] = Compression(h['tile_compression'])
73
+ h['tile_type'] = TileType(h['tile_type'])
74
+
75
+
76
+ def finalize(self, metadata, header):
77
+ self.enhance_header(header)
78
+ self.pmtiles_writer.finalize(header, metadata)
79
+
80
+
81
+ class MbtilesArchiveWriter(ArchiveWriter):
82
+ def __init__(self, output_file):
83
+ super().__init__(output_file)
84
+ self.conn = None
85
+ self.cursor = None
86
+
87
+ def init(self):
88
+ self.conn = sqlite3.connect(self.output_file)
89
+ self.cursor = self.conn.cursor()
90
+
91
+ self.cursor.execute("""PRAGMA synchronous=0""")
92
+ self.cursor.execute("""PRAGMA locking_mode=EXCLUSIVE""")
93
+ self.cursor.execute("""PRAGMA journal_mode=DELETE""")
94
+
95
+ self.cursor.execute("CREATE TABLE metadata (name text, value text);")
96
+ self.cursor.execute(
97
+ "CREATE TABLE tiles (zoom_level integer, tile_column integer, tile_row integer, tile_data blob);"
98
+ )
99
+
100
+ def add_to_archive(self, zxy, tile_data):
101
+ flipped_y = (1 << zxy[0]) - 1 - zxy[2]
102
+ self.cursor.execute(
103
+ "INSERT INTO tiles VALUES(?,?,?,?)",
104
+ (zxy[0], zxy[1], flipped_y, tile_data),
105
+ )
106
+
107
+ def commit(self):
108
+ self.conn.commit()
109
+
110
+ def add_metadata(self, metadata):
111
+ json_metadata = {}
112
+ for k, v in metadata.items():
113
+ if k == "vector_layers":
114
+ json_metadata["vector_layers"] = v
115
+ continue
116
+ elif k == "tilestats":
117
+ json_metadata["tilestats"] = v
118
+ continue
119
+ elif not isinstance(v, str):
120
+ v = json.dumps(v, ensure_ascii=False)
121
+
122
+ self.cursor.execute("INSERT INTO metadata VALUES(?,?)", (k, v))
123
+
124
+ if len(json_metadata) > 0:
125
+ self.cursor.execute(
126
+ "INSERT INTO metadata VALUES(?,?)",
127
+ ("json", json.dumps(json_metadata, ensure_ascii=False)),
128
+ )
129
+
130
+
131
+ def finalize_mbtiles(self):
132
+ self.cursor.execute(
133
+ "CREATE UNIQUE INDEX tile_index on tiles (zoom_level, tile_column, tile_row);"
134
+ )
135
+ self.conn.commit()
136
+ self.cursor.execute("""ANALYZE;""")
137
+
138
+ self.conn.close()
139
+
140
+ def enhance_metadata(self, cm, ch):
141
+
142
+ cm['maxzoom'] = ch['max_zoom']
143
+ cm['minzoom'] = ch['min_zoom']
144
+
145
+ max_lat = ch['max_lat_e7'] / 10000000
146
+ min_lat = ch['min_lat_e7'] / 10000000
147
+ max_lon = ch['max_lon_e7'] / 10000000
148
+ min_lon = ch['min_lon_e7'] / 10000000
149
+ cm['bounds'] = f"{min_lon},{min_lat},{max_lon},{max_lat}"
150
+
151
+ center_lat = ch['center_lat_e7'] / 10000000
152
+ center_lon = ch['center_lon_e7'] / 10000000
153
+ center_zoom = ch['center_zoom']
154
+ cm['center'] = f"{center_lon},{center_lat},{center_zoom}"
155
+
156
+ def finalize(self, metadata, header):
157
+ self.enhance_metadata(metadata, header)
158
+ self.add_metadata(metadata)
159
+ self.finalize_mbtiles()
160
+
161
+ def get_filename_from_url(url):
162
+ url_parsed = urlparse(url)
163
+ return unquote(PurePosixPath(url_parsed.path).name)
164
+
165
+ class Merger:
166
+ def __init__(self, mosaic_url, archive_type, output_file, request_timeout_secs, num_http_retries):
167
+ self.session = self.get_session(num_http_retries)
168
+ self.request_timeout_secs = request_timeout_secs
169
+
170
+ self.output_file = output_file
171
+ self.output_dir = output_file.parent
172
+ self.tracker_file = self.output_dir / 'tracker.txt'
173
+ self.mosaic_file = self.output_dir / get_filename_from_url(mosaic_url)
174
+
175
+ self.archive_type = archive_type
176
+ self.archive_writer = self.get_archive_writer()
177
+
178
+ self.mosaic_url = mosaic_url
179
+ self.mosaic_data = None
180
+ self.mosaic_version = 0
181
+ self.done_stages = set()
182
+
183
+ def get_archive_writer(self):
184
+ if self.archive_type == 'mbtiles':
185
+ return MbtilesArchiveWriter(self.output_file)
186
+
187
+ if self.archive_type == 'pmtiles':
188
+ return PmtilesArchiveWriter(self.output_file)
189
+
190
+ raise ValueError(f'Unsupported archive type: {self.archive_type}')
191
+
192
+ def get_session(self, num_http_retries):
193
+ session = requests.session()
194
+ retries = num_http_retries
195
+ retry = Retry(
196
+ total=retries,
197
+ read=retries,
198
+ connect=retries,
199
+ )
200
+ session.mount('http://', HTTPAdapter(max_retries=retry))
201
+ session.mount('https://', HTTPAdapter(max_retries=retry))
202
+ return session
203
+
204
+ def get_mosaic(self, mosaic_url):
205
+ self.output_dir.mkdir(parents=True, exist_ok=True)
206
+ if not self.mosaic_file.exists():
207
+ self.download_file(mosaic_url, self.mosaic_file)
208
+
209
+ self.mosaic_data = json.loads(self.mosaic_file.read_text())
210
+
211
+
212
+ def download_file(self, url, file):
213
+ print(f'downloading {url} to {file}')
214
+ resp = self.session.get(url, stream=True, timeout=self.request_timeout_secs)
215
+ with open(file, 'wb') as f:
216
+ for data in resp.iter_content(chunk_size=4096):
217
+ f.write(data)
218
+
219
+ def init_tracker(self):
220
+ if not self.tracker_file.exists():
221
+ self.tracker_file.write_text("")
222
+
223
+ def populate_done_list(self):
224
+ if not self.tracker_file.exists():
225
+ return
226
+
227
+ self.done_stages = set(self.tracker_file.read_text().strip().split('\n'))
228
+
229
+ def mark_as_done(self, stage):
230
+ with open(self.tracker_file, 'a') as f:
231
+ f.write(stage)
232
+ f.write('\n')
233
+ self.done_stages.add(stage)
234
+
235
+ def get_pmtiles_url(self, k):
236
+ if self.mosaic_version == 0 and k.startswith('../'):
237
+ k = k[3:]
238
+ return urljoin(self.mosaic_url, k)
239
+
240
+ def collect_header(self, items):
241
+ ch = {}
242
+ for item in items:
243
+ h = item['header']
244
+ if 'max_zoom' not in ch or ch['max_zoom'] < h['max_zoom']:
245
+ ch['max_zoom'] = h['max_zoom']
246
+ if 'min_zoom' not in ch or ch['min_zoom'] > h['min_zoom']:
247
+ ch['min_zoom'] = h['min_zoom']
248
+
249
+ if 'max_lat_e7' not in ch or ch['max_lat_e7'] < h['max_lat_e7']:
250
+ ch['max_lat_e7'] = h['max_lat_e7']
251
+ if 'min_lat_e7' not in ch or ch['min_lat_e7'] > h['min_lat_e7']:
252
+ ch['min_lat_e7'] = h['min_lat_e7']
253
+ if 'max_lon_e7' not in ch or ch['max_lon_e7'] < h['max_lon_e7']:
254
+ ch['max_lon_e7'] = h['max_lon_e7']
255
+ if 'min_lon_e7' not in ch or ch['min_lon_e7'] > h['min_lon_e7']:
256
+ ch['min_lon_e7'] = h['min_lon_e7']
257
+
258
+ ch['tile_compression'] = h['tile_compression']
259
+ ch['tile_type'] = h['tile_type']
260
+
261
+ ch['center_lat_e7'] = (ch['max_lat_e7'] + ch['min_lat_e7']) // 2
262
+ ch['center_lon_e7'] = (ch['max_lon_e7'] + ch['min_lon_e7']) // 2
263
+ ch['center_zoom'] = (ch['max_zoom'] + ch['min_zoom']) // 2
264
+
265
+ return ch
266
+
267
+ def get_metadata_and_header(self):
268
+ if self.mosaic_version != 0:
269
+ metadata = self.mosaic_data['metadata']
270
+ header = self.mosaic_data['header']
271
+ else:
272
+ metadata = list(self.mosaic_data.values())[0]['metadata']
273
+ header = self.collect_header(self.mosaic_data.values())
274
+
275
+ return metadata, header
276
+
277
+ def process(self):
278
+ self.get_mosaic(self.mosaic_url)
279
+ if 'version' in self.mosaic_data:
280
+ self.mosaic_version = self.mosaic_data['version']
281
+
282
+ if self.output_file.exists() and not self.tracker_file.exists():
283
+ raise Exception(f'Output file {self.archive_writer.output_file} already exists, and no tracker file found. Exiting to avoid overwriting.')
284
+
285
+ self.init_tracker()
286
+ self.populate_done_list()
287
+
288
+ self.archive_writer.init()
289
+
290
+ slice_data = self.mosaic_data if self.mosaic_version == 0 else self.mosaic_data.get('slices', {})
291
+
292
+ for k in slice_data.keys():
293
+
294
+ pmtiles_url = self.get_pmtiles_url(k)
295
+ if k in self.done_stages:
296
+ print(f'Stage {k} already done, skipping')
297
+ continue
298
+
299
+ pmtiles_file = self.output_dir / get_filename_from_url(pmtiles_url)
300
+ if pmtiles_file.exists():
301
+ raise Exception(f'{pmtiles_file} already exists, delete existing file to continue')
302
+
303
+ self.download_file(pmtiles_url, pmtiles_file)
304
+
305
+ self.archive_writer.add(pmtiles_file)
306
+
307
+ self.mark_as_done(k)
308
+
309
+ pmtiles_file.unlink()
310
+
311
+ metadata, header = self.get_metadata_and_header()
312
+ self.archive_writer.finalize(metadata, header)
313
+
314
+ def cleanup(self):
315
+ if self.tracker_file.exists():
316
+ self.tracker_file.unlink()
317
+ print('Tracker file deleted.')
318
+
319
+ if self.mosaic_file.exists():
320
+ self.mosaic_file.unlink()
321
+ print('Mosaic file deleted.')
322
+
323
+ def cli():
324
+ import argparse
325
+ parser = argparse.ArgumentParser(description='Download a mosaic and convert it to MBTiles or PMTiles format.')
326
+ parser.add_argument('--mosaic-url', '-u', required=True, type=str, help='URL of the mosaic JSON file')
327
+ parser.add_argument('--output-file', '-o', type=str, help='Output MBTiles/PMTiles file name. The format is inferred from the extension.')
328
+ parser.add_argument('--archive-type', '-a', choices=['mbtiles', 'pmtiles'], help='Type of archive to create. Required if --output-file is not provided.')
329
+ parser.add_argument('--request-timeout-secs', '-t', type=int, default=60, help='Timeout for HTTP requests in seconds')
330
+ parser.add_argument('--num-http-retries', '-r', type=int, default=3, help='Number of retries for HTTP requests')
331
+ args = parser.parse_args()
332
+
333
+ archive_type = args.archive_type
334
+ output_file = None
335
+ mosaic_fname = get_filename_from_url(args.mosaic_url)
336
+
337
+ if args.output_file:
338
+ output_file = Path(args.output_file)
339
+ if output_file.suffix == '.mbtiles':
340
+ if archive_type and archive_type != 'mbtiles':
341
+ raise ValueError("Output file extension is .mbtiles but --archive-type is not 'mbtiles'")
342
+ archive_type = 'mbtiles'
343
+ elif output_file.suffix == '.pmtiles':
344
+ if archive_type and archive_type != 'pmtiles':
345
+ raise ValueError("Output file extension is .pmtiles but --archive-type is not 'pmtiles'")
346
+ archive_type = 'pmtiles'
347
+ else:
348
+ raise ValueError("If --output-file is provided, it must have a .mbtiles or .pmtiles extension.")
349
+
350
+ if not archive_type:
351
+ raise ValueError("You must specify either --output-file (with .mbtiles or .pmtiles extension) or --archive-type.")
352
+
353
+ if not output_file:
354
+ if not mosaic_fname.endswith('.mosaic.json'):
355
+ raise ValueError('Input mosaic URL must point to a file ending with .mosaic.json if --output-file is not specified.')
356
+
357
+ out_fname = mosaic_fname[:-len('.mosaic.json')] + '.' + archive_type
358
+ output_file = Path(out_fname)
359
+
360
+
361
+ merger = Merger(args.mosaic_url,
362
+ archive_type,
363
+ output_file,
364
+ args.request_timeout_secs,
365
+ args.num_http_retries)
366
+
367
+ merger.process()
368
+ merger.cleanup()
369
+
370
+ print('Done!!!')
371
+
372
+
373
+ if __name__ == '__main__':
374
+ cli()
375
+
@@ -53,5 +53,7 @@ def upload_to_release():
53
53
  execute_bash_script("upload_to_release.sh")
54
54
 
55
55
 
56
+
57
+
56
58
  if __name__ == "__main__":
57
59
  generate_lists()