solweig 0.1.0b3__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 (78) hide show
  1. solweig-0.1.0b3/PKG-INFO +326 -0
  2. solweig-0.1.0b3/README.md +291 -0
  3. solweig-0.1.0b3/pyproject.toml +179 -0
  4. solweig-0.1.0b3/pysrc/solweig/__init__.py +199 -0
  5. solweig-0.1.0b3/pysrc/solweig/api.py +464 -0
  6. solweig-0.1.0b3/pysrc/solweig/buffers.py +246 -0
  7. solweig-0.1.0b3/pysrc/solweig/bundles.py +271 -0
  8. solweig-0.1.0b3/pysrc/solweig/cache.py +187 -0
  9. solweig-0.1.0b3/pysrc/solweig/components/__init__.py +17 -0
  10. solweig-0.1.0b3/pysrc/solweig/components/ground.py +137 -0
  11. solweig-0.1.0b3/pysrc/solweig/components/gvf.py +269 -0
  12. solweig-0.1.0b3/pysrc/solweig/components/radiation.py +456 -0
  13. solweig-0.1.0b3/pysrc/solweig/components/shadows.py +180 -0
  14. solweig-0.1.0b3/pysrc/solweig/components/svf_resolution.py +188 -0
  15. solweig-0.1.0b3/pysrc/solweig/components/tmrt.py +102 -0
  16. solweig-0.1.0b3/pysrc/solweig/computation.py +759 -0
  17. solweig-0.1.0b3/pysrc/solweig/constants.py +82 -0
  18. solweig-0.1.0b3/pysrc/solweig/data/default_materials.json +221 -0
  19. solweig-0.1.0b3/pysrc/solweig/data/default_params.json +56 -0
  20. solweig-0.1.0b3/pysrc/solweig/data/physics_defaults.json +31 -0
  21. solweig-0.1.0b3/pysrc/solweig/errors.py +123 -0
  22. solweig-0.1.0b3/pysrc/solweig/io.py +1328 -0
  23. solweig-0.1.0b3/pysrc/solweig/loaders.py +257 -0
  24. solweig-0.1.0b3/pysrc/solweig/metadata.py +137 -0
  25. solweig-0.1.0b3/pysrc/solweig/models/__init__.py +37 -0
  26. solweig-0.1.0b3/pysrc/solweig/models/config.py +233 -0
  27. solweig-0.1.0b3/pysrc/solweig/models/precomputed.py +681 -0
  28. solweig-0.1.0b3/pysrc/solweig/models/results.py +322 -0
  29. solweig-0.1.0b3/pysrc/solweig/models/state.py +152 -0
  30. solweig-0.1.0b3/pysrc/solweig/models/surface.py +1832 -0
  31. solweig-0.1.0b3/pysrc/solweig/models/weather.py +700 -0
  32. solweig-0.1.0b3/pysrc/solweig/parametersforsolweig.json +221 -0
  33. solweig-0.1.0b3/pysrc/solweig/physics/Kup_veg_2015a.py +33 -0
  34. solweig-0.1.0b3/pysrc/solweig/physics/Perez_v3.py +313 -0
  35. solweig-0.1.0b3/pysrc/solweig/physics/__init__.py +0 -0
  36. solweig-0.1.0b3/pysrc/solweig/physics/clearnessindex_2013b.py +88 -0
  37. solweig-0.1.0b3/pysrc/solweig/physics/create_patches.py +68 -0
  38. solweig-0.1.0b3/pysrc/solweig/physics/cylindric_wedge.py +109 -0
  39. solweig-0.1.0b3/pysrc/solweig/physics/daylen.py +22 -0
  40. solweig-0.1.0b3/pysrc/solweig/physics/diffusefraction.py +47 -0
  41. solweig-0.1.0b3/pysrc/solweig/physics/morphology.py +188 -0
  42. solweig-0.1.0b3/pysrc/solweig/physics/patch_radiation.py +373 -0
  43. solweig-0.1.0b3/pysrc/solweig/physics/sun_distance.py +20 -0
  44. solweig-0.1.0b3/pysrc/solweig/physics/sun_position.py +1061 -0
  45. solweig-0.1.0b3/pysrc/solweig/physics/sunlit_shaded_patches.py +38 -0
  46. solweig-0.1.0b3/pysrc/solweig/physics/wallalgorithms.py +214 -0
  47. solweig-0.1.0b3/pysrc/solweig/postprocess.py +356 -0
  48. solweig-0.1.0b3/pysrc/solweig/progress.py +245 -0
  49. solweig-0.1.0b3/pysrc/solweig/solweig_logging.py +189 -0
  50. solweig-0.1.0b3/pysrc/solweig/tiling.py +420 -0
  51. solweig-0.1.0b3/pysrc/solweig/timeseries.py +394 -0
  52. solweig-0.1.0b3/pysrc/solweig/utils.py +309 -0
  53. solweig-0.1.0b3/pysrc/solweig/walls.py +59 -0
  54. solweig-0.1.0b3/rust/Cargo.lock +1395 -0
  55. solweig-0.1.0b3/rust/Cargo.toml +20 -0
  56. solweig-0.1.0b3/rust/src/emissivity_models.rs +28 -0
  57. solweig-0.1.0b3/rust/src/gpu/mod.rs +6 -0
  58. solweig-0.1.0b3/rust/src/gpu/shadow_gpu.rs +1941 -0
  59. solweig-0.1.0b3/rust/src/gpu/shadow_propagation.wgsl +346 -0
  60. solweig-0.1.0b3/rust/src/gpu/shadow_to_u8.wgsl +69 -0
  61. solweig-0.1.0b3/rust/src/gpu/svf_accumulation.wgsl +76 -0
  62. solweig-0.1.0b3/rust/src/ground.rs +491 -0
  63. solweig-0.1.0b3/rust/src/gvf.rs +586 -0
  64. solweig-0.1.0b3/rust/src/gvf_geometry.rs +355 -0
  65. solweig-0.1.0b3/rust/src/lib.rs +188 -0
  66. solweig-0.1.0b3/rust/src/morphology.rs +100 -0
  67. solweig-0.1.0b3/rust/src/patch_radiation.rs +254 -0
  68. solweig-0.1.0b3/rust/src/pet.rs +400 -0
  69. solweig-0.1.0b3/rust/src/pipeline.rs +1061 -0
  70. solweig-0.1.0b3/rust/src/shadowing.rs +819 -0
  71. solweig-0.1.0b3/rust/src/sky.rs +843 -0
  72. solweig-0.1.0b3/rust/src/skyview.rs +925 -0
  73. solweig-0.1.0b3/rust/src/sun.rs +526 -0
  74. solweig-0.1.0b3/rust/src/sunlit_shaded_patches.rs +33 -0
  75. solweig-0.1.0b3/rust/src/tmrt.rs +216 -0
  76. solweig-0.1.0b3/rust/src/utci.rs +364 -0
  77. solweig-0.1.0b3/rust/src/vegetation.rs +1004 -0
  78. solweig-0.1.0b3/rust/src/wall_aspect.rs +435 -0
