topo_map_processor 0.1.9__tar.gz → 0.1.11__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 (20) hide show
  1. {topo_map_processor-0.1.9 → topo_map_processor-0.1.11}/PKG-INFO +12 -47
  2. {topo_map_processor-0.1.9 → topo_map_processor-0.1.11}/README.md +10 -45
  3. {topo_map_processor-0.1.9 → topo_map_processor-0.1.11}/pyproject.toml +2 -8
  4. {topo_map_processor-0.1.9 → topo_map_processor-0.1.11}/topo_map_processor/_version.py +2 -2
  5. {topo_map_processor-0.1.9 → topo_map_processor-0.1.11}/topo_map_processor/processor.py +9 -4
  6. {topo_map_processor-0.1.9 → topo_map_processor-0.1.11}/topo_map_processor/tools/retile.py +19 -21
  7. topo_map_processor-0.1.11/topo_map_processor/tools/retile_e2e.py +182 -0
  8. {topo_map_processor-0.1.9 → topo_map_processor-0.1.11}/topo_map_processor/tools/tile.py +20 -9
  9. topo_map_processor-0.1.9/topo_map_processor/bash_scripts/generate_lists.sh +0 -33
  10. topo_map_processor-0.1.9/topo_map_processor/bash_scripts/upload_to_release.sh +0 -148
  11. topo_map_processor-0.1.9/topo_map_processor/tools/__init__.py +0 -0
  12. topo_map_processor-0.1.9/topo_map_processor/tools/download_mosaic.py +0 -256
  13. topo_map_processor-0.1.9/topo_map_processor/tools/execute_bash.py +0 -57
  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/tools/tile_sources.py +0 -417
  16. {topo_map_processor-0.1.9 → topo_map_processor-0.1.11}/.gitignore +0 -0
  17. {topo_map_processor-0.1.9 → topo_map_processor-0.1.11}/LICENSE +0 -0
  18. {topo_map_processor-0.1.9 → topo_map_processor-0.1.11}/topo_map_processor/__init__.py +0 -0
  19. {topo_map_processor-0.1.9/topo_map_processor/bash_scripts → topo_map_processor-0.1.11/topo_map_processor/tools}/__init__.py +0 -0
  20. {topo_map_processor-0.1.9 → topo_map_processor-0.1.11}/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.11
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-mosaic>=0.1.0
36
36
  Requires-Dist: requests>=2.32.4
37
37
  Requires-Dist: shapely>=2.1.1
38
38
  Provides-Extra: easyocr
@@ -184,7 +184,13 @@ The `get_intersection_point` method is the most critical part to customize for a
184
184
  3. Identifying the point closest to the expected corner position, while also matching an expected angle and distance from an anchor point.
185
185
 
186
186
  ![corner for 4way intersection point](/samples/corner3.png)
187
- - **`get_4way_intersection_point`**: This is a more robust version for crosshair-style corners. It specifically looks for intersections where four lines meet, making it less prone to errors from stray lines in the map collar.
187
+ - **`get_4way_intersection_point`**: This is a more robust version for crosshair-style corners. It works by:
188
+ 1. Isolating lines based on their color.
189
+ 2. Optionally removing any text that might interfere with line detection.
190
+ 3. Ignoring the edges of the corner image patch to avoid spurious intersections with the border.
191
+ 4. Detecting all horizontal and vertical lines and finding their intersection points.
192
+ 5. Validating each intersection by checking for the presence of lines extending in all four directions (up, down, left, right) from the intersection point. This ensures it's a true 4-way "crosshair" intersection.
193
+ 6. It is designed to be very specific and will fail if it doesn't find exactly one 4-way intersection.
188
194
 
189
195
  ![corner for biggest contour corner](/samples/corner2.png)
190
196
  - **`get_biggest_contour_corner`**: This strategy is designed for corners that are marked by a solid shape or a filled area (e.g., a colored square or circle). It works by:
@@ -208,15 +214,6 @@ Similar to corner detection, the `locate_grid_lines` method can be implemented u
208
214
 
209
215
  - **`get_grid_line_corrections`**: This method serves as a refinement step to improve accuracy. While the transformer provides a very close estimate, this function can correct for minor printing or scanning inaccuracies. For each projected grid intersection, it searches a small area in the actual image for the *true* intersection of printed lines, adjusting the point to match the visual data. This ensures that the lines targeted for removal are precisely the ones printed on the map.
210
216
 
