topo_map_processor 0.1.5__tar.gz → 0.1.7__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 (24) hide show
  1. {topo_map_processor-0.1.5 → topo_map_processor-0.1.7}/PKG-INFO +34 -20
  2. {topo_map_processor-0.1.5 → topo_map_processor-0.1.7}/README.md +23 -11
  3. {topo_map_processor-0.1.5 → topo_map_processor-0.1.7}/pyproject.toml +27 -10
  4. {topo_map_processor-0.1.5 → topo_map_processor-0.1.7}/topo_map_processor/_version.py +2 -2
  5. topo_map_processor-0.1.7/topo_map_processor/bash_scripts/__init__.py +0 -0
  6. topo_map_processor-0.1.7/topo_map_processor/bash_scripts/generate_lists.sh +32 -0
  7. topo_map_processor-0.1.7/topo_map_processor/bash_scripts/upload_to_release.sh +148 -0
  8. topo_map_processor-0.1.5/topo_map_processor/__init__.py → topo_map_processor-0.1.7/topo_map_processor/processor.py +11 -10
  9. topo_map_processor-0.1.7/topo_map_processor/tools/__init__.py +0 -0
  10. topo_map_processor-0.1.7/topo_map_processor/tools/execute_bash.py +54 -0
  11. {topo_map_processor-0.1.5 → topo_map_processor-0.1.7}/topo_map_processor/tools/partition.py +1 -1
  12. {topo_map_processor-0.1.5 → topo_map_processor-0.1.7}/topo_map_processor/tools/retile.py +90 -52
  13. {topo_map_processor-0.1.5 → topo_map_processor-0.1.7}/topo_map_processor/tools/tile.py +28 -16
  14. {topo_map_processor-0.1.5 → topo_map_processor-0.1.7}/topo_map_processor/tools/tile_sources.py +52 -6
  15. topo_map_processor-0.1.5/.github/workflows/publish.yml +0 -56
  16. topo_map_processor-0.1.5/.python-version +0 -1
  17. topo_map_processor-0.1.5/samples/corner1.png +0 -0
  18. topo_map_processor-0.1.5/samples/corner2.png +0 -0
  19. topo_map_processor-0.1.5/samples/corner3.png +0 -0
  20. topo_map_processor-0.1.5/uv.lock +0 -940
  21. {topo_map_processor-0.1.5 → topo_map_processor-0.1.7}/.gitignore +0 -0
  22. {topo_map_processor-0.1.5 → topo_map_processor-0.1.7}/LICENSE +0 -0
  23. {topo_map_processor-0.1.5/topo_map_processor/tools → topo_map_processor-0.1.7/topo_map_processor}/__init__.py +0 -0
  24. {topo_map_processor-0.1.5 → topo_map_processor-0.1.7}/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.5
3
+ Version: 0.1.7
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
@@ -31,17 +31,19 @@ License: This is free and unencumbered software released into the public domain.
31
31
  For more information, please refer to <https://unlicense.org>
32
32
  License-File: LICENSE
33
33
  Requires-Python: >=3.12
34
- Requires-Dist: easyocr>=1.7.2
35
- Requires-Dist: imgcat>=0.6.0
36
34
  Requires-Dist: mercantile>=1.2.1
37
- Requires-Dist: numpy>=2.3.1
38
- Requires-Dist: opencv-python-headless>=4.11.0.86
39
- Requires-Dist: pillow>=11.3.0
40
35
  Requires-Dist: pmtiles>=3.4.1
41
- Requires-Dist: pyproj>=3.7.1
42
- Requires-Dist: pytesseract>=0.3.13
43
- Requires-Dist: rasterio>=1.4.3
44
36
  Requires-Dist: shapely>=2.1.1
37
+ Provides-Extra: easyocr
38
+ Requires-Dist: easyocr>=1.7.2; extra == 'easyocr'
39
+ Provides-Extra: parse
40
+ Requires-Dist: imgcat>=0.6.0; extra == 'parse'
41
+ Requires-Dist: numpy>=2.3.1; extra == 'parse'
42
+ Requires-Dist: opencv-python-headless>=4.11.0.86; extra == 'parse'
43
+ Requires-Dist: pillow>=11.3.0; extra == 'parse'
44
+ Requires-Dist: pyproj>=3.7.1; extra == 'parse'
45
+ Requires-Dist: pytesseract>=0.3.13; extra == 'parse'
46
+ Requires-Dist: rasterio>=1.4.3; extra == 'parse'
45
47
  Description-Content-Type: text/markdown
46
48
 
