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.
- {topo_map_processor-0.1.5 → topo_map_processor-0.1.7}/PKG-INFO +34 -20
- {topo_map_processor-0.1.5 → topo_map_processor-0.1.7}/README.md +23 -11
- {topo_map_processor-0.1.5 → topo_map_processor-0.1.7}/pyproject.toml +27 -10
- {topo_map_processor-0.1.5 → topo_map_processor-0.1.7}/topo_map_processor/_version.py +2 -2
- topo_map_processor-0.1.7/topo_map_processor/bash_scripts/__init__.py +0 -0
- topo_map_processor-0.1.7/topo_map_processor/bash_scripts/generate_lists.sh +32 -0
- topo_map_processor-0.1.7/topo_map_processor/bash_scripts/upload_to_release.sh +148 -0
- topo_map_processor-0.1.5/topo_map_processor/__init__.py → topo_map_processor-0.1.7/topo_map_processor/processor.py +11 -10
- topo_map_processor-0.1.7/topo_map_processor/tools/__init__.py +0 -0
- topo_map_processor-0.1.7/topo_map_processor/tools/execute_bash.py +54 -0
- {topo_map_processor-0.1.5 → topo_map_processor-0.1.7}/topo_map_processor/tools/partition.py +1 -1
- {topo_map_processor-0.1.5 → topo_map_processor-0.1.7}/topo_map_processor/tools/retile.py +90 -52
- {topo_map_processor-0.1.5 → topo_map_processor-0.1.7}/topo_map_processor/tools/tile.py +28 -16
- {topo_map_processor-0.1.5 → topo_map_processor-0.1.7}/topo_map_processor/tools/tile_sources.py +52 -6
- topo_map_processor-0.1.5/.github/workflows/publish.yml +0 -56
- topo_map_processor-0.1.5/.python-version +0 -1
- topo_map_processor-0.1.5/samples/corner1.png +0 -0
- topo_map_processor-0.1.5/samples/corner2.png +0 -0
- topo_map_processor-0.1.5/samples/corner3.png +0 -0
- topo_map_processor-0.1.5/uv.lock +0 -940
- {topo_map_processor-0.1.5 → topo_map_processor-0.1.7}/.gitignore +0 -0
- {topo_map_processor-0.1.5 → topo_map_processor-0.1.7}/LICENSE +0 -0
- {topo_map_processor-0.1.5/topo_map_processor/tools → topo_map_processor-0.1.7/topo_map_processor}/__init__.py +0 -0
- {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.
|
|
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 [](https://pypi.org/project/topo_map_processor/) [](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,
|
|
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 `
|
|
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
|
|
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,
|
|
127
|
-
super().__init__(filepath, extra,
|
|
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
|
-
|
|
159
|
-
processor = SampleProcessor(filepath, extra,
|
|
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 `
|
|
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
|
|
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,
|
|
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 `
|
|
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
|
|
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,
|
|
81
|
-
super().__init__(filepath, extra,
|
|
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
|
-
|
|
113
|
-
processor = SampleProcessor(filepath, extra,
|
|
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 `
|
|
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
|
|
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
|
|
53
|
-
|
|
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
|
+
|
|
File without changes
|
|
@@ -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,
|
|
72
|
+
def __init__(self, filepath, extra, index_box, index_properties={}):
|
|
74
73
|
self.filepath = filepath
|
|
75
|
-
self.
|
|
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
|
|
145
|
-
|
|
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):
|
|
File without changes
|
|
@@ -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
|