smoothify 0.1.1__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.
- smoothify-0.1.1/LICENSE +21 -0
- smoothify-0.1.1/PKG-INFO +182 -0
- smoothify-0.1.1/README.md +164 -0
- smoothify-0.1.1/pyproject.toml +43 -0
- smoothify-0.1.1/setup.cfg +4 -0
- smoothify-0.1.1/smoothify/__init__.py +28 -0
- smoothify-0.1.1/smoothify/__version__.py +1 -0
- smoothify-0.1.1/smoothify/coordinator.py +173 -0
- smoothify-0.1.1/smoothify/geometry_ops.py +492 -0
- smoothify-0.1.1/smoothify/smoothify_core.py +336 -0
- smoothify-0.1.1/smoothify.egg-info/PKG-INFO +182 -0
- smoothify-0.1.1/smoothify.egg-info/SOURCES.txt +20 -0
- smoothify-0.1.1/smoothify.egg-info/dependency_links.txt +1 -0
- smoothify-0.1.1/smoothify.egg-info/requires.txt +5 -0
- smoothify-0.1.1/smoothify.egg-info/top_level.txt +1 -0
- smoothify-0.1.1/tests/test_area_tolerance.py +90 -0
- smoothify-0.1.1/tests/test_auto_segment_length.py +324 -0
- smoothify-0.1.1/tests/test_chaikin.py +90 -0
- smoothify-0.1.1/tests/test_edge_cases_coverage.py +224 -0
- smoothify-0.1.1/tests/test_geometry_types.py +315 -0
- smoothify-0.1.1/tests/test_smoothify_api.py +255 -0
- smoothify-0.1.1/tests/test_smoothify_core.py +174 -0
smoothify-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 DPIRD-DMA
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
smoothify-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: smoothify
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Transform pixelated geometries from raster data into smooth natural looking features
|
|
5
|
+
Author-email: Nick Wright <nicholas.wright@dpird.wa.gov.au>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/DPIRD-DMA/Smoothify
|
|
8
|
+
Keywords: geometry,smoothing,smooth,GIS,raster,vector,chaikin,shapely,geopandas
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: geopandas>=1.0.0
|
|
13
|
+
Requires-Dist: joblib>=1.4.0
|
|
14
|
+
Requires-Dist: numpy>=1.27.0
|
|
15
|
+
Requires-Dist: scipy>=1.11.0
|
|
16
|
+
Requires-Dist: shapely>=2.0.2
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
<p align="left">
|
|
20
|
+
<img src="https://raw.githubusercontent.com/DPIRD-DMA/Smoothify/main/images/smoothify_logo.png" alt="Smoothify Text" width="600">
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
[](https://www.python.org/downloads/)
|
|
24
|
+
[](https://github.com/DPIRD-DMA/Smoothify/blob/main/LICENSE)
|
|
25
|
+
[](https://pypi.org/project/smoothify/)
|
|
26
|
+
[](https://github.com/DPIRD-DMA/Smoothify/tree/main/examples)
|
|
27
|
+
|
|
28
|
+
📋 [View Changelog](https://github.com/DPIRD-DMA/Smoothify/blob/main/CHANGELOG.md)
|
|
29
|
+
|
|
30
|
+
A Python package for smoothing and refining geometries derived from raster data classifications. Smoothify transforms jagged polygons and lines resulting from raster-to-vector conversion into smooth, visually appealing features using an optimized implementation of Chaikin's corner-cutting algorithm.
|
|
31
|
+
|
|
32
|
+
## Problem
|
|
33
|
+
|
|
34
|
+
Polygons and lines derived from classified raster data (e.g., ML model predictions, spectral indices, or remote sensing classifications) often have unnatural "stair-stepped" or "pixelated" edges that:
|
|
35
|
+
|
|
36
|
+
- Are visually unappealing in maps and GIS applications
|
|
37
|
+
- Can be difficult to work with in downstream vector processing
|
|
38
|
+
- Don't represent the real-world features they're meant to depict
|
|
39
|
+
|
|
40
|
+
## Solution
|
|
41
|
+
|
|
42
|
+
Smoothify applies an optimized implementation of Chaikin's corner-cutting algorithm along with other geometric processing to create smooth, natural-looking features while:
|
|
43
|
+
|
|
44
|
+
- Preserving the general shape and area of polygons
|
|
45
|
+
- Supporting all shapley geometry types
|
|
46
|
+
- Handling shapes with interior holes
|
|
47
|
+
- Efficiently processing large datasets with multiprocessing
|
|
48
|
+
|
|
49
|
+
<p align="left">
|
|
50
|
+
<img src="https://raw.githubusercontent.com/DPIRD-DMA/Smoothify/main/images/smoothify_hero.png" alt="Smoothify Hero Image" width="600">
|
|
51
|
+
</p>
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
uv add smoothify
|
|
57
|
+
```
|
|
58
|
+
or
|
|
59
|
+
```bash
|
|
60
|
+
pip install smoothify
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Quick Start
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import geopandas as gpd
|
|
67
|
+
from smoothify import smoothify
|
|
68
|
+
|
|
69
|
+
# Load your polygonized raster data
|
|
70
|
+
polygon_gdf = gpd.read_file("path/to/your/polygons.gpkg")
|
|
71
|
+
|
|
72
|
+
# Apply smoothing (segment_length auto-detected from geometry)
|
|
73
|
+
smoothed_gdf = smoothify(
|
|
74
|
+
geom=polygon_gdf,
|
|
75
|
+
smooth_iterations=3, # More iterations = smoother result
|
|
76
|
+
num_cores=4 # Use parallel processing for large datasets
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Or specify segment_length explicitly (generally recommended)
|
|
80
|
+
smoothed_gdf = smoothify(
|
|
81
|
+
geom=polygon_gdf,
|
|
82
|
+
segment_length=10.0, # Use the original raster resolution
|
|
83
|
+
smooth_iterations=3,
|
|
84
|
+
num_cores=4
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Save the result
|
|
88
|
+
smoothed_gdf.to_file("smoothed_polygons.gpkg")
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## General Usage
|
|
92
|
+
|
|
93
|
+
The `smoothify()` function accepts three types of input:
|
|
94
|
+
|
|
95
|
+
### 1. GeoDataFrame
|
|
96
|
+
```python
|
|
97
|
+
import geopandas as gpd
|
|
98
|
+
from smoothify import smoothify
|
|
99
|
+
|
|
100
|
+
gdf = gpd.read_file("polygons.gpkg")
|
|
101
|
+
smoothed_gdf = smoothify(
|
|
102
|
+
geom=gdf,
|
|
103
|
+
segment_length=10.0,
|
|
104
|
+
smooth_iterations=3,
|
|
105
|
+
num_cores=4
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 2. Single Geometry
|
|
110
|
+
```python
|
|
111
|
+
from shapely.geometry import Polygon
|
|
112
|
+
from smoothify import smoothify
|
|
113
|
+
|
|
114
|
+
polygon = Polygon([(0, 0), (10, 0), (10, 10), (0, 10)])
|
|
115
|
+
smoothed_polygon = smoothify(
|
|
116
|
+
geom=polygon,
|
|
117
|
+
smooth_iterations=3
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 3. List of Geometries or GeometryCollection
|
|
122
|
+
```python
|
|
123
|
+
from shapely.geometry import Polygon, LineString
|
|
124
|
+
from smoothify import smoothify
|
|
125
|
+
|
|
126
|
+
geometries = [
|
|
127
|
+
Polygon([(0, 0), (10, 0), (10, 10), (0, 10)]),
|
|
128
|
+
LineString([(0, 0), (5, 5), (10, 0)])
|
|
129
|
+
]
|
|
130
|
+
smoothed = smoothify(
|
|
131
|
+
geom=geometries,
|
|
132
|
+
segment_length=1.0,
|
|
133
|
+
smooth_iterations=3
|
|
134
|
+
)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Parameters
|
|
138
|
+
|
|
139
|
+
| Parameter | Type | Default | Description |
|
|
140
|
+
|-----------|------|---------|-------------|
|
|
141
|
+
| `geom` | GeoDataFrame, BaseGeometry, or list[BaseGeometry] | Required | The geometry/geometries to smooth |
|
|
142
|
+
| `segment_length` | float | None | Resolution of the original raster data in map units. If None (default), automatically detects by finding the minimum segment length (from a data sample). Recommended to specify explicitly when known |
|
|
143
|
+
| `smooth_iterations` | int | 3 | Number of Chaikin corner-cutting iterations (typically 3-5). Higher values = smoother output with more vertices |
|
|
144
|
+
| `num_cores` | int | 0 | Number of CPU cores for parallel processing (0 = all available cores, 1 = serial) |
|
|
145
|
+
| `merge_collection` | bool | True | Whether to merge/dissolve adjacent geometries in collections before smoothing |
|
|
146
|
+
| `merge_multipolygons` | bool | True | Whether to merge adjacent polygons within MultiPolygons before smoothing |
|
|
147
|
+
| `preserve_area` | bool | True | Whether to restore original area after smoothing via buffering (applies to Polygons only) |
|
|
148
|
+
| `area_tolerance` | float | 0.01 | Percentage of original area allowed as error (e.g., 0.01 = 0.01% error = 99.99% preservation). Only affects Polygons when preserve_area=True |
|
|
149
|
+
|
|
150
|
+
## How It Works
|
|
151
|
+
|
|
152
|
+
Smoothify uses an advanced multi-step smoothing pipeline:
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
1. Adds intermediate vertices along line segments (segmentize)
|
|
156
|
+
2. Generates multiple rotated variants (for Polygons) to avoid artifacts
|
|
157
|
+
3. Simplifies each variant to remove noise
|
|
158
|
+
4. Applies Chaikin corner cutting to smooth
|
|
159
|
+
5. Merges all variants via union to eliminate start-point artifacts
|
|
160
|
+
6. Applies final smoothing pass
|
|
161
|
+
7. Optionally restores original area via buffering (for Polygons)
|
|
162
|
+
|
|
163
|
+
## Performance Considerations
|
|
164
|
+
|
|
165
|
+
- **Parallel Processing**: For large GeoDataFrames or collections, use `num_cores` = 0 to enable parallel processing
|
|
166
|
+
- **Smoothing Iterations**: Values of 3-5 typically provide good results. Higher values create smoother output but increase processing time and vertex count
|
|
167
|
+
- **Memory Usage**: Scales with geometry complexity. The algorithm creates multiple variants during smoothing
|
|
168
|
+
- **Optimal segment_length**: Should match the original raster cell size (pixel size) or be slightly larger for best results
|
|
169
|
+
|
|
170
|
+
## Contributing
|
|
171
|
+
|
|
172
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
173
|
+
|
|
174
|
+
1. Fork the repository
|
|
175
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
176
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
177
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
178
|
+
5. Open a Pull Request
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
This project is licensed under the MIT License - see the [LICENSE](https://github.com/DPIRD-DMA/Smoothify/blob/main/LICENSE) file for details.
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
<p align="left">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/DPIRD-DMA/Smoothify/main/images/smoothify_logo.png" alt="Smoothify Text" width="600">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
[](https://www.python.org/downloads/)
|
|
6
|
+
[](https://github.com/DPIRD-DMA/Smoothify/blob/main/LICENSE)
|
|
7
|
+
[](https://pypi.org/project/smoothify/)
|
|
8
|
+
[](https://github.com/DPIRD-DMA/Smoothify/tree/main/examples)
|
|
9
|
+
|
|
10
|
+
📋 [View Changelog](https://github.com/DPIRD-DMA/Smoothify/blob/main/CHANGELOG.md)
|
|
11
|
+
|
|
12
|
+
A Python package for smoothing and refining geometries derived from raster data classifications. Smoothify transforms jagged polygons and lines resulting from raster-to-vector conversion into smooth, visually appealing features using an optimized implementation of Chaikin's corner-cutting algorithm.
|
|
13
|
+
|
|
14
|
+
## Problem
|
|
15
|
+
|
|
16
|
+
Polygons and lines derived from classified raster data (e.g., ML model predictions, spectral indices, or remote sensing classifications) often have unnatural "stair-stepped" or "pixelated" edges that:
|
|
17
|
+
|
|
18
|
+
- Are visually unappealing in maps and GIS applications
|
|
19
|
+
- Can be difficult to work with in downstream vector processing
|
|
20
|
+
- Don't represent the real-world features they're meant to depict
|
|
21
|
+
|
|
22
|
+
## Solution
|
|
23
|
+
|
|
24
|
+
Smoothify applies an optimized implementation of Chaikin's corner-cutting algorithm along with other geometric processing to create smooth, natural-looking features while:
|
|
25
|
+
|
|
26
|
+
- Preserving the general shape and area of polygons
|
|
27
|
+
- Supporting all shapley geometry types
|
|
28
|
+
- Handling shapes with interior holes
|
|
29
|
+
- Efficiently processing large datasets with multiprocessing
|
|
30
|
+
|
|
31
|
+
<p align="left">
|
|
32
|
+
<img src="https://raw.githubusercontent.com/DPIRD-DMA/Smoothify/main/images/smoothify_hero.png" alt="Smoothify Hero Image" width="600">
|
|
33
|
+
</p>
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uv add smoothify
|
|
39
|
+
```
|
|
40
|
+
or
|
|
41
|
+
```bash
|
|
42
|
+
pip install smoothify
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import geopandas as gpd
|
|
49
|
+
from smoothify import smoothify
|
|
50
|
+
|
|
51
|
+
# Load your polygonized raster data
|
|
52
|
+
polygon_gdf = gpd.read_file("path/to/your/polygons.gpkg")
|
|
53
|
+
|
|
54
|
+
# Apply smoothing (segment_length auto-detected from geometry)
|
|
55
|
+
smoothed_gdf = smoothify(
|
|
56
|
+
geom=polygon_gdf,
|
|
57
|
+
smooth_iterations=3, # More iterations = smoother result
|
|
58
|
+
num_cores=4 # Use parallel processing for large datasets
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Or specify segment_length explicitly (generally recommended)
|
|
62
|
+
smoothed_gdf = smoothify(
|
|
63
|
+
geom=polygon_gdf,
|
|
64
|
+
segment_length=10.0, # Use the original raster resolution
|
|
65
|
+
smooth_iterations=3,
|
|
66
|
+
num_cores=4
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Save the result
|
|
70
|
+
smoothed_gdf.to_file("smoothed_polygons.gpkg")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## General Usage
|
|
74
|
+
|
|
75
|
+
The `smoothify()` function accepts three types of input:
|
|
76
|
+
|
|
77
|
+
### 1. GeoDataFrame
|
|
78
|
+
```python
|
|
79
|
+
import geopandas as gpd
|
|
80
|
+
from smoothify import smoothify
|
|
81
|
+
|
|
82
|
+
gdf = gpd.read_file("polygons.gpkg")
|
|
83
|
+
smoothed_gdf = smoothify(
|
|
84
|
+
geom=gdf,
|
|
85
|
+
segment_length=10.0,
|
|
86
|
+
smooth_iterations=3,
|
|
87
|
+
num_cores=4
|
|
88
|
+
)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 2. Single Geometry
|
|
92
|
+
```python
|
|
93
|
+
from shapely.geometry import Polygon
|
|
94
|
+
from smoothify import smoothify
|
|
95
|
+
|
|
96
|
+
polygon = Polygon([(0, 0), (10, 0), (10, 10), (0, 10)])
|
|
97
|
+
smoothed_polygon = smoothify(
|
|
98
|
+
geom=polygon,
|
|
99
|
+
smooth_iterations=3
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 3. List of Geometries or GeometryCollection
|
|
104
|
+
```python
|
|
105
|
+
from shapely.geometry import Polygon, LineString
|
|
106
|
+
from smoothify import smoothify
|
|
107
|
+
|
|
108
|
+
geometries = [
|
|
109
|
+
Polygon([(0, 0), (10, 0), (10, 10), (0, 10)]),
|
|
110
|
+
LineString([(0, 0), (5, 5), (10, 0)])
|
|
111
|
+
]
|
|
112
|
+
smoothed = smoothify(
|
|
113
|
+
geom=geometries,
|
|
114
|
+
segment_length=1.0,
|
|
115
|
+
smooth_iterations=3
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Parameters
|
|
120
|
+
|
|
121
|
+
| Parameter | Type | Default | Description |
|
|
122
|
+
|-----------|------|---------|-------------|
|
|
123
|
+
| `geom` | GeoDataFrame, BaseGeometry, or list[BaseGeometry] | Required | The geometry/geometries to smooth |
|
|
124
|
+
| `segment_length` | float | None | Resolution of the original raster data in map units. If None (default), automatically detects by finding the minimum segment length (from a data sample). Recommended to specify explicitly when known |
|
|
125
|
+
| `smooth_iterations` | int | 3 | Number of Chaikin corner-cutting iterations (typically 3-5). Higher values = smoother output with more vertices |
|
|
126
|
+
| `num_cores` | int | 0 | Number of CPU cores for parallel processing (0 = all available cores, 1 = serial) |
|
|
127
|
+
| `merge_collection` | bool | True | Whether to merge/dissolve adjacent geometries in collections before smoothing |
|
|
128
|
+
| `merge_multipolygons` | bool | True | Whether to merge adjacent polygons within MultiPolygons before smoothing |
|
|
129
|
+
| `preserve_area` | bool | True | Whether to restore original area after smoothing via buffering (applies to Polygons only) |
|
|
130
|
+
| `area_tolerance` | float | 0.01 | Percentage of original area allowed as error (e.g., 0.01 = 0.01% error = 99.99% preservation). Only affects Polygons when preserve_area=True |
|
|
131
|
+
|
|
132
|
+
## How It Works
|
|
133
|
+
|
|
134
|
+
Smoothify uses an advanced multi-step smoothing pipeline:
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
1. Adds intermediate vertices along line segments (segmentize)
|
|
138
|
+
2. Generates multiple rotated variants (for Polygons) to avoid artifacts
|
|
139
|
+
3. Simplifies each variant to remove noise
|
|
140
|
+
4. Applies Chaikin corner cutting to smooth
|
|
141
|
+
5. Merges all variants via union to eliminate start-point artifacts
|
|
142
|
+
6. Applies final smoothing pass
|
|
143
|
+
7. Optionally restores original area via buffering (for Polygons)
|
|
144
|
+
|
|
145
|
+
## Performance Considerations
|
|
146
|
+
|
|
147
|
+
- **Parallel Processing**: For large GeoDataFrames or collections, use `num_cores` = 0 to enable parallel processing
|
|
148
|
+
- **Smoothing Iterations**: Values of 3-5 typically provide good results. Higher values create smoother output but increase processing time and vertex count
|
|
149
|
+
- **Memory Usage**: Scales with geometry complexity. The algorithm creates multiple variants during smoothing
|
|
150
|
+
- **Optimal segment_length**: Should match the original raster cell size (pixel size) or be slightly larger for best results
|
|
151
|
+
|
|
152
|
+
## Contributing
|
|
153
|
+
|
|
154
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
155
|
+
|
|
156
|
+
1. Fork the repository
|
|
157
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
158
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
159
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
160
|
+
5. Open a Pull Request
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
|
|
164
|
+
This project is licensed under the MIT License - see the [LICENSE](https://github.com/DPIRD-DMA/Smoothify/blob/main/LICENSE) file for details.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools >= 61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "smoothify"
|
|
7
|
+
authors = [{name = "Nick Wright", email = "nicholas.wright@dpird.wa.gov.au"}]
|
|
8
|
+
dynamic = ["version"]
|
|
9
|
+
description = "Transform pixelated geometries from raster data into smooth natural looking features"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"geopandas>=1.0.0",
|
|
13
|
+
"joblib>=1.4.0",
|
|
14
|
+
"numpy>=1.27.0",
|
|
15
|
+
"scipy>=1.11.0",
|
|
16
|
+
"shapely>=2.0.2",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
license = "MIT"
|
|
20
|
+
readme = {file = "README.md", content-type = "text/markdown"}
|
|
21
|
+
keywords = ["geometry", "smoothing", "smooth", "GIS", "raster", "vector", "chaikin", "shapely", "geopandas"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
[tool.setuptools.dynamic]
|
|
25
|
+
version = {attr = "smoothify.__version__.__version__"}
|
|
26
|
+
|
|
27
|
+
[dependency-groups]
|
|
28
|
+
dev = [
|
|
29
|
+
"descartes>=1.1.0",
|
|
30
|
+
"ipykernel>=6.29.5",
|
|
31
|
+
"matplotlib>=3.9.4",
|
|
32
|
+
"pytest>=8.4.2",
|
|
33
|
+
"pytest-cov>=6.0.0",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/DPIRD-DMA/Smoothify"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools]
|
|
40
|
+
packages = ["smoothify"]
|
|
41
|
+
|
|
42
|
+
[tool.ruff.lint]
|
|
43
|
+
select = ["E", "F", "B"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Smoothify - Geometry Smoothing Package
|
|
3
|
+
|
|
4
|
+
A Python package for smoothing and refining geometries derived from raster data
|
|
5
|
+
classifications. Transforms jagged polygons and lines resulting from raster-to-vector
|
|
6
|
+
conversion into smooth, visually appealing features using an optimized implementation
|
|
7
|
+
of Chaikin's corner-cutting algorithm.
|
|
8
|
+
|
|
9
|
+
Supports:
|
|
10
|
+
- Polygons (including those with holes)
|
|
11
|
+
- LineStrings
|
|
12
|
+
- MultiPolygons
|
|
13
|
+
- MultiLineStrings
|
|
14
|
+
- GeometryCollections
|
|
15
|
+
- GeoDataFrames
|
|
16
|
+
|
|
17
|
+
Main function:
|
|
18
|
+
smoothify() - Apply Chaikin corner-cutting smoothing to geometries
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from .__version__ import __version__
|
|
22
|
+
from .coordinator import smoothify
|
|
23
|
+
|
|
24
|
+
# Package-wide exports
|
|
25
|
+
__all__ = [
|
|
26
|
+
"smoothify",
|
|
27
|
+
"__version__",
|
|
28
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.1"
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
from multiprocessing import cpu_count
|
|
2
|
+
from typing import Optional, Sequence, overload
|
|
3
|
+
|
|
4
|
+
import geopandas as gpd
|
|
5
|
+
from shapely.geometry import (
|
|
6
|
+
GeometryCollection,
|
|
7
|
+
LinearRing,
|
|
8
|
+
LineString,
|
|
9
|
+
MultiLineString,
|
|
10
|
+
MultiPolygon,
|
|
11
|
+
Polygon,
|
|
12
|
+
)
|
|
13
|
+
from shapely.geometry.base import BaseGeometry
|
|
14
|
+
|
|
15
|
+
from smoothify.geometry_ops import (
|
|
16
|
+
_auto_detect_segment_length,
|
|
17
|
+
_smoothify_bulk,
|
|
18
|
+
_smoothify_geodataframe,
|
|
19
|
+
_smoothify_single,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@overload
|
|
24
|
+
def smoothify(
|
|
25
|
+
geom: gpd.GeoDataFrame,
|
|
26
|
+
segment_length: Optional[float] = None,
|
|
27
|
+
num_cores: int = 0,
|
|
28
|
+
smooth_iterations: int = 3,
|
|
29
|
+
merge_collection: bool = True,
|
|
30
|
+
merge_multipolygons: bool = True,
|
|
31
|
+
preserve_area: bool = True,
|
|
32
|
+
area_tolerance: float = 0.01,
|
|
33
|
+
) -> gpd.GeoDataFrame: ...
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@overload
|
|
37
|
+
def smoothify(
|
|
38
|
+
geom: Sequence[BaseGeometry],
|
|
39
|
+
segment_length: Optional[float] = None,
|
|
40
|
+
num_cores: int = 0,
|
|
41
|
+
smooth_iterations: int = 3,
|
|
42
|
+
merge_collection: bool = True,
|
|
43
|
+
merge_multipolygons: bool = True,
|
|
44
|
+
preserve_area: bool = True,
|
|
45
|
+
area_tolerance: float = 0.01,
|
|
46
|
+
) -> BaseGeometry: ...
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@overload
|
|
50
|
+
def smoothify(
|
|
51
|
+
geom: BaseGeometry,
|
|
52
|
+
segment_length: Optional[float] = None,
|
|
53
|
+
num_cores: int = 0,
|
|
54
|
+
smooth_iterations: int = 3,
|
|
55
|
+
merge_collection: bool = True,
|
|
56
|
+
merge_multipolygons: bool = True,
|
|
57
|
+
preserve_area: bool = True,
|
|
58
|
+
area_tolerance: float = 0.01,
|
|
59
|
+
) -> BaseGeometry: ...
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def smoothify(
|
|
63
|
+
geom: BaseGeometry | Sequence[BaseGeometry] | gpd.GeoDataFrame,
|
|
64
|
+
segment_length: Optional[float] = None,
|
|
65
|
+
num_cores: int = 0,
|
|
66
|
+
smooth_iterations: int = 3,
|
|
67
|
+
merge_collection: bool = True,
|
|
68
|
+
merge_multipolygons: bool = True,
|
|
69
|
+
preserve_area: bool = True,
|
|
70
|
+
area_tolerance: float = 0.01,
|
|
71
|
+
) -> BaseGeometry | Sequence[BaseGeometry] | gpd.GeoDataFrame:
|
|
72
|
+
"""Smooth geometries derived from raster data using Chaikin's corner-cutting algorithm.
|
|
73
|
+
|
|
74
|
+
Main entry point for smoothing jagged polygons and lines resulting from
|
|
75
|
+
raster-to-vector conversion. Transforms pixelated edges into smooth, natural-looking
|
|
76
|
+
curves while preserving general shape and area characteristics.
|
|
77
|
+
|
|
78
|
+
Supports multiple input types:
|
|
79
|
+
- Single geometries (Polygon, LineString, LinearRing, MultiPolygon, MultiLineString)
|
|
80
|
+
- Lists of geometries or GeometryCollections
|
|
81
|
+
- GeoDataFrames
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
geom: Geometry, list of geometries, or GeoDataFrame to smooth.
|
|
85
|
+
segment_length: Resolution of the original raster data in map units. Used for
|
|
86
|
+
adding intermediate vertices and simplification. Should match or exceed
|
|
87
|
+
the original raster pixel size. If None (default), automatically detects
|
|
88
|
+
segment length by finding the minimum segment length, which represents the
|
|
89
|
+
true pixel size in pixelated geometries (corners retain minimum length even
|
|
90
|
+
when straight edges are simplified during polygonization).
|
|
91
|
+
num_cores: Number of CPU cores for parallel processing (0 = all available cores,
|
|
92
|
+
1 = serial processing). Only applies to GeoDataFrames and collections.
|
|
93
|
+
smooth_iterations: Number of Chaikin corner-cutting iterations (typically 3-5).
|
|
94
|
+
Higher values produce smoother results but add more vertices.
|
|
95
|
+
merge_collection: Whether to merge/dissolve adjacent geometries in collections
|
|
96
|
+
before smoothing. Useful for joining polygons from adjacent raster cells.
|
|
97
|
+
merge_multipolygons: Whether to merge adjacent polygons within MultiPolygons
|
|
98
|
+
before smoothing.
|
|
99
|
+
preserve_area: Whether to restore original area after smoothing via buffering.
|
|
100
|
+
Applied to Polygons only.
|
|
101
|
+
area_tolerance: Percentage of original area allowed as error
|
|
102
|
+
(e.g., 0.01 = 0.01% error). Default is 0.01% (99.99% area preservation).
|
|
103
|
+
Smaller values = more accurate area preservation but slower.
|
|
104
|
+
Only affects Polygons when preserve_area=True.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Smoothed geometry matching the input type:
|
|
108
|
+
- BaseGeometry for single geometry inputs
|
|
109
|
+
- list[BaseGeometry] for list inputs
|
|
110
|
+
- GeoDataFrame for GeoDataFrame inputs
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
ValueError: If input is not a supported geometry type.
|
|
114
|
+
|
|
115
|
+
Examples:
|
|
116
|
+
>>> from smoothify import smoothify
|
|
117
|
+
>>> import geopandas as gpd
|
|
118
|
+
>>> from shapely.geometry import Polygon
|
|
119
|
+
>>>
|
|
120
|
+
>>> # Smooth a single polygon with default area tolerance (0.01%)
|
|
121
|
+
>>> polygon = Polygon([(0, 0), (10, 0), (10, 10), (0, 10)])
|
|
122
|
+
>>> smoothed = smoothify(polygon, segment_length=1.0, smooth_iterations=3)
|
|
123
|
+
>>>
|
|
124
|
+
>>> # Smooth with stricter area preservation (0.001% error)
|
|
125
|
+
>>> smoothed = smoothify(polygon, segment_length=1.0, area_tolerance=0.001)
|
|
126
|
+
>>>
|
|
127
|
+
>>> # Smooth a GeoDataFrame in parallel
|
|
128
|
+
>>> gdf = gpd.read_file("water_bodies.gpkg")
|
|
129
|
+
>>> smoothed_gdf = smoothify(gdf, segment_length=10.0, num_cores=4)
|
|
130
|
+
""" # noqa: E501
|
|
131
|
+
if num_cores <= 0:
|
|
132
|
+
num_cores = cpu_count()
|
|
133
|
+
if isinstance(geom, list):
|
|
134
|
+
geom = GeometryCollection(geom)
|
|
135
|
+
|
|
136
|
+
if segment_length is None:
|
|
137
|
+
segment_length = _auto_detect_segment_length(geom)
|
|
138
|
+
|
|
139
|
+
if isinstance(geom, GeometryCollection | MultiPolygon | MultiLineString):
|
|
140
|
+
return _smoothify_bulk(
|
|
141
|
+
geom=geom,
|
|
142
|
+
segment_length=segment_length,
|
|
143
|
+
num_cores=num_cores,
|
|
144
|
+
smooth_iterations=smooth_iterations,
|
|
145
|
+
merge_collection=merge_collection,
|
|
146
|
+
merge_multipolygons=merge_multipolygons,
|
|
147
|
+
preserve_area=preserve_area,
|
|
148
|
+
area_tolerance=area_tolerance,
|
|
149
|
+
)
|
|
150
|
+
elif isinstance(geom, Polygon | LineString | LinearRing):
|
|
151
|
+
return _smoothify_single(
|
|
152
|
+
geom=geom,
|
|
153
|
+
segment_length=segment_length,
|
|
154
|
+
smooth_iterations=smooth_iterations,
|
|
155
|
+
merge_multipolygons=merge_multipolygons,
|
|
156
|
+
preserve_area=preserve_area,
|
|
157
|
+
area_tolerance=area_tolerance,
|
|
158
|
+
)
|
|
159
|
+
elif isinstance(geom, gpd.GeoDataFrame):
|
|
160
|
+
return _smoothify_geodataframe(
|
|
161
|
+
gdf=geom,
|
|
162
|
+
segment_length=segment_length,
|
|
163
|
+
num_cores=num_cores,
|
|
164
|
+
smooth_iterations=smooth_iterations,
|
|
165
|
+
merge_collection=merge_collection,
|
|
166
|
+
merge_multipolygons=merge_multipolygons,
|
|
167
|
+
preserve_area=preserve_area,
|
|
168
|
+
area_tolerance=area_tolerance,
|
|
169
|
+
)
|
|
170
|
+
else:
|
|
171
|
+
raise ValueError(
|
|
172
|
+
f"Input geometry must be a BaseGeometry or list of BaseGeometry. Got {type(geom)}." # noqa: E501
|
|
173
|
+
)
|