47
49
  # Topo Map Processor [![PyPI - Latest Version](https://img.shields.io/pypi/v/topo_map_processor)](https://pypi.org/project/topo_map_processor/) [![GitHub Tag](https://img.shields.io/github/v/tag/ramSeraph/topo_map_processor?filter=v*)](https://github.com/ramSeraph/topo_map_processor/releases/latest)
@@ -51,7 +53,7 @@ A Python utility for processing topographic maps.
51
53
 
52
54
  ## Description
53
55
 
54
- This project provides a collection of command-line tools and a Python library to process, analyze, and manipulate topographic map data, specifically for creating web-mappable tiles from GeoTIFF files. The tools allow you to create tiles, partition large tile sets, and update existing tile sets efficiently.
56
+ This project provides a collection of command-line tools and a Python library to process, georeference, and export topographic map data in raster form, specifically for creating web-mappable tiles from GeoTIFF files. The tools allow you to create tiles, partition large tile sets, and update existing tile sets efficiently.
55
57
 
56
58
  ## Installation
57
59
 
@@ -106,7 +108,7 @@ The main steps, executed by the `processor.process()` method, are as follows:
106
108
  1. **Image Preparation (`rotate`):** To minimize computation, the processor first creates a smaller, shrunken version of the full-resolution map. On this small image, it locates the main contour of the map's frame (the border area). By calculating the angle of this contour's minimum bounding box, it determines the skew of the map. The full-resolution image is then rotated using this angle to make the frame perfectly horizontal, correcting for any tilt introduced during scanning.
107
109
  2. **Corner Detection (`get_corners`):** Once the image is straightened, the processor uses the same map frame contour to identify the four corner regions. By narrowing the search to these smaller areas, it can apply more precise logic to find the exact pixel coordinates of the map's neatline corners. This is a critical step that often requires custom implementation in a subclass (`get_intersection_point`) to handle the specific visual features of the map's corners (e.g., crosses, specific colors, patterns).
108
110
  3. **Grid Line Removal (`remove_grid_lines`):** If configured, the processor can detect and remove grid lines (graticules) from the map image. This is achieved by implementing the `locate_grid_lines` method. The removal helps create a cleaner final map by inpainting the areas where the lines were.
109
- 4. **Georeferencing (`georeference`):** Using the detected corner coordinates and the corresponding real-world geographic coordinates (provided via the `index_map`), the tool creates Ground Control Points (GCPs). It then uses `gdal_translate` to create an initial georeferenced GeoTIFF.
111
+ 4. **Georeferencing (`georeference`):** Using the detected corner coordinates and the corresponding real-world geographic coordinates (provided via the `index_box`), the tool creates Ground Control Points (GCPs). It then uses `gdal_translate` to create an initial georeferenced GeoTIFF.
110
112
  5. **Warping and Cutting (`warp`):** The georeferenced image is then warped into the standard `EPSG:3857` projection used by most web maps. During this step, it's also precisely cropped to the map's boundary (neatline) using a cutline derived from the corner points. The output is a clean, map-only image, correctly projected.
111
113
  6. **Exporting (`export`):** The final warped image is converted into a Cloud-Optimized GeoTIFF (COG), which is highly efficient for web-based tiling. A corresponding bounds file (`.geojsonl`) is also created, defining the geographic extent of the map sheet.
112
114
 
@@ -116,15 +118,15 @@ The output of this library workflow—a directory of COG `.tif` files and a dire
116
118
 
117
119
  The `TopoMapProcessor` class provides a framework for processing individual map sheets. To use it, you need to create a subclass and implement the required methods.
118
120
 
119
- Here's an example of how to use the `TopoMapProcessor` class, based on the provided `parse_jica.py` script:
121
+ Here's an example of how to use the `TopoMapProcessor` class.
120
122
 
121
123
  ```python
122
- from topo_map_processor import TopoMapProcessor, LineRemovalParams
124
+ from topo_map_processor.processor import TopoMapProcessor, LineRemovalParams
123
125
 
124
126
  class SampleProcessor(TopoMapProcessor):
125
127
 
126
- def __init__(self, filepath, extra, index_map):
127
- super().__init__(filepath, extra, index_map)
128
+ def __init__(self, filepath, extra, index_box, index_props):
129
+ super().__init__(filepath, extra, index_box, index_props)
128
130
  # ... (additional initialization)
129
131
 
130
132
  def get_inter_dir(self):
@@ -155,14 +157,16 @@ def process_files():
155
157
  # ... (load image files and special cases)
156
158
  for filepath in image_files:
157
159
  extra = special_cases.get(filepath.name, {})
158
- index_map = get_index_map_jica()
159
- processor = SampleProcessor(filepath, extra, index_map)
160
+ index_box, index_props = get_index_data(filepath.name) # This should return the corresponding index box and properties for the file
161
+ processor = SampleProcessor(filepath, extra, index_box, index_props)
160
162
  processor.process()
161
163
  ```