211
- ### Tile Sources
212
-
213
- The `topo_map_processor.tools.tile_sources` module provides classes for reading tiles from different sources.
214
-
215
- - `DiskTilesSource`: Reads tiles from a directory on the local disk.
216
- - `MBTilesSource`: Reads tiles from an MBTiles file.
217
- - `PMTilesSource`: Reads tiles from a PMTiles file.
218
- - `StackedTileSource`: Combines multiple tile sources (Disk, MBTiles, PMTiles) into a single logical source, allowing for seamless access across different storage types.
219
-
220
217
  ### Command-Line Tools
221
218
 
222
219
  This project provides several command-line tools to work with topographic maps. These tools are dynamically loaded, and you can get the most up-to-date usage information by running the tool with the `--help` flag.
@@ -233,38 +230,6 @@ collect-bounds --bounds-dir <directory> --output-file <file> [--preexisting-file
233
230
  - `--output-file`: Output GeoJSON file path. (Required)
234
231
  - `--preexisting-file`: Pre existing GeoJSON file path. (Optional)
235
232
 
236
- #### `download-mosaic`
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.
239
-
240
- ```bash
241
- download-mosaic --mosaic_url <url> [--output_file <file>] [--request-timeout-secs <seconds>] [--num-http-retries <retries>]
242
- ```
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)
246
- - `--request-timeout-secs`, `-t`: Timeout for HTTP requests in seconds (default: 60).
247
- - `--num-http-retries`, `-r`: Number of retries for HTTP requests (default: 3).
248
-
249
- #### `partition`
250
-
251
- Partitions a large set of tiles into smaller PMTiles files. This is useful for managing large datasets, for example, to stay within file size limits for services like GitHub Releases. It creates a mosaic file if the output is partitioned into multiple files.
252
-
253
- ```bash
254
- partition --from-source <source> [--from-source <source> ...] --to-pmtiles <file> [--size-limit <limit>]
255
- ```
256
-
257
- - `--from-source`: Path to a source file or directory. Can be repeated. Supported sources:
258
- - `.mbtiles` file: e.g., `path/to/file.mbtiles`
259
- - `.pmtiles` file (supports glob patterns): e.g., `path/to/*.pmtiles`
260
- - Directory containing tiles: e.g., `path/to/tiles_dir`
261
- - `--to-pmtiles`: Output PMTiles file. The prefix for partitioned files will be derived from this argument.
262
- - `--size-limit`: Maximum size of each partition. Can be a number in bytes (e.g., `100000000`), or with units (e.g., `100M`, `2G`), or a preset:
263
- - `github_release`: 2GB limit
264
- - `github_file`: 100MB limit
265
- - `cloudflare_object`: 512MB limit
266
- The overhead (delta) is scaled proportionally based on the `github_release` preset (2GB with 5MB overhead). Defaults to `github_release`.
267
-
268
233
  #### `retile`
269
234
 
270
235
  Retiles a specific list of sheets (GeoTIFFs) and updates the corresponding tiles in an existing tile set. It can determine which other sheets are affected and need to be pulled in to correctly retile the area.
@@ -285,7 +250,7 @@ retile --retile-list-file <file> --bounds-file <file> [--max-zoom <zoom>] [--tif
285
250
  - `--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
251
  - `--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
252
  - `--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).
253
+ - `--tile-quality`: Quality of compression for webp and jpeg (default: 75).
289
254
 
290
255
  #### `tile`
291
256
 
@@ -303,8 +268,8 @@ tile --tiles-dir <dir> --tiffs-dir <dir> --max-zoom <zoom> --name <name> --descr
303
268
  - `--attribution`: Attribution text for the mosaic. Required if `--attribution-file` is not used.
304
269
  - `--attribution-file`: File containing attribution text for the mosaic. Required if `--attribution` is not used.
305
270
  - `--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).
271
+ - `--tile-extension`: Tile file extension (default: webp). Choices: `webp`, `jpeg`, `png`.
272
+ - `--tile-quality`: Compression quality of the tiles for `webp` and `jpeg` (default: 75).
308
273
  - `--num-parallel`: Number of parallel processes to use for tiling (default: number of CPU cores).
309
274
 
310
275
  ### Running Tools Directly with `uvx`
@@ -135,7 +135,13 @@ The `get_intersection_point` method is the most critical part to customize for a
135
135
  3. Identifying the point closest to the expected corner position, while also matching an expected angle and distance from an anchor point.
136
136
 
