topo_map_processor 0.1.7__tar.gz → 0.1.8__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 (22) hide show
  1. {topo_map_processor-0.1.7 → topo_map_processor-0.1.8}/.gitignore +1 -0
  2. {topo_map_processor-0.1.7 → topo_map_processor-0.1.8}/PKG-INFO +46 -25
  3. {topo_map_processor-0.1.7 → topo_map_processor-0.1.8}/README.md +44 -24
  4. {topo_map_processor-0.1.7 → topo_map_processor-0.1.8}/pyproject.toml +2 -0
  5. {topo_map_processor-0.1.7 → topo_map_processor-0.1.8}/topo_map_processor/_version.py +2 -2
  6. {topo_map_processor-0.1.7 → topo_map_processor-0.1.8}/topo_map_processor/bash_scripts/generate_lists.sh +2 -1
  7. {topo_map_processor-0.1.7 → topo_map_processor-0.1.8}/topo_map_processor/bash_scripts/upload_to_release.sh +2 -1
  8. topo_map_processor-0.1.8/topo_map_processor/tools/download_mosaic.py +254 -0
  9. {topo_map_processor-0.1.7 → topo_map_processor-0.1.8}/topo_map_processor/tools/execute_bash.py +4 -1
  10. topo_map_processor-0.1.8/topo_map_processor/tools/partition.py +433 -0
  11. topo_map_processor-0.1.8/topo_map_processor/tools/retile.py +474 -0
  12. {topo_map_processor-0.1.7 → topo_map_processor-0.1.8}/topo_map_processor/tools/tile.py +64 -9
  13. topo_map_processor-0.1.8/topo_map_processor/tools/tile_sources.py +417 -0
  14. topo_map_processor-0.1.7/topo_map_processor/tools/partition.py +0 -394
  15. topo_map_processor-0.1.7/topo_map_processor/tools/retile.py +0 -371
  16. topo_map_processor-0.1.7/topo_map_processor/tools/tile_sources.py +0 -332
  17. {topo_map_processor-0.1.7 → topo_map_processor-0.1.8}/LICENSE +0 -0
  18. {topo_map_processor-0.1.7 → topo_map_processor-0.1.8}/topo_map_processor/__init__.py +0 -0
  19. {topo_map_processor-0.1.7 → topo_map_processor-0.1.8}/topo_map_processor/bash_scripts/__init__.py +0 -0
  20. {topo_map_processor-0.1.7 → topo_map_processor-0.1.8}/topo_map_processor/processor.py +0 -0
  21. {topo_map_processor-0.1.7 → topo_map_processor-0.1.8}/topo_map_processor/tools/__init__.py +0 -0
  22. {topo_map_processor-0.1.7 → topo_map_processor-0.1.8}/topo_map_processor/tools/collect_bounds.py +0 -0
@@ -1,5 +1,6 @@
1
1
  # Python-generated files
2
2
  __pycache__/
3
+ topo_map_processor/_version.py
3
4
  *.py[oc]
4
5
  build/
5
6
  dist/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: topo_map_processor
3
- Version: 0.1.7
3
+ Version: 0.1.8
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
@@ -33,6 +33,7 @@ License-File: LICENSE
33
33
  Requires-Python: >=3.12
34
34
  Requires-Dist: mercantile>=1.2.1
35
35
  Requires-Dist: pmtiles>=3.4.1
36
+ Requires-Dist: requests>=2.32.4
36
37
  Requires-Dist: shapely>=2.1.1
37
38
  Provides-Extra: easyocr
38
39
  Requires-Dist: easyocr>=1.7.2; extra == 'easyocr'
@@ -211,9 +212,10 @@ Similar to corner detection, the `locate_grid_lines` method can be implemented u
211
212
 
212
213
  The `topo_map_processor.tools.tile_sources` module provides classes for reading tiles from different sources.
213
214
 
214
- - `DiskSource`: Reads tiles from a directory on the local disk.
215
- - `PartitionedPMTilesSource`: Reads tiles from a set of partitioned PMTiles files.
216
- - `DiskAndPartitionedPMTilesSource`: Reads tiles from both a local directory and a set of partitioned PMTiles files, useful for retiling.
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.
217
219
 
218
220
  ### Command-Line Tools
219
221
 