162
164
 
163
165
  The library is configurable and most parameters can be overridden at a per sheet level using the `extra` dictionary.
164
166
 
165
- The `index_map` is used to map specific file names to their corresponding metadata, which can be useful for processing specific datasets. In particular the index_map is expected to contain the geojson geometry of the map sheet in the `geometry` key, which is used to create the bounds file and georeference the sheet. The geometry is expected to be in counter clockwise order starting at the top left corner of the map sheet.
167
+ The `index_box` is used to provide the coordinates of the corners of the sheet. This information is used to create the bounds file and georeference the sheet. This is expected to be a list of coordinates in counter clockwise order starting at the top left corner of the map sheet.
168
+
169
+ The optional `index_properties` dictionary can contain additional metadata about the sheet, such as its name, description, and other properties which end up in the generated bounds files.
166
170
 
167
171
  The end result is a Cloud optimized GeoTIFF file, a bounds file in GeoJSON format from which the tiles can be generated, using the `tile` command-line tool, and a set of tiles in the specified directory.
168
172
 
@@ -203,6 +207,14 @@ Similar to corner detection, the `locate_grid_lines` method can be implemented u
203
207
 
204
208
  - **`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.
205
209
 
210
+ ### Tile Sources
211
+
212
+ The `topo_map_processor.tools.tile_sources` module provides classes for reading tiles from different sources.
213
+
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.
217
+
206
218
  ### Command-Line Tools
207
219
 
208
220
  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.
@@ -247,7 +259,7 @@ The tool works in two stages. First, it calculates the full set of sheets that n
247
259
  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.
248
260
 
249
261
  ```bash
250
- 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>]
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>]
251
263
  ```
252
264
 
253
265
  - `--retile-list-file`: File containing the list of sheets to retile. (Required)
@@ -258,19 +270,21 @@ retile --retile-list-file <file> --bounds-file <file> [--max-zoom <zoom>] [--min
258
270
  - `--tiles-dir`: Directory where the tiles will be created. (Required if not in pull-list generation mode)
259
271
  - `--from-pmtiles-prefix`: Prefix for the PMTiles source from which to pull existing tiles. (Required if not in pull-list generation mode)
260
272
  - `--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
+ - `--num-parallel`: Number of parallel processes to use for tiling (default: number of CPU cores).
261
274
 
262
275
  #### `tile`
263
276
 
264
277
  Creates web map tiles from a directory of GeoTIFF files. It combines them into a virtual raster (`.vrt`) for efficient processing.
265
278
 
266
279
  ```bash
267
- tile <tiles-dir> <tiffs-dir> <max-zoom> [<min-zoom>]
280
+ tile --tiles-dir <dir> --tiffs-dir <dir> --max-zoom <zoom> [--min-zoom <zoom>] [--num-parallel <processes>]
268
281
  ```
269
282
 
270
283
  - `<tiles-dir>`: Directory to store the output tiles. (Required)
271
284
  - `<tiffs-dir>`: Directory containing the input GeoTIFF files. (Required)
272
285
  - `<max-zoom>`: Maximum zoom level for tiling. (Required)
273
286
  - `<min-zoom>`: Minimum zoom level for tiling (default: 0).
287
+ - `--num-parallel`: Number of parallel processes to use for tiling (default: number of CPU cores).
274
288
 
275
289
  ### Running Tools Directly with `uvx`
276
290
 
@@ -5,7 +5,7 @@ A Python utility for processing topographic maps.
5
5
 
6
6
  ## Description
7
7
 
8
- This project provides a collection of command-line tools and a Python library to process, analyze, and manipulate topographic map data, specifically for creating web-mappable tiles from GeoTIFF files. The tools allow you to create tiles, partition large tile sets, and update existing tile sets efficiently.
8
+ This project provides a collection of command-line tools and a Python library to process, georeference, and export topographic map data in raster form, specifically for creating web-mappable tiles from GeoTIFF files. The tools allow you to create tiles, partition large tile sets, and update existing tile sets efficiently.
9
9
 
10
10
  ## Installation
11
11
 
@@ -60,7 +60,7 @@ The main steps, executed by the `processor.process()` method, are as follows:
60
60
  1. **Image Preparation (`rotate`):** To minimize computation, the processor first creates a smaller, shrunken version of the full-resolution map. On this small image, it locates the main contour of the map's frame (the border area). By calculating the angle of this contour's minimum bounding box, it determines the skew of the map. The full-resolution image is then rotated using this angle to make the frame perfectly horizontal, correcting for any tilt introduced during scanning.