137
137
  ![corner for 4way intersection point](/samples/corner3.png)
138
- - **`get_4way_intersection_point`**: This is a more robust version for crosshair-style corners. It specifically looks for intersections where four lines meet, making it less prone to errors from stray lines in the map collar.
138
+ - **`get_4way_intersection_point`**: This is a more robust version for crosshair-style corners. It works by:
139
+ 1. Isolating lines based on their color.
140
+ 2. Optionally removing any text that might interfere with line detection.
141
+ 3. Ignoring the edges of the corner image patch to avoid spurious intersections with the border.
142
+ 4. Detecting all horizontal and vertical lines and finding their intersection points.
143
+ 5. Validating each intersection by checking for the presence of lines extending in all four directions (up, down, left, right) from the intersection point. This ensures it's a true 4-way "crosshair" intersection.
144
+ 6. It is designed to be very specific and will fail if it doesn't find exactly one 4-way intersection.
139
145
 
140
146
  ![corner for biggest contour corner](/samples/corner2.png)
141
147
  - **`get_biggest_contour_corner`**: This strategy is designed for corners that are marked by a solid shape or a filled area (e.g., a colored square or circle). It works by:
@@ -159,15 +165,6 @@ Similar to corner detection, the `locate_grid_lines` method can be implemented u
159
165
 
160
166
  - **`get_grid_line_corrections`**: This method serves as a refinement step to improve accuracy. While the transformer provides a very close estimate, this function can correct for minor printing or scanning inaccuracies. For each projected grid intersection, it searches a small area in the actual image for the *true* intersection of printed lines, adjusting the point to match the visual data. This ensures that the lines targeted for removal are precisely the ones printed on the map.
161
167
 
162
- ### Tile Sources
163
-
164
- The `topo_map_processor.tools.tile_sources` module provides classes for reading tiles from different sources.
165
-
166
- - `DiskTilesSource`: Reads tiles from a directory on the local disk.
167
- - `MBTilesSource`: Reads tiles from an MBTiles file.
168
- - `PMTilesSource`: Reads tiles from a PMTiles file.
169
- - `StackedTileSource`: Combines multiple tile sources (Disk, MBTiles, PMTiles) into a single logical source, allowing for seamless access across different storage types.
170
-
171
168
  ### Command-Line Tools
172
169
 
173
170
  This project provides several command-line tools to work with topographic maps. These tools are dynamically loaded, and you can get the most up-to-date usage information by running the tool with the `--help` flag.
@@ -184,38 +181,6 @@ collect-bounds --bounds-dir <directory> --output-file <file> [--preexisting-file
184
181
  - `--output-file`: Output GeoJSON file path. (Required)
185
182
  - `--preexisting-file`: Pre existing GeoJSON file path. (Optional)
186
183
 
187
- #### `download-mosaic`
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.
190
-
191
- ```bash
192
- download-mosaic --mosaic_url <url> [--output_file <file>] [--request-timeout-secs <seconds>] [--num-http-retries <retries>]
193
- ```
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)
197
- - `--request-timeout-secs`, `-t`: Timeout for HTTP requests in seconds (default: 60).
198
- - `--num-http-retries`, `-r`: Number of retries for HTTP requests (default: 3).
199
-
200
- #### `partition`
201
-
202
- Partitions a large set of tiles into smaller PMTiles files. This is useful for managing large datasets, for example, to stay within file size limits for services like GitHub Releases. It creates a mosaic file if the output is partitioned into multiple files.
203
-
204
- ```bash
205
- partition --from-source <source> [--from-source <source> ...] --to-pmtiles <file> [--size-limit <limit>]
206
- ```
207
-
208
- - `--from-source`: Path to a source file or directory. Can be repeated. Supported sources:
209
- - `.mbtiles` file: e.g., `path/to/file.mbtiles`
210
- - `.pmtiles` file (supports glob patterns): e.g., `path/to/*.pmtiles`
211
- - Directory containing tiles: e.g., `path/to/tiles_dir`
212
- - `--to-pmtiles`: Output PMTiles file. The prefix for partitioned files will be derived from this argument.
213
- - `--size-limit`: Maximum size of each partition. Can be a number in bytes (e.g., `100000000`), or with units (e.g., `100M`, `2G`), or a preset:
214
- - `github_release`: 2GB limit
215
- - `github_file`: 100MB limit
216
- - `cloudflare_object`: 512MB limit
217
- The overhead (delta) is scaled proportionally based on the `github_release` preset (2GB with 5MB overhead). Defaults to `github_release`.
218
-
219
184
  #### `retile`