@@ -231,24 +233,37 @@ collect-bounds --bounds-dir <directory> --output-file <file> [--preexisting-file
231
233
  - `--output-file`: Output GeoJSON file path. (Required)
232
234
  - `--preexisting-file`: Pre existing GeoJSON file path. (Optional)
233
235
 
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
+
234
249
  #### `partition`
235
250
 
236
- 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. When run with `--only-disk`, it creates a new partitioned `pmtiles` fileset from a directory of tiles. When run without, it can be used to re-partition an existing `pmtiles` fileset, for example after having retiled some of the sheets.
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.
237
252
 
238
253
  ```bash
239
- partition --to-pmtiles-prefix <prefix> --from-tiles-dir <dir> [--from-pmtiles-prefix <prefix>] [--name <name>] [--description <desc>] [--max-zoom <zoom>] [--min-zoom <zoom>] [--only-disk] (--attribution <text> | --attribution-file <file>)
254
+ partition --from-source <source> [--from-source <source> ...] --to-pmtiles <file> [--size-limit <limit>]
240
255
  ```
241
256
 
242
- - `--to-pmtiles-prefix`: Prefix for the output PMTiles files. (Required)
243
- - `--from-tiles-dir`: Directory containing the input tiles. (Required)
244
- - `--from-pmtiles-prefix`: Prefix for input PMTiles files (required if not using `--only-disk`).
245
- - `--name`: Name of the mosaic. (Required if using `--only-disk`)
246
- - `--description`: Description of the mosaic. (Required if using `--only-disk`)
247
- - `--max-zoom`: Maximum zoom level for the mosaic. (Defaults to the max zoom found in the source)
248
- - `--min-zoom`: Minimum zoom level for the mosaic (default: 0).
249
- - `--only-disk`: Only read tiles from the local disk.
250
- - `--attribution`: Attribution text for the mosaic. (Required if using `--only-disk`)
251
- - `--attribution-file`: File containing attribution text. (Required if using `--only-disk`)
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`.
252
267
 
253
268
  #### `retile`
254
269
 
@@ -259,31 +274,37 @@ The tool works in two stages. First, it calculates the full set of sheets that n
259
274
  In the second stage, it retiles the affected area. It uses the pull list to create a temporary virtual raster (`.vrt`), generates new base tiles for the affected region, and then reconstructs the upper-level tiles by pulling existing, unaffected tiles from the PMTiles source.
260
275
 
261
276
  ```bash
262
- retile --retile-list-file <file> --bounds-file <file> [--max-zoom <zoom>] [--min-zoom <zoom>] [--tiffs-dir <dir>] [--tiles-dir <dir>] [--from-pmtiles-prefix <prefix>] [--sheets-to-pull-list-outfile <file>] [--num-parallel <processes>]
277
+ retile --retile-list-file <file> --bounds-file <file> [--max-zoom <zoom>] [--tiffs-dir <dir>] [--tiles-dir <dir>] [--from-source <source>] [--sheets-to-pull-list-outfile <file>] [--num-parallel <processes>] [--tile-quality <quality>]
263
278
  ```
264
279
 
265
280
  - `--retile-list-file`: File containing the list of sheets to retile. (Required)
266
281
  - `--bounds-file`: GeoJSON file containing the list of available sheets and their geographic bounds. (Required)
267
- - `--max-zoom`: Maximum zoom level to create tiles for. (Defaults to the max zoom from the PMTiles source)
268
- - `--min-zoom`: Minimum zoom level to create tiles for. (Defaults to the min zoom from the PMTiles source)
282
+ - `--max-zoom`: Maximum zoom level to create tiles for. Defaults to the max zoom from the source if `--from-source` is provided, otherwise it is required.
269
283
  - `--tiffs-dir`: Directory where the GeoTIFFs are located. (Required if not in pull-list generation mode)
270
284
  - `--tiles-dir`: Directory where the tiles will be created. (Required if not in pull-list generation mode)
271
- - `--from-pmtiles-prefix`: Prefix for the PMTiles source from which to pull existing tiles. (Required if not in pull-list generation mode)
285
+ - `--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.
272
286
  - `--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.
273
287
  - `--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).
274
289
 
275
290
  #### `tile`
276
291
 
277
292
  Creates web map tiles from a directory of GeoTIFF files. It combines them into a virtual raster (`.vrt`) for efficient processing.
278
293
 