@@ -0,0 +1,326 @@
1
+ Metadata-Version: 2.4
2
+ Name: solweig
3
+ Version: 0.1.0b3
4
+ Classifier: Development Status :: 3 - Alpha
5
+ Classifier: Programming Language :: Python :: 3.9
6
+ Classifier: Programming Language :: Python :: 3.10
7
+ Classifier: Programming Language :: Python :: 3.11
8
+ Classifier: Programming Language :: Python :: 3.12
9
+ Classifier: Programming Language :: Python :: 3.13
10
+ Classifier: Programming Language :: Rust
11
+ Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
12
+ Classifier: Topic :: Scientific/Engineering :: GIS
13
+ Requires-Dist: numpy>=1.26.0
14
+ Requires-Dist: pyproj>=3.6.0
15
+ Requires-Dist: scipy>=1.13.0
16
+ Requires-Dist: shapely>=2.0.1
17
+ Requires-Dist: geopandas>=1.0.1 ; extra == 'full'
18
+ Requires-Dist: rasterio>=1.3.0 ; extra == 'full'
19
+ Requires-Dist: tqdm>=4.67.1 ; extra == 'full'
20
+ Requires-Dist: pillow>=9.0.0 ; extra == 'full'
21
+ Provides-Extra: full
22
+ Provides-Extra: qgis
23
+ License-File: LICENSE
24
+ Summary: High-performance SOLWEIG urban microclimate model (Rust + Python)
25
+ Keywords: python3,geographical-information-system,spatial-data,spatial-data-analysis,urban-climate,urban-heat-island,urban-meteorology,urban-microclimate,urban-planning,urban-sustainability,urban-thermal-comfort,urban-thermal-environment,urban-thermal-mapping,urban-thermal-modelling,urban-thermal-simulation,urban-thermal-sustainability
26
+ Author: UMEP Developers
27
+ Maintainer: UMEP Developers
28
+ License: AGPL-3.0
29
+ Requires-Python: >=3.9, <3.14
30
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
31
+ Project-URL: documentation, https://github.com/UMEP-dev/solweig
32
+ Project-URL: homepage, https://github.com/UMEP-dev/solweig
33
+ Project-URL: repository, https://github.com/UMEP-dev/solweig
34
+
35
+ # SOLWEIG
36
+
37
+ High-performance urban microclimate model for computing Mean Radiant Temperature (Tmrt) and thermal comfort indices (UTCI, PET).
38
+
39
+ **Rust + Python** performance-critical algorithms with GPU and tiled processing support.
40
+
41
+ > This package is currently in testing as a proof of concept. Please open an issue if you have any feedback or suggestions.
42
+
43
+ ## Documentation
44
+
45
+ - [Quick Start Guide](docs/getting-started/quick-start.md) - Detailed tutorial
46
+ - [API Reference](docs/) - Full documentation site (MkDocs, 25 pages)
47
+ - [Physics Specifications](specs/) - Scientific documentation (10 specs)
48
+ - [ROADMAP.md](ROADMAP.md) - Development priorities
49
+
50
+ ## Original Code
51
+
52
+ This package is adapted from the GPLv3-licensed [UMEP-processing](https://github.com/UMEP-dev/UMEP-processing) by Fredrik Lindberg, Ting Sun, Sue Grimmond, Yihao Tang, and Nils Wallenberg.
53
+
54
+ Licensed under GNU General Public License v3.0. See [LICENSE](LICENSE) for details.
55
+
56
+ **Citation:**
57
+
58
+ Adapted from UMEP (Urban Multi-scale Environmental Predictor) by Fredrik Lindberg, Sue Grimmond, and contributors. If you use this plugin in research, please cite:
59
+
60
+ > Lindberg F, Grimmond CSB, Gabey A, Huang B, Kent CW, Sun T, Theeuwes N, Järvi L, Ward H, Capel-Timms I, Chang YY, Jonsson P, Krave N, Liu D, Meyer D, Olofson F, Tan JG, Wästberg D, Xue L, Zhang Z (2018) Urban Multi-scale Environmental Predictor (UMEP) - An integrated tool for city-based climate services. Environmental Modelling and Software 99, 70-87 [doi:10.1016/j.envsoft.2017.09.020](https://doi.org/10.1016/j.envsoft.2017.09.020)
61
+
62
+ ## Installation
63
+
64
+ ```bash
65
+ # Clone and install
66
+ git clone https://github.com/UMEP-dev/solweig.git
67
+ cd solweig
68
+ uv sync # Install Python dependencies
69
+ maturin develop # Build Rust extension
70
+ ```
71
+
72
+ ## Quick Start
73
+
74
+ ```python
75
+ import solweig
76
+ from datetime import datetime
77
+
78
+ # Create surface from DSM array
79
+ surface = solweig.SurfaceData(dsm=my_dsm_array, pixel_size=1.0)
80
+
81
+ # Define location and weather
82
+ location = solweig.Location(latitude=57.7, longitude=12.0)
83
+ weather = solweig.Weather(
84
+ datetime=datetime(2024, 7, 15, 12, 0),
85
+ ta=25.0, # Air temperature (°C)
86
+ rh=50.0, # Relative humidity (%)
87
+ global_rad=800.0 # Global radiation (W/m²)
88
+ )
89
+
90
+ # Calculate Tmrt
91
+ result = solweig.calculate(surface, location, weather)
92
+ print(f"Tmrt: {result.tmrt.mean():.1f}°C")
93
+ ```
94
+
95
+ ## Loading from GeoTIFFs
96
+
97
+ ```python
98
+ import solweig
99
+
100
+ # Load and prepare surface data (auto-computes walls/SVF)
101
+ surface = solweig.SurfaceData.prepare(
102
+ dsm="data/dsm.tif",
103
+ working_dir="cache/", # Walls/SVF cached here
104
+ cdsm="data/cdsm.tif", # Optional: vegetation
105
+ )
106
+
107
+ # Load weather from EPW file
108
+ weather_list = solweig.Weather.from_epw(
109
+ "data/weather.epw",
110
+ start="2023-07-01",
111
+ end="2023-07-03",
112
+ )
113
+
114
+ # Calculate timeseries
115
+ results = solweig.calculate_timeseries(
116
+ surface=surface,
117
+ weather_series=weather_list,
118
+ output_dir="output/",
119
+ )
120
+ ```
121
+
122
+ ## Post-Processing (UTCI/PET)
123
+
124
+ Thermal comfort indices can be computed directly from results:
125
+
126
+ ```python
127
+ # Single timestep: compute directly from result
128
+ result = solweig.calculate(surface, location, weather)
129
+ utci = result.compute_utci(weather) # Fast polynomial
130
+ pet = result.compute_pet(weather) # Slower iterative solver
131
+
132
+ # Batch processing: from saved Tmrt files
133
+ solweig.compute_utci(tmrt_dir="output/", weather_series=weather_list, output_dir="utci/")
134
+ solweig.compute_pet(tmrt_dir="output/", weather_series=weather_list, output_dir="pet/")
135
+ ```
136
+
137
+ ## Input Validation
138
+
139
+ Validate inputs before expensive calculations:
140
+
141
+ ```python
142
+ try:
143
+ warnings = solweig.validate_inputs(surface, location, weather)
144
+ for w in warnings:
145
+ print(f"Warning: {w}")
146
+ result = solweig.calculate(surface, location, weather)
147
+ except solweig.GridShapeMismatch as e:
148
+ print(f"Grid mismatch: {e.field}")
149
+ except solweig.MissingPrecomputedData as e:
150
+ print(f"Missing data: {e}")
151
+ ```
152
+
153
+ ## Demos
154
+
155
+ Complete working examples are in the [demos/](demos/) folder:
156
+
157
+ - [demos/athens-demo.py](demos/athens-demo.py) - Full workflow with GeoTIFFs
158
+ - [demos/solweig_gbg_test.py](demos/solweig_gbg_test.py) - Gothenburg test data
159
+
160
+ ## QGIS Plugin
161
+
162
+ SOLWEIG is available as a QGIS Processing plugin for interactive spatial analysis:
163
+
164
+ 1. Open QGIS → **Plugins** → **Manage and Install Plugins**
165
+ 2. Go to **Settings** tab → Check **"Show also experimental plugins"**
166
+ 3. Search for **"SOLWEIG"** in the **All** tab
167
+ 4. Click **Install Plugin**
168
+
169
+ See [qgis_plugin/](qgis_plugin/) for source code and development details.
170
+
171
+ ## Build & Test
172
+
173
+ ```bash
174
+ maturin develop # Build Rust extension
175
+ pytest tests/ # Run all 353 tests
176
+ poe verify_project # Full verification (format, lint, test)
177
+ ```
178
+
179
+ ---
180
+
181
+ ## Development
182
+
183
+ ### Project Structure
184
+
185
+ ```text
186
+ pysrc/solweig/ # Python source (modular architecture)
187
+ api.py # Public API re-exports
188
+ models/ # Dataclass package (~3,080 lines)
189
+ components/ # Modular component functions
190
+ computation.py # Core orchestration logic
191
+ timeseries.py # Batch time series processing
192
+ tiling.py # Large raster tiling support
193
+ rust/ # Rust extensions via maturin
194
+ qgis_plugin/ # QGIS Processing plugin
195
+ tests/ # 353 tests (100% pass rate)
196
+ golden/ # Reference data validation
197
+ spec/ # Physical property tests
198
+ docs/ # MkDocs documentation site
199
+ specs/ # Markdown specifications
200
+ ```
201
+
202
+ ### Development Setup
203
+
204
+ ```bash
205
+ # Install dependencies
206
+ uv sync
207
+
208
+ # Build Rust extension for development
209
+ maturin develop
210
+
211
+ # Run tests with coverage
212
+ pytest tests/ --cov=pysrc/solweig
213
+
214
+ # Format and lint
215
+ ruff format pysrc/ tests/
216
+ ruff check pysrc/ tests/ --fix
217
+
218
+ # Type checking
219
+ ty pysrc/
220
+
221
+ # Full verification pipeline
222
+ poe verify_project
223
+ ```
224
+
225
+ ### Building Documentation
226
+
227
+ ```bash
228
+ # Serve docs locally
229
+ mkdocs serve
230
+
231
+ # Build static site
232
+ mkdocs build
233
+ ```
234
+
235
+ ### QGIS Plugin Bundle Preparation
236
+
237
+ To prepare the QGIS plugin for distribution:
238
+
239
+ #### 1. Update Plugin Metadata
240
+
241
+ Edit `qgis_plugin/metadata.txt`:
242
+
243
+ ```ini
244
+ [general]
245
+ name=SOLWEIG
246
+ version=0.1.0 # Increment version
247
+ experimental=True # Set to False when stable
248
+ description=Urban microclimate model for thermal comfort analysis
249
+ qgisMinimumVersion=3.0
250
+ author=Your Name
251
+ email=your.email@example.com
252
+ repository=https://github.com/UMEP-dev/solweig
253
+ tracker=https://github.com/UMEP-dev/solweig/issues
254
+ ```
255
+
256
+ #### 2. Create Distribution ZIP
257
+
258
+ ```bash
259
+ # Create a clean build directory
260
+ cd /Users/gareth/dev/umep/solweig
261
+ mkdir -p build/solweig
262
+
263
+ # Copy plugin files (excluding build artifacts)
264
+ cp -r qgis_plugin/* build/solweig/
265
+
266
+ # Create ZIP with correct structure
267
+ cd build
268
+ zip -r solweig.zip solweig/ -x "*.pyc" -x "*__pycache__*" -x "*.DS_Store"
269
+ ```
270
+
271
+ #### 3. Test Plugin Locally
272
+
273
+ Before uploading, test the plugin in QGIS:
274
+
275
+ ```bash
276
+ # Copy to QGIS plugins directory (macOS)
277
+ cp -r build/solweig ~/Library/Application\ Support/QGIS/QGIS3/profiles/default/python/plugins/
278
+
279
+ # Or use symlink for development
280
+ ln -s /Users/gareth/dev/umep/solweig/qgis_plugin ~/Library/Application\ Support/QGIS/QGIS3/profiles/default/python/plugins/solweig
281
+ ```
282
+
283
+ Then:
284
+
285
+ 1. Open QGIS
286
+ 2. **Plugins** → **Manage and Install Plugins** → **Installed**
287
+ 3. Enable **SOLWEIG**
288
+ 4. Test in **Processing Toolbox** → **SOLWEIG**
289
+
290
+ #### 4. Upload to QGIS Plugin Repository
291
+
292
+ 1. Register at <https://plugins.qgis.org/>
293
+ 2. Log in → **My Plugins** → **Upload a plugin**
294
+ 3. Select `build/solweig.zip`
295
+ 4. Check **"Experimental"** for pre-release versions
296
+ 5. Add changelog/release notes
297
+ 6. Click **Upload**
298
+
299
+ Review typically takes 1-3 days.
300
+
301
+ ### Release Checklist
302
+
303
+ - [ ] Version incremented in `qgis_plugin/metadata.txt`
304
+ - [ ] All tests passing (`pytest tests/`)
305
+ - [ ] Documentation updated
306
+ - [ ] CHANGELOG.md updated
307
+ - [ ] Plugin tested in QGIS locally
308
+ - [ ] ZIP bundle created and validated
309
+ - [ ] Uploaded to plugins.qgis.org
310
+
311
+ ### Tooling Preferences
312
+
313
+ | Tool | Use For | Instead Of |
314
+ | -------- | ---------------------- | ---------------------------- |
315
+ | **uv** | Package management | pip, poetry, pipenv |
316
+ | **ruff** | Linting and formatting | black, isort, flake8, pylint |
317
+ | **ty** | Type checking | mypy, pyright |
318
+
319
+ ### Code Metrics
320
+
321
+ - **api.py**: 403 lines (simplified from 3,976)
322
+ - **models/ package**: ~3,080 lines (6 modules)
323
+ - **Component functions**: All ≤ 455 lines
324
+ - **Test count**: 353 tests (100% pass rate)
325
+ - **Legacy code removed**: 6,100 lines
326
+
@@ -0,0 +1,291 @@
1
+ # SOLWEIG
2
+
3
+ High-performance urban microclimate model for computing Mean Radiant Temperature (Tmrt) and thermal comfort indices (UTCI, PET).
4
+
5
+ **Rust + Python** performance-critical algorithms with GPU and tiled processing support.
6
+
7
+ > This package is currently in testing as a proof of concept. Please open an issue if you have any feedback or suggestions.
8
+
9
+ ## Documentation
10
+
11
+ - [Quick Start Guide](docs/getting-started/quick-start.md) - Detailed tutorial
12
+ - [API Reference](docs/) - Full documentation site (MkDocs, 25 pages)
13
+ - [Physics Specifications](specs/) - Scientific documentation (10 specs)
14
+ - [ROADMAP.md](ROADMAP.md) - Development priorities
15
+
16
+ ## Original Code
17
+
18
+ This package is adapted from the GPLv3-licensed [UMEP-processing](https://github.com/UMEP-dev/UMEP-processing) by Fredrik Lindberg, Ting Sun, Sue Grimmond, Yihao Tang, and Nils Wallenberg.
19
+
20
+ Licensed under GNU General Public License v3.0. See [LICENSE](LICENSE) for details.
21
+
22
+ **Citation:**
23
+
24
+ Adapted from UMEP (Urban Multi-scale Environmental Predictor) by Fredrik Lindberg, Sue Grimmond, and contributors. If you use this plugin in research, please cite:
25
+
26
+ > Lindberg F, Grimmond CSB, Gabey A, Huang B, Kent CW, Sun T, Theeuwes N, Järvi L, Ward H, Capel-Timms I, Chang YY, Jonsson P, Krave N, Liu D, Meyer D, Olofson F, Tan JG, Wästberg D, Xue L, Zhang Z (2018) Urban Multi-scale Environmental Predictor (UMEP) - An integrated tool for city-based climate services. Environmental Modelling and Software 99, 70-87 [doi:10.1016/j.envsoft.2017.09.020](https://doi.org/10.1016/j.envsoft.2017.09.020)
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ # Clone and install
32
+ git clone https://github.com/UMEP-dev/solweig.git
33
+ cd solweig
34
+ uv sync # Install Python dependencies
35
+ maturin develop # Build Rust extension
36
+ ```
37
+
38
+ ## Quick Start
39
+
40
+ ```python
41
+ import solweig
42
+ from datetime import datetime
43
+
44
+ # Create surface from DSM array
45
+ surface = solweig.SurfaceData(dsm=my_dsm_array, pixel_size=1.0)
46
+
47
+ # Define location and weather
48
+ location = solweig.Location(latitude=57.7, longitude=12.0)
49
+ weather = solweig.Weather(
50
+ datetime=datetime(2024, 7, 15, 12, 0),
51
+ ta=25.0, # Air temperature (°C)
52
+ rh=50.0, # Relative humidity (%)
53
+ global_rad=800.0 # Global radiation (W/m²)
54
+ )
55
+
56
+ # Calculate Tmrt
57
+ result = solweig.calculate(surface, location, weather)
58
+ print(f"Tmrt: {result.tmrt.mean():.1f}°C")
59
+ ```
60
+
61
+ ## Loading from GeoTIFFs
62
+
63
+ ```python
64
+ import solweig
65
+
66
+ # Load and prepare surface data (auto-computes walls/SVF)
67
+ surface = solweig.SurfaceData.prepare(
68
+ dsm="data/dsm.tif",
69
+ working_dir="cache/", # Walls/SVF cached here
70
+ cdsm="data/cdsm.tif", # Optional: vegetation
71
+ )
72
+
73
+ # Load weather from EPW file
74
+ weather_list = solweig.Weather.from_epw(
75
+ "data/weather.epw",
76
+ start="2023-07-01",
77
+ end="2023-07-03",
78
+ )
79
+
80
+ # Calculate timeseries
81
+ results = solweig.calculate_timeseries(
82
+ surface=surface,
83
+ weather_series=weather_list,
84
+ output_dir="output/",
85
+ )
86
+ ```
87
+
88
+ ## Post-Processing (UTCI/PET)
89
+
90
+ Thermal comfort indices can be computed directly from results:
91
+
92
+ ```python
93
+ # Single timestep: compute directly from result
94
+ result = solweig.calculate(surface, location, weather)
95
+ utci = result.compute_utci(weather) # Fast polynomial
96
+ pet = result.compute_pet(weather) # Slower iterative solver
97
+
98
+ # Batch processing: from saved Tmrt files
99
+ solweig.compute_utci(tmrt_dir="output/", weather_series=weather_list, output_dir="utci/")
100
+ solweig.compute_pet(tmrt_dir="output/", weather_series=weather_list, output_dir="pet/")
101
+ ```
102
+
103
+ ## Input Validation
104
+
105
+ Validate inputs before expensive calculations:
106
+
107
+ ```python
108
+ try:
109
+ warnings = solweig.validate_inputs(surface, location, weather)
110
+ for w in warnings:
111
+ print(f"Warning: {w}")
112
+ result = solweig.calculate(surface, location, weather)
113
+ except solweig.GridShapeMismatch as e:
114
+ print(f"Grid mismatch: {e.field}")
115
+ except solweig.MissingPrecomputedData as e:
116
+ print(f"Missing data: {e}")
117
+ ```
118
+
119
+ ## Demos
120
+
121
+ Complete working examples are in the [demos/](demos/) folder:
122
+
123
+ - [demos/athens-demo.py](demos/athens-demo.py) - Full workflow with GeoTIFFs
124
+ - [demos/solweig_gbg_test.py](demos/solweig_gbg_test.py) - Gothenburg test data
125
+
126
+ ## QGIS Plugin
127
+
128
+ SOLWEIG is available as a QGIS Processing plugin for interactive spatial analysis:
129
+
130
+ 1. Open QGIS → **Plugins** → **Manage and Install Plugins**
131
+ 2. Go to **Settings** tab → Check **"Show also experimental plugins"**
132
+ 3. Search for **"SOLWEIG"** in the **All** tab
133
+ 4. Click **Install Plugin**
134
+
135
+ See [qgis_plugin/](qgis_plugin/) for source code and development details.
136
+
137
+ ## Build & Test
138
+
139
+ ```bash
140
+ maturin develop # Build Rust extension
141
+ pytest tests/ # Run all 353 tests
142
+ poe verify_project # Full verification (format, lint, test)
143
+ ```
144
+
145
+ ---
146
+
147
+ ## Development
148
+
149
+ ### Project Structure
150
+
151
+ ```text
152
+ pysrc/solweig/ # Python source (modular architecture)
153
+ api.py # Public API re-exports
154
+ models/ # Dataclass package (~3,080 lines)
155
+ components/ # Modular component functions
156
+ computation.py # Core orchestration logic
157
+ timeseries.py # Batch time series processing
158
+ tiling.py # Large raster tiling support
159
+ rust/ # Rust extensions via maturin
160
+ qgis_plugin/ # QGIS Processing plugin
161
+ tests/ # 353 tests (100% pass rate)
162
+ golden/ # Reference data validation
163
+ spec/ # Physical property tests
164
+ docs/ # MkDocs documentation site
165
+ specs/ # Markdown specifications
166
+ ```
167
+
168
+ ### Development Setup
169
+
170
+ ```bash
171
+ # Install dependencies
172
+ uv sync
173
+
174
+ # Build Rust extension for development
175
+ maturin develop
176
+
177
+ # Run tests with coverage
178
+ pytest tests/ --cov=pysrc/solweig
179
+
180
+ # Format and lint
181
+ ruff format pysrc/ tests/
182
+ ruff check pysrc/ tests/ --fix
183
+
184
+ # Type checking
185
+ ty pysrc/
186
+
187
+ # Full verification pipeline
188
+ poe verify_project
189
+ ```
190
+
191
+ ### Building Documentation
192
+
193
+ ```bash
194
+ # Serve docs locally
195
+ mkdocs serve
196
+
197
+ # Build static site
198
+ mkdocs build
199
+ ```
200
+
201
+ ### QGIS Plugin Bundle Preparation
202
+
203
+ To prepare the QGIS plugin for distribution:
204
+
205
+ #### 1. Update Plugin Metadata
206
+
207
+ Edit `qgis_plugin/metadata.txt`:
208
+
209
+ ```ini
210
+ [general]
211
+ name=SOLWEIG
212
+ version=0.1.0 # Increment version
213
+ experimental=True # Set to False when stable
214
+ description=Urban microclimate model for thermal comfort analysis
215
+ qgisMinimumVersion=3.0
216
+ author=Your Name
217
+ email=your.email@example.com
218
+ repository=https://github.com/UMEP-dev/solweig
219
+ tracker=https://github.com/UMEP-dev/solweig/issues
220
+ ```
221
+
222
+ #### 2. Create Distribution ZIP
223
+
224
+ ```bash
225
+ # Create a clean build directory
226
+ cd /Users/gareth/dev/umep/solweig
227
+ mkdir -p build/solweig
228
+
229
+ # Copy plugin files (excluding build artifacts)
230
+ cp -r qgis_plugin/* build/solweig/
231
+
232
+ # Create ZIP with correct structure
233
+ cd build
234
+ zip -r solweig.zip solweig/ -x "*.pyc" -x "*__pycache__*" -x "*.DS_Store"
235
+ ```
236
+
237
+ #### 3. Test Plugin Locally
238
+
239
+ Before uploading, test the plugin in QGIS:
240
+
241
+ ```bash
242
+ # Copy to QGIS plugins directory (macOS)
243
+ cp -r build/solweig ~/Library/Application\ Support/QGIS/QGIS3/profiles/default/python/plugins/
244
+
245
+ # Or use symlink for development
246
+ ln -s /Users/gareth/dev/umep/solweig/qgis_plugin ~/Library/Application\ Support/QGIS/QGIS3/profiles/default/python/plugins/solweig
247
+ ```
248
+
249
+ Then:
250
+
251
+ 1. Open QGIS
252
+ 2. **Plugins** → **Manage and Install Plugins** → **Installed**
253
+ 3. Enable **SOLWEIG**
254
+ 4. Test in **Processing Toolbox** → **SOLWEIG**
255
+
256
+ #### 4. Upload to QGIS Plugin Repository
257
+
258
+ 1. Register at <https://plugins.qgis.org/>
259
+ 2. Log in → **My Plugins** → **Upload a plugin**
260
+ 3. Select `build/solweig.zip`
261
+ 4. Check **"Experimental"** for pre-release versions
262
+ 5. Add changelog/release notes
263
+ 6. Click **Upload**
264
+
265
+ Review typically takes 1-3 days.
266
+
267
+ ### Release Checklist
268
+
269
+ - [ ] Version incremented in `qgis_plugin/metadata.txt`
270
+ - [ ] All tests passing (`pytest tests/`)
271
+ - [ ] Documentation updated
272
+ - [ ] CHANGELOG.md updated
273
+ - [ ] Plugin tested in QGIS locally
274
+ - [ ] ZIP bundle created and validated
275
+ - [ ] Uploaded to plugins.qgis.org
276
+
277
+ ### Tooling Preferences
278
+
279
+ | Tool | Use For | Instead Of |
280
+ | -------- | ---------------------- | ---------------------------- |
281
+ | **uv** | Package management | pip, poetry, pipenv |
282
+ | **ruff** | Linting and formatting | black, isort, flake8, pylint |
283
+ | **ty** | Type checking | mypy, pyright |
284
+
285
+ ### Code Metrics
286
+
287
+ - **api.py**: 403 lines (simplified from 3,976)
288
+ - **models/ package**: ~3,080 lines (6 modules)
289
+ - **Component functions**: All ≤ 455 lines
290
+ - **Test count**: 353 tests (100% pass rate)
291
+ - **Legacy code removed**: 6,100 lines