220
185
 
221
186
  Retiles a specific list of sheets (GeoTIFFs) and updates the corresponding tiles in an existing tile set. It can determine which other sheets are affected and need to be pulled in to correctly retile the area.
@@ -236,7 +201,7 @@ retile --retile-list-file <file> --bounds-file <file> [--max-zoom <zoom>] [--tif
236
201
  - `--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
202
  - `--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
203
  - `--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).
204
+ - `--tile-quality`: Quality of compression for webp and jpeg (default: 75).
240
205
 
241
206
  #### `tile`
242
207
 
@@ -254,8 +219,8 @@ tile --tiles-dir <dir> --tiffs-dir <dir> --max-zoom <zoom> --name <name> --descr
254
219
  - `--attribution`: Attribution text for the mosaic. Required if `--attribution-file` is not used.
255
220
  - `--attribution-file`: File containing attribution text for the mosaic. Required if `--attribution` is not used.
256
221
  - `--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).
222
+ - `--tile-extension`: Tile file extension (default: webp). Choices: `webp`, `jpeg`, `png`.
223
+ - `--tile-quality`: Compression quality of the tiles for `webp` and `jpeg` (default: 75).
259
224
  - `--num-parallel`: Number of parallel processes to use for tiling (default: number of CPU cores).
260
225
 
261
226
  ### 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-mosaic>=0.1.0",
13
13
  "requests>=2.32.4",
14
14
  "shapely>=2.1.1",
15
15
  ]
@@ -38,13 +38,9 @@ repository = "https://github.com/ramSeraph/topo_map_processor.git"
38
38
 
39
39
  [project.scripts]
40
40
  collect-bounds = "topo_map_processor.tools.collect_bounds:cli"
41
- download-mosaic = "topo_map_processor.tools.download_mosaic:cli"
42
- partition = "topo_map_processor.tools.partition:cli"
43
41
  retile = "topo_map_processor.tools.retile:cli"
44
42
  tile = "topo_map_processor.tools.tile:cli"
45
- generate-lists = "topo_map_processor.tools.execute_bash:generate_lists"
46
- upload-to-release = "topo_map_processor.tools.execute_bash:upload_to_release"
47
-
43
+ retile-e2e = "topo_map_processor.tools.retile_e2e:cli"
48
44
 
49
45
  [build-system]
50
46
  requires = [
@@ -62,12 +58,10 @@ version-file = "topo_map_processor/_version.py"
62
58
  [tool.hatch.build.targets.sdist]
63
59
  include = [
64
60
  "topo_map_processor",
65
- "topo_map_processor/bash_scripts/**",
66
61
  ]
67
62
 
68
63
  [tool.hatch.build.targets.wheel]
69
64
  include = [
70
65
  "topo_map_processor",
71
- "topo_map_processor/bash_scripts/**",
72
66
  ]
73
67
 
@@ -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.11'
21
+ __version_tuple__ = version_tuple = (0, 1, 11)
@@ -1074,12 +1074,16 @@ class TopoMapProcessor:
1074
1074
 
1075
1075
  return ip
1076
1076
 
1077
- def get_4way_intersection_point(self, img, line_color,
1077
+ def get_4way_intersection_point(self, img, line_color, remove_text,
1078
1078
  find_line_scale, find_line_iter,
1079
- cwidth, ext_thresh):
1079
+ cwidth, ext_thresh, direction,
1080
+ remove_corner_edges_ratio):
1080
1081
  img_mask = self.get_color_mask(img, line_color)
1081
1082
 
1082
- self.remove_text(img_mask)
1083
+ if remove_text:
1084
+ self.remove_text(img_mask)
1085
+
1086
+ self.remove_corner_edges(img_mask, direction, remove_corner_edges_ratio)
1083
1087
 
1084
1088
  h, w = img_mask.shape[:2]
1085
1089
  self.show_image(img_mask)
@@ -1106,7 +1110,8 @@ class TopoMapProcessor:
1106
1110
  if len(four_corner_ips) > 1:
1107
1111
  continue
1108
1112
 
1109
- self.show_points(four_corner_ips, img_mask, [0, 255, 0])
1113
+ self.show_points(four_corner_ips, img_mask, [0, 255, 0])
1114
+
1110
1115
  if len(four_corner_ips) > 1:
1111
1116
  raise Exception(f'multiple intersection points found - {len(four_corner_ips)}')
1112
1117
 
@@ -1,16 +1,7 @@
1
- # /// script
2
- # requires-python = ">=3.12"
3
- # dependencies = [
4
- # "mercantile",
5
- # "pmtiles",
6
- # "shapely",
7
- # ]
8
- # ///
9
1
 
10
2
  import os
11
3
  import re
12
4
  import sys
13
- import glob
14
5
  import json
15
6
  import argparse
16
7
 
@@ -33,7 +24,7 @@ from osgeo_utils.gdal2tiles import (
33
24
  DividedCache,
34
25
  )
35
26
 
36
- from .tile_sources import create_source_from_paths, MissingTileError
27
+ from pmtiles_mosaic.tile_sources import create_source_from_paths, MissingTileError
37
28
 
38
29
  def run_external(cmd):
39
30
  print(f'running cmd - {cmd}')
@@ -142,7 +133,7 @@ class Tiler:
142
133
  '--webp-quality', str(self.tile_quality),
143
134
  ]