279
294
  ```bash
280
- tile --tiles-dir <dir> --tiffs-dir <dir> --max-zoom <zoom> [--min-zoom <zoom>] [--num-parallel <processes>]
295
+ tile --tiles-dir <dir> --tiffs-dir <dir> --max-zoom <zoom> --name <name> --description <desc> (--attribution <text> | --attribution-file <file>) [--min-zoom <zoom>] [--tile-extension <ext>] [--tile-quality <quality>] [--num-parallel <processes>]
281
296
  ```
282
297
 
283
- - `<tiles-dir>`: Directory to store the output tiles. (Required)
284
- - `<tiffs-dir>`: Directory containing the input GeoTIFF files. (Required)
285
- - `<max-zoom>`: Maximum zoom level for tiling. (Required)
286
- - `<min-zoom>`: Minimum zoom level for tiling (default: 0).
298
+ - `--tiles-dir`: Directory to store the output tiles. (Required)
299
+ - `--tiffs-dir`: Directory containing the input GeoTIFF files. (Required)
300
+ - `--max-zoom`: Maximum zoom level for tiling. (Required)
301
+ - `--name`: Name of the mosaic. (Required)
302
+ - `--description`: Description of the mosaic. (Required)
303
+ - `--attribution`: Attribution text for the mosaic. Required if `--attribution-file` is not used.
304
+ - `--attribution-file`: File containing attribution text for the mosaic. Required if `--attribution` is not used.
305
+ - `--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).
287
308
  - `--num-parallel`: Number of parallel processes to use for tiling (default: number of CPU cores).
288
309
 
289
310
  ### Running Tools Directly with `uvx`
@@ -163,9 +163,10 @@ Similar to corner detection, the `locate_grid_lines` method can be implemented u
163
163
 
164
164
  The `topo_map_processor.tools.tile_sources` module provides classes for reading tiles from different sources.
165
165
 
166
- - `DiskSource`: Reads tiles from a directory on the local disk.
167
- - `PartitionedPMTilesSource`: Reads tiles from a set of partitioned PMTiles files.
168
- - `DiskAndPartitionedPMTilesSource`: Reads tiles from both a local directory and a set of partitioned PMTiles files, useful for retiling.
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.
169
170
 
170
171
  ### Command-Line Tools
171
172
 
@@ -183,24 +184,37 @@ collect-bounds --bounds-dir <directory> --output-file <file> [--preexisting-file
183
184
  - `--output-file`: Output GeoJSON file path. (Required)
184
185
  - `--preexisting-file`: Pre existing GeoJSON file path. (Optional)
185
186
 
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
+
186
200
  #### `partition`
187
201
 
188
- 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. When run with `--only-disk`, it creates a new partitioned `pmtiles` fileset from a directory of tiles. When run without, it can be used to re-partition an existing `pmtiles` fileset, for example after having retiled some of the sheets.
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.
189
203
 
190
204
  ```bash
191
- partition --to-pmtiles-prefix <prefix> --from-tiles-dir <dir> [--from-pmtiles-prefix <prefix>] [--name <name>] [--description <desc>] [--max-zoom <zoom>] [--min-zoom <zoom>] [--only-disk] (--attribution <text> | --attribution-file <file>)
205
+ partition --from-source <source> [--from-source <source> ...] --to-pmtiles <file> [--size-limit <limit>]
192
206
  ```
193
207
 
194
- - `--to-pmtiles-prefix`: Prefix for the output PMTiles files. (Required)
195
- - `--from-tiles-dir`: Directory containing the input tiles. (Required)
196
- - `--from-pmtiles-prefix`: Prefix for input PMTiles files (required if not using `--only-disk`).
197
- - `--name`: Name of the mosaic. (Required if using `--only-disk`)
198
- - `--description`: Description of the mosaic. (Required if using `--only-disk`)
199
- - `--max-zoom`: Maximum zoom level for the mosaic. (Defaults to the max zoom found in the source)
200
- - `--min-zoom`: Minimum zoom level for the mosaic (default: 0).
201
- - `--only-disk`: Only read tiles from the local disk.
202
- - `--attribution`: Attribution text for the mosaic. (Required if using `--only-disk`)
203
- - `--attribution-file`: File containing attribution text. (Required if using `--only-disk`)
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`.
204
218
 
205
219
  #### `retile`
206
220
 