61
61
  2. **Corner Detection (`get_corners`):** Once the image is straightened, the processor uses the same map frame contour to identify the four corner regions. By narrowing the search to these smaller areas, it can apply more precise logic to find the exact pixel coordinates of the map's neatline corners. This is a critical step that often requires custom implementation in a subclass (`get_intersection_point`) to handle the specific visual features of the map's corners (e.g., crosses, specific colors, patterns).
62
62
  3. **Grid Line Removal (`remove_grid_lines`):** If configured, the processor can detect and remove grid lines (graticules) from the map image. This is achieved by implementing the `locate_grid_lines` method. The removal helps create a cleaner final map by inpainting the areas where the lines were.
63
- 4. **Georeferencing (`georeference`):** Using the detected corner coordinates and the corresponding real-world geographic coordinates (provided via the `index_map`), the tool creates Ground Control Points (GCPs). It then uses `gdal_translate` to create an initial georeferenced GeoTIFF.
63
+ 4. **Georeferencing (`georeference`):** Using the detected corner coordinates and the corresponding real-world geographic coordinates (provided via the `index_box`), the tool creates Ground Control Points (GCPs). It then uses `gdal_translate` to create an initial georeferenced GeoTIFF.
64
64
  5. **Warping and Cutting (`warp`):** The georeferenced image is then warped into the standard `EPSG:3857` projection used by most web maps. During this step, it's also precisely cropped to the map's boundary (neatline) using a cutline derived from the corner points. The output is a clean, map-only image, correctly projected.
65
65
  6. **Exporting (`export`):** The final warped image is converted into a Cloud-Optimized GeoTIFF (COG), which is highly efficient for web-based tiling. A corresponding bounds file (`.geojsonl`) is also created, defining the geographic extent of the map sheet.
66
66
 
@@ -70,15 +70,15 @@ The output of this library workflow—a directory of COG `.tif` files and a dire
70
70
 
71
71
  The `TopoMapProcessor` class provides a framework for processing individual map sheets. To use it, you need to create a subclass and implement the required methods.
72
72
 
73
- Here's an example of how to use the `TopoMapProcessor` class, based on the provided `parse_jica.py` script:
73
+ Here's an example of how to use the `TopoMapProcessor` class.
74
74
 
75
75
  ```python
76
- from topo_map_processor import TopoMapProcessor, LineRemovalParams
76
+ from topo_map_processor.processor import TopoMapProcessor, LineRemovalParams
77
77
 
78
78
  class SampleProcessor(TopoMapProcessor):
79
79
 
80
- def __init__(self, filepath, extra, index_map):
81
- super().__init__(filepath, extra, index_map)
80
+ def __init__(self, filepath, extra, index_box, index_props):
81
+ super().__init__(filepath, extra, index_box, index_props)
82
82
  # ... (additional initialization)
83
83
 
84
84
  def get_inter_dir(self):
@@ -109,14 +109,16 @@ def process_files():
109
109
  # ... (load image files and special cases)
110
110
  for filepath in image_files:
111
111
  extra = special_cases.get(filepath.name, {})
112
- index_map = get_index_map_jica()
113
- processor = SampleProcessor(filepath, extra, index_map)
112
+ index_box, index_props = get_index_data(filepath.name) # This should return the corresponding index box and properties for the file
113
+ processor = SampleProcessor(filepath, extra, index_box, index_props)
114
114
  processor.process()
115
115
  ```
116
116
 
117
117
  The library is configurable and most parameters can be overridden at a per sheet level using the `extra` dictionary.
118
118
 
119
- The `index_map` is used to map specific file names to their corresponding metadata, which can be useful for processing specific datasets. In particular the index_map is expected to contain the geojson geometry of the map sheet in the `geometry` key, which is used to create the bounds file and georeference the sheet. The geometry is expected to be in counter clockwise order starting at the top left corner of the map sheet.
119
+ The `index_box` is used to provide the coordinates of the corners of the sheet. This information is used to create the bounds file and georeference the sheet. This is expected to be a list of coordinates in counter clockwise order starting at the top left corner of the map sheet.
120
+
121
+ The optional `index_properties` dictionary can contain additional metadata about the sheet, such as its name, description, and other properties which end up in the generated bounds files.
120
122
 
121
123
  The end result is a Cloud optimized GeoTIFF file, a bounds file in GeoJSON format from which the tiles can be generated, using the `tile` command-line tool, and a set of tiles in the specified directory.
122
124
 
@@ -157,6 +159,14 @@ Similar to corner detection, the `locate_grid_lines` method can be implemented u
157
159
 