144
135
 
145
- if self.tile_extension == 'jpg':
136
+ if self.tile_extension == 'jpeg':
146
137
  return params + [
147
138
  '--jpeg_quality', str(self.tile_quality),
148
139
  ]
@@ -156,7 +147,7 @@ class Tiler:
156
147
  if self.tile_extension == 'webp':
157
148
  return 'WEBP'
158
149
 
159
- if self.tile_extension == 'jpg':
150
+ if self.tile_extension == 'jpeg':
160
151
  return 'JPEG'
161
152
 
162
153
  if self.tile_extension == 'png':
@@ -171,7 +162,7 @@ class Tiler:
171
162
  'webp_lossless': False,
172
163
  }
173
164
 
174
- if self.tile_extension == 'jpg':
165
+ if self.tile_extension == 'jpeg':
175
166
  return {
176
167
  'jpeg_quality': self.tile_quality,
177
168
  }
@@ -308,7 +299,10 @@ class Tiler:
308
299
 
309
300
  prev_affected_tiles = curr_affected_tiles
310
301
 
311
- self.disk_handler.save_metadata(self.orig_tile_source.get_metadata())
302
+ metadata = self.orig_tile_source.get_metadata()
303
+ metadata['minzoom'] = min_zoom
304
+ metadata['maxzoom'] = max_zoom
305
+ self.disk_handler.save_metadata(metadata)
312
306
  print('All Done!!!')
313
307
 
314
308
 
@@ -379,11 +373,7 @@ def assess_sheet_requirements(retile_list_file, bounds_file, max_zoom):
379
373
 
380
374
  return sheets_to_pull, affected_base_tiles
381
375
 
382
- def cli():
383
-
384
- # I don't care.. this shit isn't worth thinking about
385
- if sys.platform == 'darwin':
386
- set_start_method('fork')
376
+ def retile_main(args):
387
377
 
388
378
  parser = argparse.ArgumentParser()
389
379
  parser.add_argument('--retile-list-file', required=True, help='File containing list of sheets to retile')
@@ -395,9 +385,9 @@ def cli():
395
385
  parser.add_argument('--tiles-dir', help='Directory where the tiles will be created')
396
386
  parser.add_argument('--tiffs-dir', help='Directory where the tiffs are present')
397
387
  parser.add_argument('--num-parallel', type=int, default=cpu_count(), help='Number of parallel processes to use for tiling (default: number of CPU cores)')
398
- parser.add_argument('--tile-quality', default=75, help='quality of compression for webp and jpg (default: 75)')
388
+ parser.add_argument('--tile-quality', default=75, help='quality of compression for webp and jpeg (default: 75)')
399
389
 
400
- args = parser.parse_args()
390
+ args = parser.parse_args(args)
401
391
 
402
392
  retile_list_file = Path(args.retile_list_file)
403
393
  if not retile_list_file.exists():
@@ -452,6 +442,8 @@ def cli():
452
442
 
453
443
  metadata = orig_source.get_metadata()
454
444
  tile_extension = metadata['format']
445
+ if tile_extension == 'jpg':
446
+ tile_extension = 'jpeg'
455
447
 
456
448
  os.environ['GDAL_CACHEMAX'] = '2048'
457
449
  os.environ['GDAL_MAX_DATASET_POOL_SIZE'] = '5000'
@@ -470,5 +462,11 @@ def cli():
470
462
 
471
463
  return 0
472
464
 