@@ -211,31 +225,37 @@ The tool works in two stages. First, it calculates the full set of sheets that n
211
225
  In the second stage, it retiles the affected area. It uses the pull list to create a temporary virtual raster (`.vrt`), generates new base tiles for the affected region, and then reconstructs the upper-level tiles by pulling existing, unaffected tiles from the PMTiles source.
212
226
 
213
227
  ```bash
214
- retile --retile-list-file <file> --bounds-file <file> [--max-zoom <zoom>] [--min-zoom <zoom>] [--tiffs-dir <dir>] [--tiles-dir <dir>] [--from-pmtiles-prefix <prefix>] [--sheets-to-pull-list-outfile <file>] [--num-parallel <processes>]
228
+ retile --retile-list-file <file> --bounds-file <file> [--max-zoom <zoom>] [--tiffs-dir <dir>] [--tiles-dir <dir>] [--from-source <source>] [--sheets-to-pull-list-outfile <file>] [--num-parallel <processes>] [--tile-quality <quality>]
215
229
  ```
216
230
 
217
231
  - `--retile-list-file`: File containing the list of sheets to retile. (Required)
218
232
  - `--bounds-file`: GeoJSON file containing the list of available sheets and their geographic bounds. (Required)
219
- - `--max-zoom`: Maximum zoom level to create tiles for. (Defaults to the max zoom from the PMTiles source)
220
- - `--min-zoom`: Minimum zoom level to create tiles for. (Defaults to the min zoom from the PMTiles source)
233
+ - `--max-zoom`: Maximum zoom level to create tiles for. Defaults to the max zoom from the source if `--from-source` is provided, otherwise it is required.
221
234
  - `--tiffs-dir`: Directory where the GeoTIFFs are located. (Required if not in pull-list generation mode)
222
235
  - `--tiles-dir`: Directory where the tiles will be created. (Required if not in pull-list generation mode)
223
- - `--from-pmtiles-prefix`: Prefix for the PMTiles source from which to pull existing tiles. (Required if not in pull-list generation mode)
236
+ - `--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.
224
237
  - `--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.
225
238
  - `--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).
226
240
 
227
241
  #### `tile`
228
242
 
229
243
  Creates web map tiles from a directory of GeoTIFF files. It combines them into a virtual raster (`.vrt`) for efficient processing.
230
244
 
231
245
  ```bash
232
- tile --tiles-dir <dir> --tiffs-dir <dir> --max-zoom <zoom> [--min-zoom <zoom>] [--num-parallel <processes>]
246
+ tile --tiles-dir <dir> --tiffs-dir <dir> --max-zoom <zoom> --name <name> --description <desc> (--attribution <text> | --attribution-file <file>) [--min-zoom <zoom>] [--tile-extension <ext>] [--tile-quality <quality>] [--num-parallel <processes>]
233
247
  ```
234
248
 
235
- - `<tiles-dir>`: Directory to store the output tiles. (Required)
236
- - `<tiffs-dir>`: Directory containing the input GeoTIFF files. (Required)
237
- - `<max-zoom>`: Maximum zoom level for tiling. (Required)
238
- - `<min-zoom>`: Minimum zoom level for tiling (default: 0).
249
+ - `--tiles-dir`: Directory to store the output tiles. (Required)
250
+ - `--tiffs-dir`: Directory containing the input GeoTIFF files. (Required)
251
+ - `--max-zoom`: Maximum zoom level for tiling. (Required)
252
+ - `--name`: Name of the mosaic. (Required)
253
+ - `--description`: Description of the mosaic. (Required)
254
+ - `--attribution`: Attribution text for the mosaic. Required if `--attribution-file` is not used.
255
+ - `--attribution-file`: File containing attribution text for the mosaic. Required if `--attribution` is not used.
256
+ - `--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).
239
259
  - `--num-parallel`: Number of parallel processes to use for tiling (default: number of CPU cores).
240
260
 
241
261
  ### Running Tools Directly with `uvx`
