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.
- {topo_map_processor-0.1.9 → topo_map_processor-0.1.11}/PKG-INFO +12 -47
- {topo_map_processor-0.1.9 → topo_map_processor-0.1.11}/README.md +10 -45
- {topo_map_processor-0.1.9 → topo_map_processor-0.1.11}/pyproject.toml +2 -8
- {topo_map_processor-0.1.9 → topo_map_processor-0.1.11}/topo_map_processor/_version.py +2 -2
- {topo_map_processor-0.1.9 → topo_map_processor-0.1.11}/topo_map_processor/processor.py +9 -4
- {topo_map_processor-0.1.9 → topo_map_processor-0.1.11}/topo_map_processor/tools/retile.py +19 -21
- topo_map_processor-0.1.11/topo_map_processor/tools/retile_e2e.py +182 -0
- {topo_map_processor-0.1.9 → topo_map_processor-0.1.11}/topo_map_processor/tools/tile.py +20 -9
- topo_map_processor-0.1.9/topo_map_processor/bash_scripts/generate_lists.sh +0 -33
- topo_map_processor-0.1.9/topo_map_processor/bash_scripts/upload_to_release.sh +0 -148
- topo_map_processor-0.1.9/topo_map_processor/tools/__init__.py +0 -0
- topo_map_processor-0.1.9/topo_map_processor/tools/download_mosaic.py +0 -256
- topo_map_processor-0.1.9/topo_map_processor/tools/execute_bash.py +0 -57
- topo_map_processor-0.1.9/topo_map_processor/tools/partition.py +0 -438
- topo_map_processor-0.1.9/topo_map_processor/tools/tile_sources.py +0 -417
- {topo_map_processor-0.1.9 → topo_map_processor-0.1.11}/.gitignore +0 -0
- {topo_map_processor-0.1.9 → topo_map_processor-0.1.11}/LICENSE +0 -0
- {topo_map_processor-0.1.9 → topo_map_processor-0.1.11}/topo_map_processor/__init__.py +0 -0
- {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
- {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.
|
|
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>=
|
|
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
|

|
|
187
|
-
- **`get_4way_intersection_point`**: This is a more robust version for crosshair-style corners. It
|
|
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
|

|
|
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
|
|
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`, `
|
|
307
|
-
- `--tile-quality`: Compression quality of the tiles for `webp` and `
|
|
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
|

|
|
138
|
-
- **`get_4way_intersection_point`**: This is a more robust version for crosshair-style corners. It
|
|
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
|

|
|
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
|
|
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`, `
|
|
258
|
-
- `--tile-quality`: Compression quality of the tiles for `webp` and `
|
|
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>=
|
|
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
|
-
|
|
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
|
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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 == '
|
|
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 == '
|
|
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 == '
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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=
|
|
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
|
-
|
|
133
|
-
|
|
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':
|
|
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!!')
|