465
+ def cli():
466
+ if sys.platform == 'darwin':
467
+ set_start_method('fork')
468
+
469
+ retile_main(sys.argv[1:])
470
+
473
471
  if __name__ == '__main__':
474
472
  cli()
@@ -0,0 +1,182 @@
1
+
2
+ import argparse
3
+ import csv
4
+ import glob
5
+ import shutil
6
+ import subprocess
7
+ import sys
8
+ from pathlib import Path
9
+ from multiprocessing import set_start_method
10
+
11
+ from pmtiles_mosaic.partition import partition_main
12
+
13
+ from .retile import retile_main
14
+
15
+
16
+ def run_command(cmd, check=True, cwd=None):
17
+ """Runs a command and prints its output."""
18
+ print(f"Running command: {' '.join(cmd)}")
19
+ try:
20
+ result = subprocess.run(
21
+ cmd,
22
+ check=check,
23
+ capture_output=True,
24
+ text=True,
25
+ cwd=cwd,
26
+ )
27
+ if result.stdout:
28
+ print(result.stdout)
29
+ if result.stderr:
30
+ print(result.stderr, file=sys.stderr)
31
+ return result
32
+ except subprocess.CalledProcessError as e:
33
+ print(f"Command failed with exit code {e.returncode}", file=sys.stderr)
34
+ if e.stdout:
35
+ print("STDOUT:", file=sys.stderr)
36
+ print(e.stdout, file=sys.stderr)
37
+ if e.stderr:
38
+ print("STDERR:", file=sys.stderr)
39
+ print(e.stderr, file=sys.stderr)
40
+ raise
41
+
42
+
43
+ def cli():
44
+ """Main function to execute the end-to-end retiling process."""
45
+
46
+ if sys.platform == 'darwin':
47
+ set_start_method('fork')
48
+
49
+ parser = argparse.ArgumentParser(description="End-to-end retiling script.")
50
+ parser.add_argument("-p", "--pmtiles-release", required=True, help="PMTiles release tag")
51
+ parser.add_argument("-g", "--gtiffs-release", required=True, help="GeoTIFFs release tag")
52
+ parser.add_argument("-x", "--pmtiles-prefix", required=True, help="Prefix for PMTiles files")
53
+ parser.add_argument("-l", "--listing-files-tiled", required=True, help="Name of the tiled listing file")
54
+ args = parser.parse_args()
55
+
56
+ # Define paths and filenames
57
+ sheets_to_pull_list_outfile = Path("sheets_to_pull_list.txt")
58
+ tiles_dir = Path("staging/tiles/")
59
+ tiffs_dir = Path("staging/gtiffs/")
60
+ from_pmtiles_dir = Path("staging/pmtiles")
61
+ to_pmtiles_dir = Path("export/pmtiles")
62
+ retile_list_file = Path("to_retile.txt")
63
+
64
+ from_pmtiles_prefix = from_pmtiles_dir / args.pmtiles_prefix
65
+ to_pmtiles_prefix = to_pmtiles_dir / args.pmtiles_prefix
66
+
67
+ # Create directories
68
+ for d in [tiles_dir, tiffs_dir, from_pmtiles_dir, to_pmtiles_dir]:
69
+ d.mkdir(parents=True, exist_ok=True)
70
+
71
+ # Download listing files
72
+ print("Downloading listing files...")
73
+ run_command(["gh", "release", "download", args.gtiffs_release, "-p", "listing_files.csv", "--clobber"])
74
+ run_command(["gh", "release", "download", args.pmtiles_release, "-p", args.listing_files_tiled, "--clobber", "-O", "listing_files_tiled.csv"])
75
+
76
+ # Determine sheets to retile
77
+ print("Determining sheets to retile...")
78
+ with open("listing_files.csv") as f:
79
+ gtiffs_sheets = {row['name'] for row in csv.DictReader(f)}
80
+ with open("listing_files_tiled.csv") as f:
81
+ pmtiles_sheets = {row['name'] for row in csv.DictReader(f)}
82
+
83
+ sheets_to_retile = sorted(list(gtiffs_sheets - pmtiles_sheets))
84
+
85
+ with open(retile_list_file, "w") as f:
86
+ for sheet in sheets_to_retile:
87
+ f.write(f"{sheet}\n")
88
+
89
+ print(f"Found {len(sheets_to_retile)} sheets to retile.")
90
+ Path("listing_files_tiled.csv").unlink()
91
+
92
+ if not sheets_to_retile:
93
+ print("No sheets to retile found. Cleaning up and exiting.")
94
+ Path("listing_files.csv").unlink()
95
+ retile_list_file.unlink()
96
+ sys.exit(0)
97
+
98
+ # Download bounds and original PMTiles
99
+ print("Downloading bounds file...")
100
+ run_command(["gh", "release", "download", args.gtiffs_release, "-p", "bounds.geojson"])
101
+ print("Getting original PMTiles files...")
102
+ run_command(["gh", "release", "download", args.pmtiles_release, "-D", str(from_pmtiles_dir), "-p", f"{args.pmtiles_prefix}*"])
103
+
104
+ # Get list of sheets to pull
105
+ print("Getting list of sheets to pull...")
106
+ retile_main([
107
+ "--retile-list-file", str(retile_list_file),
108
+ "--bounds-file", "bounds.geojson",
109
+ "--sheets-to-pull-list-outfile", str(sheets_to_pull_list_outfile),
110
+ "--from-source", f"{from_pmtiles_prefix}*.pmtiles",
111
+ ])
112
+
113
+ print("Sheets to pull:")
114
+ print(sheets_to_pull_list_outfile.read_text())
115
+
116
+ # Download TIFFs
117
+ print("Downloading TIFFs...")
118
+ with open("listing_files.csv") as f:
119
+ url_map = {row[0]: row[2] for row in csv.reader(f)}
120
+
121
+ with open(sheets_to_pull_list_outfile) as f:
122
+ for line in f:
123
+ fname = line.strip()
124
+ if fname in url_map:
125
+ url = url_map[fname]
126
+ print(f"Pulling {fname}")
127
+ run_command(["wget", "-q", "-P", str(tiffs_dir), url])
128
+
129
+ sheets_to_pull_list_outfile.unlink()
130
+
131
+ # Retile sheets
132
+ print("Retiling the sheets...")
133
+ retile_main([
134
+ "--retile-list-file", str(retile_list_file),
135
+ "--bounds-file", "bounds.geojson",
136
+ "--from-source", f"{from_pmtiles_prefix}*.pmtiles",
137
+ "--tiles-dir", str(tiles_dir),
138
+ "--tiffs-dir", str(tiffs_dir),
139
+ ])
140
+
141
+ retile_list_file.unlink()
142
+ Path("bounds.geojson").unlink()
143
+
144
+ # Create new PMTiles files
145
+ print("Creating new pmtiles files...")
146
+ partition_main([
147
+ "--from-source", str(tiles_dir),
148
+ "--from-source", f"{from_pmtiles_prefix}*.pmtiles",
149
+ "--to-pmtiles", f"{to_pmtiles_prefix}.pmtiles",
150
+ ])
151
+
152
+ # Upload new PMTiles and manage release assets
153
+ print("Deleting old PMTiles files from the release...")
154
+ old_pmtiles = glob.glob(f"{from_pmtiles_dir}/{args.pmtiles_prefix}*")
155
+ for pmtile in old_pmtiles:
156
+ run_command(["gh", "release", "delete-asset", args.pmtiles_release, Path(pmtile).name, "-y"])
157
+
158
+ print("Uploading new PMTiles files...")
159
+ new_pmtiles = glob.glob(f"{to_pmtiles_prefix}*")
160
+ run_command(["gh", "release", "upload", args.pmtiles_release, "--clobber"] + new_pmtiles)
161
+
162
+ # Handle listing files
163
+ if args.listing_files_tiled != "listing_files.csv":
164
+ print(f"Renaming listing_files.csv to {args.listing_files_tiled}")
165
+ Path("listing_files.csv").rename(args.listing_files_tiled)
166
+
167
+ print("Uploading new listing file...")
168
+ run_command(["gh", "release", "upload", args.pmtiles_release, args.listing_files_tiled, "--clobber"])
169
+ Path(args.listing_files_tiled).unlink()
170
+
171
+ # Cleanup
172
+ print("Cleaning up staging directories...")
173
+ shutil.rmtree(tiles_dir, ignore_errors=True)
174
+ shutil.rmtree(tiffs_dir, ignore_errors=True)
175
+ shutil.rmtree(from_pmtiles_dir, ignore_errors=True)
176
+ shutil.rmtree(to_pmtiles_dir, ignore_errors=True)
177
+
178
+ print("Done!")
179
+
180
+
181
+ if __name__ == "__main__":
182
+ cli()
@@ -9,9 +9,12 @@ import subprocess
9
9
 