@@ -10,6 +10,7 @@ authors = [
10
10
  dependencies = [
11
11
  "mercantile>=1.2.1",
12
12
  "pmtiles>=3.4.1",
13
+ "requests>=2.32.4",
13
14
  "shapely>=2.1.1",
14
15
  ]
15
16
  dynamic = [
@@ -37,6 +38,7 @@ repository = "https://github.com/ramSeraph/topo_map_processor.git"
37
38
 
38
39
  [project.scripts]
39
40
  collect-bounds = "topo_map_processor.tools.collect_bounds:cli"
41
+ download-mosaic = "topo_map_processor.tools.download_mosaic:cli"
40
42
  partition = "topo_map_processor.tools.partition:cli"
41
43
  retile = "topo_map_processor.tools.retile:cli"
42
44
  tile = "topo_map_processor.tools.tile:cli"
@@ -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.7'
21
- __version_tuple__ = version_tuple = (0, 1, 7)
20
+ __version__ = version = '0.1.8'
21
+ __version_tuple__ = version_tuple = (0, 1, 8)
@@ -1,10 +1,11 @@
1
1
  #!/bin/bash
2
2
 
3
+ prog_name=${COMMNAND_NAME:-$(basename "$0")}
3
4
  release_base=$1
4
5
  ext=$2
5
6
 
6
7
  if [ -z "$release_base" ] || [ -z "$ext" ]; then
7
- echo "Usage: $0 <release_base> <extension>"
8
+ echo "Usage: $prog_name <release_base> <extension>"
8
9
  exit 1
9
10
  fi
10
11
 
@@ -11,9 +11,10 @@ set -o pipefail
11
11
  # TAG: The git tag of the release to upload to.
12
12
  # FOLDER: The local folder containing the files to upload.
13
13
  # OVERWRITE: Optional. Set to 'yes' to allow overwriting existing assets.
14
+ prog_name=${COMMNAND_NAME:-$(basename "$0")}
14
15
 
15
16
  if [ "$#" -lt 3 ] || [ "$#" -gt 4 ]; then
16
- echo "Usage: $0 <tag> <folder> <extension_without_the_leading_dot> [yes_to_overwrite]"
17
+ echo "Usage: $prog_name <tag> <folder> <extension_without_the_leading_dot> [yes_to_overwrite]"
17
18
  exit 1
18
19
  fi
19
20
 
@@ -0,0 +1,254 @@
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
+
23
+ # heavily copied from https://github.com/protomaps/PMTiles/blob/main/python/pmtiles/convert.py
24
+ # and https://github.com/mapbox/mbutil/blob/master/mbutil/util.py
25
+
26
+ session = None
27
+ timeout = None
28
+
29
+ def download_file(url, fname):
30
+ global session, timeout
31
+ print(f'downloading {url} to {fname}')
32
+ resp = session.get(url, stream=True, timeout=timeout)
33
+ with open(fname, 'wb') as f:
34
+ for data in resp.iter_content(chunk_size=4096):
35
+ f.write(data)
36
+
37
+
38
+ def get_mosaic(mosaic_file, mosaic_url):
39
+ if not mosaic_file.exists():
40
+ download_file(mosaic_url, mosaic_file)
41
+
42
+ return json.loads(mosaic_file.read_text())
43
+
44
+
45
+ def get_metadata(mosaic_data):
46
+ ch = {}
47
+
48
+ cm = list(mosaic_data.values())[0]['metadata']
49
+ for k, item in mosaic_data.items():
50
+ h = item['header']
51
+ if 'max_zoom' not in ch or ch['max_zoom'] < h['max_zoom']:
52
+ ch['max_zoom'] = h['max_zoom']
53
+ if 'min_zoom' not in ch or ch['min_zoom'] > h['min_zoom']:
54
+ ch['min_zoom'] = h['min_zoom']
55
+ if 'max_lat_e7' not in ch or ch['max_lat_e7'] < h['max_lat_e7']:
56
+ ch['max_lat_e7'] = h['max_lat_e7']
57
+ if 'min_lat_e7' not in ch or ch['min_lat_e7'] > h['min_lat_e7']:
58
+ ch['min_lat_e7'] = h['min_lat_e7']
59
+ if 'max_lon_e7' not in ch or ch['max_lon_e7'] < h['max_lon_e7']:
60
+ ch['max_lon_e7'] = h['max_lon_e7']
61
+ if 'min_lon_e7' not in ch or ch['min_lon_e7'] > h['min_lon_e7']:
62
+ ch['min_lon_e7'] = h['min_lon_e7']
63
+
64
+ cm['maxzoom'] = ch['max_zoom']
65
+ cm['minzoom'] = ch['min_zoom']
66
+
67
+ max_lat = ch['max_lat_e7'] / 10000000
68
+ min_lat = ch['min_lat_e7'] / 10000000
69
+ max_lon = ch['max_lon_e7'] / 10000000
70
+ min_lon = ch['min_lon_e7'] / 10000000
71
+ cm['bounds'] = f"{min_lon},{min_lat},{max_lon},{max_lat}"
72
+
73
+ center_lat = (max_lat + min_lat) / 2
74
+ center_lon = (max_lon + min_lon) / 2
75
+ center_zoom = (ch['max_zoom'] + ch['min_zoom']) // 2
76
+ cm['center'] = f"{center_lon},{center_lat},{center_zoom}"
77
+
78
+ vector_layers = cm.get('vector_layers', [])
79
+ for layer in vector_layers:
80
+ layer['minzoom'] = cm['minzoom']
81
+ layer['maxzoom'] = cm['maxzoom']
82
+
83
+ return cm
84
+
85
+
86
+ def finalize_mbtiles(conn, cursor):
87
+ cursor.execute(
88
+ "CREATE UNIQUE INDEX tile_index on tiles (zoom_level, tile_column, tile_row);"
89
+ )
90
+ conn.commit()
91
+ cursor.execute("""ANALYZE;""")
92
+
93
+ conn.close()
94
+
95
+
96
+ def add_to_mbtiles(pmtiles_fname, cursor, conn):
97
+ print(f'adding {pmtiles_fname} to mbtiles')
98
+ with open(pmtiles_fname, "r+b") as f:
99
+ source = MmapSource(f)
100
+ reader = Reader(source)
101
+
102
+ for zxy, tile_data in all_tiles(reader.get_bytes):
103
+ flipped_y = (1 << zxy[0]) - 1 - zxy[2]
104
+ cursor.execute(
105
+ "INSERT INTO tiles VALUES(?,?,?,?)",
106
+ (zxy[0], zxy[1], flipped_y, tile_data),
107
+ )
108
+ conn.commit()
109
+
110
+
111
+ def optimize_cursor(cursor):
112
+ cursor.execute("""PRAGMA synchronous=0""")
113
+ cursor.execute("""PRAGMA locking_mode=EXCLUSIVE""")
114
+ cursor.execute("""PRAGMA journal_mode=DELETE""")
115
+
116
+
117
+ def initialize_tables(cursor, metadata):
118
+ cursor.execute("CREATE TABLE metadata (name text, value text);")
119
+ cursor.execute(
120
+ "CREATE TABLE tiles (zoom_level integer, tile_column integer, tile_row integer, tile_data blob);"
121
+ )
122
+
123
+ json_metadata = {}
124
+ for k, v in metadata.items():
125
+ if k == "vector_layers":
126
+ json_metadata["vector_layers"] = v
127
+ continue
128
+ elif k == "tilestats":
129
+ json_metadata["tilestats"] = v
130
+ continue
131
+ elif not isinstance(v, str):
132
+ v = json.dumps(v, ensure_ascii=False)
133
+ cursor.execute("INSERT INTO metadata VALUES(?,?)", (k, v))
134
+
135
+ if len(json_metadata) > 0:
136
+ cursor.execute(
137
+ "INSERT INTO metadata VALUES(?,?)",
138
+ ("json", json.dumps(json_metadata, ensure_ascii=False)),
139
+ )
140
+
141
+
142
+
143
+ def get_mbtiles_conn(output_file):
144
+ conn = sqlite3.connect(output_file)
145
+ cursor = conn.cursor()
146
+ optimize_cursor(cursor)
147
+ return conn, cursor
148
+
149
+
150
+ def get_pmtiles_url(mosaic_url, k):
151
+ # undo prioir ididocy
152
+ if k.startswith('../') and mosaic_url.startswith('https://github.com/ramSeraph/'):
153
+ k = k[3:]
154
+ return urljoin(mosaic_url, k)
155
+
156
+
157
+ def init_tracker(tracker_file):
158
+ if not tracker_file.exists():
159
+ tracker_file.write_text("")
160
+
161
+ def stage_done(tracker_file, stage):
162
+ txt = tracker_file.read_text()
163
+
164
+ done_stages = txt.split('\n')
165
+
166
+ if stage in done_stages:
167
+ return True
168
+
169
+ return False
170
+
171
+
172
+ def mark_done(tracker_file, stage):
173
+ with open(tracker_file, 'a') as f:
174
+ f.write(stage)
175
+ f.write('\n')
176
+
177
+ def get_filename_from_url(url):
178
+ url_parsed = urlparse(url)
179
+ return unquote(PurePosixPath(url_parsed.path).name)
180
+
181
+ def cli():
182
+ import argparse
183
+ parser = argparse.ArgumentParser(description='Download a mosaic and convert it to MBTiles format.')
184
+ parser.add_argument('--mosaic_url', '-u', required=True, type=str, help='URL of the mosaic JSON file')
185
+ parser.add_argument('--output_file', '-o', type=str, help='Output MBTiles file name, if not specified is derived from the mosaic URL')
186
+ parser.add_argument('--request-timeout-secs', '-t', type=int, default=60, help='Timeout for HTTP requests in seconds')
187
+ parser.add_argument('--num-http-retries', '-r', type=int, default=3, help='Number of retries for HTTP requests')
188
+ args = parser.parse_args()
189
+
190
+ mosaic_url = args.mosaic_url
191
+
192
+ mosaic_fname = get_filename_from_url(mosaic_url)
193
+
194
+ mosaic_file = Path(mosaic_fname)
195
+ mosaic_data = get_mosaic(mosaic_file, mosaic_url)
196
+ metadata = get_metadata(mosaic_data)
197
+
198
+ if args.output_file:
199
+ out_mbtiles_file = Path(args.output_file)
200
+ else:
201
+ if not mosaic_fname.endswith('.mosaic.json'):
202
+ raise ValueError('Output file must end with .mosaic.json, if the output_file is not specified.')
203
+
204
+ out_mbtiles_fname = mosaic_fname[:-len('.mosaic.json')] + '.mbtiles'
205
+ out_mbtiles_file = Path(out_mbtiles_fname)
206
+
207
+ tracker_file = Path('tracker.txt')
208
+
209
+ if out_mbtiles_file.exists() and not tracker_file.exists():
210
+ print(f'Output file {out_mbtiles_file} already exists, and no tracker file found. Exiting to avoid overwriting.')
211
+ return
212
+
213
+ conn, cursor = get_mbtiles_conn(out_mbtiles_file)
214
+
215
+ init_tracker(tracker_file)
216
+
217
+ print(f'Output MBTiles file: {out_mbtiles_file}')
218
+
219
+ if not stage_done(tracker_file, 'table_init'):
220
+ print('Initializing tables...')
221
+ initialize_tables(cursor, metadata)
222
+ mark_done(tracker_file, 'table_init')
223
+
224
+ global session, timeout
225
+ session = requests.session()
226
+ retries = args.num_http_retries
227
+ retry = Retry(
228
+ total=retries,
229
+ read=retries,
230
+ connect=retries,
231
+ )
232
+ session.mount('http://', HTTPAdapter(max_retries=retry))
233
+ session.mount('https://', HTTPAdapter(max_retries=retry))
234
+ timeout = args.request_timeout_secs
235
+
236
+ for k in mosaic_data.keys():
237
+ pmtiles_url = get_pmtiles_url(mosaic_url, k)
238
+ if stage_done(tracker_file, k):
239
+ continue
240
+
241
+ pmtiles_fname = get_filename_from_url(pmtiles_url)
242
+ download_file(pmtiles_url, pmtiles_fname)
243
+ add_to_mbtiles(pmtiles_fname, cursor, conn)
244
+ mark_done(tracker_file, k)
245
+ Path(pmtiles_fname).unlink()
246
+
247
+ finalize_mbtiles(conn, cursor)
248
+ tracker_file.unlink()
249
+ mosaic_file.unlink()
250
+ print('Done!!!')
251
+
252
+
253
+ if __name__ == '__main__':
254
+ cli()
@@ -1,6 +1,7 @@
1
- import subprocess
1
+ import os
2
2
  import sys
3
3
  import shutil
4
+ import subprocess
4
5
  import importlib.resources as resources
5
6
 
6
7
  def execute_bash_script(script_name):
@@ -18,6 +19,8 @@ def execute_bash_script(script_name):
18
19
  except FileNotFoundError:
19
20
  raise FileNotFoundError(f"The script {script_name} does not exist or is not a file. Searched in package data.")
20
21
 
22
+ command_name = sys.argv[0]
23
+ os.environ["COMMAND_NAME"] = command_name
21
24
  # Execute the bash script and stream output, merging stderr into stdout
22
25
  process = subprocess.Popen(
23
26
  [shutil.which("bash"), str(script_path)] + sys.argv[1:],