158
160
  - **`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.
159
161
 
162
+ ### Tile Sources
163
+
164
+ The `topo_map_processor.tools.tile_sources` module provides classes for reading tiles from different sources.
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.
169
+
160
170
  ### Command-Line Tools
161
171
 
162
172
  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.
@@ -201,7 +211,7 @@ The tool works in two stages. First, it calculates the full set of sheets that n
201
211
  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.
202
212
 
203
213
  ```bash
204
- 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>]
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>]
205
215
  ```
206
216
 
207
217
  - `--retile-list-file`: File containing the list of sheets to retile. (Required)
@@ -212,19 +222,21 @@ retile --retile-list-file <file> --bounds-file <file> [--max-zoom <zoom>] [--min
212
222
  - `--tiles-dir`: Directory where the tiles will be created. (Required if not in pull-list generation mode)
213
223
  - `--from-pmtiles-prefix`: Prefix for the PMTiles source from which to pull existing tiles. (Required if not in pull-list generation mode)
214
224
  - `--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
+ - `--num-parallel`: Number of parallel processes to use for tiling (default: number of CPU cores).
215
226
 
216
227
  #### `tile`
217
228
 
218
229
  Creates web map tiles from a directory of GeoTIFF files. It combines them into a virtual raster (`.vrt`) for efficient processing.
219
230
 
220
231
  ```bash
221
- tile <tiles-dir> <tiffs-dir> <max-zoom> [<min-zoom>]
232
+ tile --tiles-dir <dir> --tiffs-dir <dir> --max-zoom <zoom> [--min-zoom <zoom>] [--num-parallel <processes>]
222
233
  ```
223
234
 
224
235
  - `<tiles-dir>`: Directory to store the output tiles. (Required)
225
236
  - `<tiffs-dir>`: Directory containing the input GeoTIFF files. (Required)
226
237
  - `<max-zoom>`: Maximum zoom level for tiling. (Required)
227
238
  - `<min-zoom>`: Minimum zoom level for tiling (default: 0).
239
+ - `--num-parallel`: Number of parallel processes to use for tiling (default: number of CPU cores).
228
240
 
229
241
  ### Running Tools Directly with `uvx`
230
242
 
@@ -8,22 +8,28 @@ authors = [
8
8
  { name = "Sreeram Kandimalla", email = "kandimalla.sreeram@gmail.com" },
9
9
  ]
10
10
  dependencies = [
11
- "easyocr>=1.7.2",
12
- "imgcat>=0.6.0",
13
11
  "mercantile>=1.2.1",
14
- "numpy>=2.3.1",
15
- "opencv-python-headless>=4.11.0.86",
16
- "pillow>=11.3.0",
17
12
  "pmtiles>=3.4.1",
18
- "pyproj>=3.7.1",
19
- "pytesseract>=0.3.13",
20
- "rasterio>=1.4.3",
21
13
  "shapely>=2.1.1",
22
14
  ]
23
15
  dynamic = [
24
16
  "version"
25
17
  ]
26
18
 
19
+ [project.optional-dependencies]
20
+ easyocr = [
21
+ "easyocr>=1.7.2",
22
+ ]
23
+ parse = [
24
+ "numpy>=2.3.1",
25
+ "pillow>=11.3.0",
26
+ "imgcat>=0.6.0",
27
+ "opencv-python-headless>=4.11.0.86",
28
+ "pyproj>=3.7.1",
29
+ "pytesseract>=0.3.13",
30
+ "rasterio>=1.4.3",
31
+ ]
32
+
27
33
  [project.urls]
28
34
  homepage = "https://github.com/ramSeraph/topo_map_processor"
29
35
  repository = "https://github.com/ramSeraph/topo_map_processor.git"
@@ -34,6 +40,8 @@ collect-bounds = "topo_map_processor.tools.collect_bounds:cli"
34
40
  partition = "topo_map_processor.tools.partition:cli"
35
41
  retile = "topo_map_processor.tools.retile:cli"
36
42
  tile = "topo_map_processor.tools.tile:cli"
43
+ generate-lists = "topo_map_processor.tools.execute_bash:generate_lists"
44
+ upload-to-release = "topo_map_processor.tools.execute_bash:upload_to_release"
37
45
 
38
46
 
39
47
  [build-system]
@@ -49,6 +57,15 @@ source = "vcs"
49
57
  [tool.hatch.build.hooks.vcs]
50
58
  version-file = "topo_map_processor/_version.py"
51
59
 