10
10
  from pathlib import Path
11
11
  from multiprocessing import set_start_method, cpu_count
12
+ from pprint import pprint
12
13
 
13
14
  from osgeo_utils.gdal2tiles import submain as gdal2tiles_main
14
15
 
16
+ SUPPORTED_FORMATS = ['webp', 'jpeg', 'png']
17
+
15
18
  def run_external(cmd):
16
19
  print(f'running cmd - {cmd}')
17
20
  start = time.time()
@@ -41,7 +44,7 @@ def get_tiler_cmd_params(tile_extension, tile_quality):
41
44
  '--webp-quality', str(tile_quality),
42
45
  ]
43
46
 
44
- if tile_extension in ['jpg', 'jpeg']:
47
+ if tile_extension == 'jpeg':
45
48
  return [
46
49
  '--tiledriver', 'JPEG',
47
50
  '--jpeg_quality', str(tile_quality),
@@ -52,8 +55,16 @@ def get_tiler_cmd_params(tile_extension, tile_quality):
52
55
  '--tiledriver', 'PNG',
53
56
  ]
54
57
 
55
- raise ValueError(f'Unsupported tile extension: {tile_extension}')
58
+ raise ValueError(f'Unsupported tile extension: {tile_extension}, {SUPPORTED_FORMATS=}')
56
59
 
60
+ def get_format(tile_extension):
61
+ if tile_extension == 'webp':
62
+ return 'webp'
63
+ if tile_extension == 'jpeg':
64
+ return 'jpeg'
65
+ if tile_extension == 'png':
66
+ return 'png'
67
+ raise ValueError(f'Unsupported tile extension: {tile_extension}, {SUPPORTED_FORMATS=}')
57
68
 
58
69
 
59
70
  def cli():
@@ -65,7 +76,7 @@ def cli():
65
76
  parser.add_argument('--tiffs-dir', required=True, help='Directory with GTiffs to tile')
66
77
  parser.add_argument('--max-zoom', type=int, required=True, help='Maximum zoom level for tiling')
67
78
  parser.add_argument('--min-zoom', type=int, default=0, help='Minimum zoom level for tiling')
68
- parser.add_argument('--tile-extension', type=str, default='webp', choices=['webp', 'jpg', 'jpeg', 'png'], help='Tile file extension (default: webp)')
79
+ parser.add_argument('--tile-extension', type=str, default='webp', choices=SUPPORTED_FORMATS, help='Tile file extension (default: webp)')
69
80
  parser.add_argument('--tile-quality', type=int, default=75, help='Compression quality of the tiles (default: 75)')
70
81
  parser.add_argument('--num-parallel', type=int, default=cpu_count(), help='Number of parallel processes to use for tiling (default: number of CPU cores)')
71
82
  parser.add_argument('--name', required=True, help='Name of the mosaic.')
@@ -119,7 +130,6 @@ def cli():
119
130
  '--resume',
120
131
  '--xyz',
121
132
  '--processes', f'{args.num_parallel}',
122
- '--processes=8',
123
133
  '-z', f'{args.min_zoom}-{args.max_zoom}',
124
134
  ] + get_tiler_cmd_params(args.tile_extension, args.tile_quality) + [
125
135
  str(vrt_file), str(tiles_dir)
@@ -129,15 +139,13 @@ def cli():
129
139
  vrt_file.unlink()
130
140
 
131
141
  metadata_file = tiles_dir / 'tiles.json'
132
- if args.tile_extension == 'jpg':
133
- format = 'jpeg'
134
- else:
135
- format = args.tile_extension
142
+
143
+ fmt = get_format(args.tile_extension)
136
144
 
137
145
  # it is not really a proper tilejson spec.. it is the basic things needed to populate a pmtiles metadata
138
146
  metadata = {
139
147
  'type': 'baselayer',
140
- 'format': format,
148
+ 'format': fmt,
141
149
  'attribution': attribution_text,
142
150
  'description': args.description,
143
151
  'name': args.name,
@@ -145,6 +153,9 @@ def cli():
145
153
  'maxzoom': args.max_zoom,
146
154
  'minzoom': args.min_zoom,
147
155
  }
156
+
157
+ print(f'writing metadata to {metadata_file}')
158
+ pprint(metadata)
148
159
  metadata_file.write_text(json.dumps(metadata, indent=2))
149
160
 
150
161
  print('All Done!!')