52
- tool.hatch.build.targets.sdist.include = [
53
- "topo_map_processor"
60
+ [tool.hatch.build.targets.sdist]
61
+ include = [
62
+ "topo_map_processor",
63
+ "topo_map_processor/bash_scripts/**",
54
64
  ]
65
+
66
+ [tool.hatch.build.targets.wheel]
67
+ include = [
68
+ "topo_map_processor",
69
+ "topo_map_processor/bash_scripts/**",
70
+ ]
71
+
@@ -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.5'
21
- __version_tuple__ = version_tuple = (0, 1, 5)
20
+ __version__ = version = '0.1.7'
21
+ __version_tuple__ = version_tuple = (0, 1, 7)
@@ -0,0 +1,32 @@
1
+ #!/bin/bash
2
+
3
+ release_base=$1
4
+ ext=$2
5
+
6
+ if [ -z "$release_base" ] || [ -z "$ext" ]; then
7
+ echo "Usage: $0 <release_base> <extension>"
8
+ exit 1
9
+ fi
10
+
11
+ #owner=$(gh repo view --json owner -q .owner.login)
12
+ #name=$(gh repo view --json name -q .name)
13
+ #repo="$owner/$name"
14
+
15
+ echo "getting file list"
16
+ echo "name,size,url" > listing_files.csv
17
+
18
+ # Get all releases, then filter for the base release and any -extra releases.
19
+ releases_to_process=$(gh release list --repo "$repo" --json tagName -q '.[].tagName' | grep -E "^${release_base}(-extra[0-9]+)?$")
20
+ releases_to_process=$(echo "$releases_to_process" | sort -u)
21
+
22
+ echo "Will process releases: $releases_to_process"
23
+
24
+ for release in $releases_to_process; do
25
+ echo "Processing release: $release"
26
+ gh release view "$release" --json assets -q ".assets[] | select(.name | endswith(\"$ext\")) | \"\\(.name),\\(.size),\\(.url)\"" >> listing_files.csv
27
+ done
28
+
29
+ gh release upload "$release_base" listing_files.csv --clobber
30
+
31
+ rm listing_files.csv
32
+
@@ -0,0 +1,148 @@
1
+ #!/bin/bash
2
+
3
+ # A script to upload files from a folder to a GitHub release,
4
+ # skipping files that already exist in the release.
5
+ # If the fourth argument is 'yes', it will overwrite existing assets.
6
+
7
+ set -e
8
+ set -o pipefail
9
+
10
+ # --- Configuration ---
11
+ # TAG: The git tag of the release to upload to.
12
+ # FOLDER: The local folder containing the files to upload.
13
+ # OVERWRITE: Optional. Set to 'yes' to allow overwriting existing assets.
14
+
15
+ if [ "$#" -lt 3 ] || [ "$#" -gt 4 ]; then
16
+ echo "Usage: $0 <tag> <folder> <extension_without_the_leading_dot> [yes_to_overwrite]"
17
+ exit 1
18
+ fi
19
+
20
+ TAG="$1"
21
+ FOLDER="$2"
22
+ EXT="$3"
23
+ OVERWRITE_FLAG=${4:-no}
24
+
25
+ # --- Pre-flight Checks ---
26
+
27
+ # Check if gh is installed
28
+ if ! command -v gh &> /dev/null; then
29
+ echo "Error: gh command-line tool is not installed. Please install it to continue."
30
+ exit 1
31
+ fi
32
+
33
+ if [ -z "$TAG" ]; then
34
+ echo "Error: Release tag is not specified."
35
+ exit 1
36
+ fi
37
+
38
+ if [ ! -d "$FOLDER" ]; then
39
+ echo "Error: Folder '$FOLDER' not found."
40
+ exit 1
41
+ fi
42
+
43
+ if [ -z "$EXT" ]; then
44
+ echo "Error: Extension '$EXT' is not specified."
45
+ exit 1
46
+ fi
47
+
48
+ ALLOW_OVERWRITE=0
49
+ if [ "$OVERWRITE_FLAG" == "yes" ]; then
50
+ ALLOW_OVERWRITE=1
51
+ echo "Overwrite flag is set. Existing assets will be overwritten."
52
+ fi
53
+
54
+ # --- Main Logic ---
55
+
56
+ echo "Fetching existing assets for releases matching pattern '${TAG}(-extra[0-9]+)?'..."
57
+
58
+ # Get all releases, then filter for the base release and any -extra releases, and sort them
59
+ RELEASES_TO_PROCESS=$(gh release list --json tagName -q '.[].tagName' | grep -E "^${TAG}(-extra[0-9]+)?$" | sort -V)
60
+
61
+ if [ -z "$RELEASES_TO_PROCESS" ]; then
62
+ echo "Error: No releases found matching pattern '${TAG}(-extra[0-9]+)?'." >&2
63
+ exit 1
64
+ fi
65
+
66
+ echo "Found releases: $RELEASES_TO_PROCESS"
67
+
68
+ # Store assets with their release tags. Format: "asset_name release_tag"
69
+ EXISTING_ASSETS_WITH_RELEASES=""
70
+ for release in $RELEASES_TO_PROCESS; do
71
+ echo "Fetching assets from release: $release"
72
+ assets=$(gh release view "$release" --json assets -q '.assets[].name' 2>/dev/null || echo "")
73
+ if [ -n "$assets" ]; then
74
+ while IFS= read -r asset_name; do
75
+ if [ -n "$asset_name" ]; then
76
+ EXISTING_ASSETS_WITH_RELEASES="${EXISTING_ASSETS_WITH_RELEASES}${asset_name} ${release}"$'
77
+ '
78
+ fi
79
+ done <<< "$assets"
80
+ fi
81
+ done
82
+
83
+ if [ -z "$EXISTING_ASSETS_WITH_RELEASES" ]; then
84
+ echo "Could not fetch any assets. This could mean the releases do not have assets, or you lack permissions."
85
+ fi
86
+
87
+ # Determine available releases and their current asset counts
88
+ AVAILABLE_RELEASES=()
89
+ AVAILABLE_ASSET_COUNTS=()
90
+ for release in $RELEASES_TO_PROCESS; do
91
+ count=$(gh release view "$release" --json assets -q '.assets | length' 2>/dev/null || echo "0")
92
+
93
+ MAX_ASSETS=998
94
+ if [ "$release" == "$TAG" ]; then
95
+ MAX_ASSETS=988
96
+ fi
97
+
98
+ if [ "$count" -lt "$MAX_ASSETS" ]; then
99
+ AVAILABLE_RELEASES+=("$release")
100
+ AVAILABLE_ASSET_COUNTS+=("$count")
101
+ fi
102
+ done
103
+
104
+ echo "Starting upload process from folder '$FOLDER'..."
105
+
106
+ # Find all files in the folder that are not yet in any release and loop through them.
107
+ find "${FOLDER}" -type f | grep "^.*\.${EXT}$" | while read -r FILE_PATH; do
108
+ FILENAME=$(basename "$FILE_PATH")
109
+
110
+ RELEASE_FOR_ASSET=$(echo -n "$EXISTING_ASSETS_WITH_RELEASES" | grep "^${FILENAME} " | head -n 1 | awk '{print $2}')
111
+
112
+ if [ -n "$RELEASE_FOR_ASSET" ]; then
113
+ # Asset exists
114
+ if [ "$ALLOW_OVERWRITE" -eq 1 ]; then
115
+ echo " -> Overwriting '$FILENAME' in release '$RELEASE_FOR_ASSET'..."
116
+ gh release upload "$RELEASE_FOR_ASSET" "$FILE_PATH" --clobber
117
+ else
118
+ echo " -> Skipping '$FILENAME', it already exists in release '$RELEASE_FOR_ASSET'."
119
+ fi
120
+ continue
121
+ fi
122
+
123
+ # Find a release to upload to for a new asset
124
+ if [ ${#AVAILABLE_RELEASES[@]} -eq 0 ]; then
125
+ echo "Error: All existing releases are full. No space to upload '$FILENAME'." >&2
126
+ exit 1
127
+ fi
128
+
129
+ UPLOAD_TARGET=${AVAILABLE_RELEASES[0]}
130
+
131
+ echo " -> Uploading '$FILENAME' to '$UPLOAD_TARGET'..."
132
+ gh release upload "$UPLOAD_TARGET" "$FILE_PATH"
133
+
134
+ # Update asset count and remove release from available list if full
135
+ AVAILABLE_ASSET_COUNTS[0]=$((${AVAILABLE_ASSET_COUNTS[0]} + 1))
136
+
137
+ MAX_ASSETS=998
138
+ if [ "$UPLOAD_TARGET" == "$TAG" ]; then
139
+ MAX_ASSETS=988
140
+ fi
141
+
142
+ if [ "${AVAILABLE_ASSET_COUNTS[0]}" -ge "$MAX_ASSETS" ]; then
143
+ AVAILABLE_RELEASES=("${AVAILABLE_RELEASES[@]:1}")
144
+ AVAILABLE_ASSET_COUNTS=("${AVAILABLE_ASSET_COUNTS[@]:1}")
145
+ fi
146
+ done
147
+
148
+ echo "Upload process complete."
@@ -14,7 +14,6 @@ import cv2
14
14
  import numpy as np
15
15
 
16
16
  import pytesseract
17
- import easyocr
18
17
  from PIL import Image
19
18
  from imgcat import imgcat
20
19
 
@@ -70,9 +69,10 @@ class GCPBasedTransformer:
70
69
 
71
70
 
72
71
  class TopoMapProcessor:
73
- def __init__(self, filepath, extra, index_map):
72
+ def __init__(self, filepath, extra, index_box, index_properties={}):
74
73
  self.filepath = filepath
75
- self.index_map = index_map
74
+ self.index_box = index_box
75
+ self.index_properties = index_properties
76
76
 
77
77
  self.SHOW_IMG = os.getenv('SHOW_IMG', '0') == '1'
78
78
  self.INSPECT = os.getenv('INSPECT', '0') == '1'
@@ -141,11 +141,8 @@ class TopoMapProcessor:
141
141
  ext = self.filepath.suffix
142
142
  return self.filepath.name.replace(ext, '')
143
143
 
144
- def get_sheet_ibox(self, sheet_id=None):
145
- if sheet_id is None:
146
- sheet_id = self.get_id()
147
-
148
- return self.index_map[sheet_id]
144
+ def get_sheet_ibox(self):
145
+ return self.index_box
149
146
 
150
147
  def get_data_dir(self):
151
148
  return Path('data')
@@ -553,6 +550,7 @@ class TopoMapProcessor:
553
550
  global easy_ocr_reader
554
551
 
555
552
  if easy_ocr_reader is None:
553
+ import easyocr
556
554
  easy_ocr_reader = easyocr.Reader(['en'], gpu=False)
557
555
 
558
556
  mask = np.zeros(img.shape[:2], dtype=np.uint8)
@@ -1607,12 +1605,15 @@ class TopoMapProcessor:
1607
1605
  def get_cutline_props(self):
1608
1606
  crs_proj = self.get_crs_proj()
1609
1607
 
1610
- props = {
1608
+ props = {}
1609
+ props.update(self.index_properties)
1610
+
1611
+ props.update({
1611
1612
  'id': self.get_id(),
1612
1613
  'crs': crs_proj,
1613
1614
  'gcps': self.get_gcps(pre_rotated=True),
1614
1615
  'pixel_cutline': self.get_full_pixel_cutline(pre_rotated=True),
1615
- }
1616
+ })
1616
1617
  return props
1617
1618
 
1618
1619
  def create_cutline(self, ibox, file):
@@ -0,0 +1,54 @@
1
+ import subprocess
2
+ import sys
3
+ import shutil
4
+ import importlib.resources as resources
5
+
6
+ def execute_bash_script(script_name):
7
+ """
8
+ Executes a bash script from the bash_scripts directory, locating it
9
+ relative to the project structure.
10
+ """
11
+ if not shutil.which("bash"):
12
+ raise FileNotFoundError("bash executable not found in PATH")
13
+
14
+ # Use importlib.resources to access the script as package data
15
+ # The script is located in topo_map_processor/bash_scripts
16
+ try:
17
+ script_path = resources.files('topo_map_processor.bash_scripts').joinpath(script_name)
18
+ except FileNotFoundError:
19
+ raise FileNotFoundError(f"The script {script_name} does not exist or is not a file. Searched in package data.")
20
+
21
+ # Execute the bash script and stream output, merging stderr into stdout
22
+ process = subprocess.Popen(
23
+ [shutil.which("bash"), str(script_path)] + sys.argv[1:],
24
+ stdout=subprocess.PIPE,
25
+ stderr=subprocess.STDOUT, # Redirect stderr to stdout
26
+ text=True,
27
+ bufsize=1 # Line-buffered
28
+ )
29
+
30
+ # Stream combined stdout and stderr
31
+ for line in process.stdout:
32
+ sys.stdout.write(line)
33
+ sys.stdout.flush()
34
+
35
+ process.wait()
36
+
37
+ if process.returncode != 0:
38
+ raise Exception(f"Bash script '{script_name}' failed with exit code {process.returncode}")
39
+
40
+ def generate_lists():
41
+ """
42
+ Executes the generate_lists.sh bash script.
43
+ """
44
+ execute_bash_script("generate_lists.sh")
45
+
46
+ def upload_to_release():
47
+ """
48
+ Executes the upload_to_release.sh bash script.
49
+ """
50
+ execute_bash_script("upload_to_release.sh")
51
+
52
+
53
+ if __name__ == "__main__":
54
+ generate_lists()
@@ -16,7 +16,7 @@ from pprint import pprint
16
16
  from pmtiles.tile import zxy_to_tileid, TileType, Compression
17
17
  from pmtiles.writer import Writer as PMTilesWriter
18
18
 
19
- from tile_sources import (
19
+ from .tile_sources import (
20
20
  DiskSource,
21
21
  DiskAndPartitionedPMTilesSource,
22
22
  MissingTileError