# velantis-wind/velantiswind architecture

> Architecture diagram of the GitHub repository velantis-wind/velantiswind, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-06-12). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/velantis-wind/velantiswind
- Repository: https://github.com/velantis-wind/velantiswind

## Overview

1. **Host environment and main boundary**
   VelantisWind is a **QGIS Python plugin**, not a standalone app. The architectural boundary is the QGIS host: plugin code reads project layers, launches Qt dialogs/panels, runs domain calculations in-process, and writes results back into QGIS layers, rasters, or plugin dialogs.

2. **Top-level plugin entry and feature navigation**
   `plugin.py` is the integration point that exposes the plugin to QGIS. From there, the user is routed into three major workflows: **Energy**, **Noise**, and **Shadow**. The UI entry files (`energy_page.py`, `noise_page.py`, `shadow_page.py`) and dialogs (`*_dialog.py`, results dialogs, support/help dialogs) act as presentation shells around the computational cores.

3. **Shared UI and compatibility layer**
   The plugin includes a small shared UI layer: `ui_core/responsive.py` for layout behavior and `qt_compat.py` for Qt/QGIS version compatibility. This matters because the repository targets both **QGIS 3.x / Qt5** and **QGIS 4 / Qt6**. UI code is therefore separated from computation so the computational modules can survive Qt migration with minimal change.

4. **Energy / AEP subsystem**
   The energy workflow is split into a higher-level orchestration layer (`energy_core`) and a deeper analysis engine (`ag_core`). `energy_core` handles dialog state, validation, user feedback, and runner control. `ag_core` contains the actual AEP pipeline: turbine models, wind resource loading, simulation orchestration, physics factories, results building, and QGIS export adapters. The readme notes that the wake-aware energy path depends on **PyWake** in the QGIS Python environment.

5. **Energy data flow**
   The energy flow is: user inputs and QGIS layers → validation and model setup → resource loading (`wrg_reader.py`, `wrg_site.py`, `ag_core/orchestration/resource_loader.py`) → simulation (`ag_core/simulation/runner.py`, `ag_core/aep_compute.py`) → result assembly (`ag_core/orchestration/result_builder.py`, `ag_core/results/*`) → export to QGIS or file outputs (`ag_core/qgis_io/*`, `ag_core/export_results.py`). This subsystem is the most clearly layered one in the repo.

6. **Energy physics and model boundaries**
   Within `ag_core/physics`, the calculation engine is split into **wake**, **turbulence**, **blockage**, and **rotor_average** factories, plus a `common/compat.py` utility. These factory-style subpackages indicate interchangeable physics implementations behind a common orchestration interface, which is important for diagramming the numerical core as modular rather than monolithic.

7. **Noise subsystem**
   `noise_core` is a self-contained workflow for early-stage acoustic assessment. It has its own dialog controller/state, validation, runner, task orchestration, result evaluation, and QGIS output adapters. Internally it separates **sources**, **receivers**, **propagation**, **acoustics/spectra**, and **raster/task processing**, with both a `noise_engine_fast.py` and `noise_engine_iso.py` suggesting alternate calculation paths or standards-based modes.

8. **Noise data flow**
   The noise pipeline is: source and receiver collection from QGIS layers → snapshot/task preparation → propagation and spectral calculations → result payload/summary generation → QGIS layer updates via `noise_core/qgis_io/*`. The bundled `spectrum_library/*.csv` files are a key architectural input source, meaning the noise engine relies on packaged turbine spectrum data rather than only external inputs.

9. **Shadow flicker subsystem**
   `shadow_core` is another independent domain workflow. It collects turbines and receptors, models terrain and solar position, handles timezone logic, and runs calculations via `calculation/executor.py` and `point_runner.py`. Like noise, it has a dedicated runner, dialog controller/state, validation, results summary, raster tasking, and QGIS I/O layer.

10. **Shadow-specific infrastructure**
   The shadow module includes `solar_geometry.py`, `terrain/dem.py`, and `timezone_utils.py`, showing that shadow flicker depends on both geographic elevation and time-zone-aware solar timing. The bundled `assets/tzdata` package is architecturally important here: it gives the plugin a self-contained timezone database so time calculations can be done reliably inside QGIS environments.

11. **QGIS integration and output surfaces**
   Each major module has a QGIS adapter package (`ag_core/qgis_io`, `noise_core/qgis_io`, `shadow_core/qgis_io`) to translate calculation outputs into map layers, raster products, or project objects. This is the main boundary where numerical results become GIS-visible artifacts. Result dialogs (`results_dialog.py`, `noise_results_dialog.py`) complement the map outputs for review and validation.

12. **Shared data model and validation pattern**
   Across modules, the repository consistently uses a pattern of `domain.py`, `dialog_state.py`, `validation.py`, `runner.py`, and `ui_feedback.py`. That implies a repeated architecture: define a domain model, validate user/project inputs, run the computation, and report status back to the UI. This makes the modules independently testable and easier to diagram as parallel pipelines.

13. **External and packaged dependencies**
   The key runtime dependency that materially affects architecture is **QGIS** itself, with the plugin running in its embedded Python environment. For energy calculations, **PyWake** is an external dependency that must be installed into the same environment. The repo also bundles supporting assets like timezone data and wind/noise spectrum files, reducing reliance on system-installed data.

14. **Overall architectural shape**
   The repository is a **plugin shell around three domain engines**, with shared UI/runtime compatibility at the top and QGIS adapters at the edges. The dominant flow is: QGIS layers and project state in → module-specific validation and computation in the core → QGIS layers, rasters, and result dialogs out. The clearest diagram should show three parallel vertical stacks—Energy, Noise, Shadow—sharing the same host/plugin boundary but using separate numerical and data-handling internals.

Main entry points: [`VelantisWind/hub_dialog.py`](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/hub_dialog.py) (Hub dialog), [`VelantisWind/ui_core/responsive.py`](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/ui_core/responsive.py) (Responsive UI), [`VelantisWind/energy_core/dialog_controller.py`](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/energy_core/dialog_controller.py) (Energy control), [`VelantisWind/ag_core/simulation/runner.py`](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/ag_core/simulation/runner.py) (Simulation run)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_host["QGIS Host"]
  node_tzdata[("TZ data<br/>packaged data")]
end

subgraph group_ui["UI Shell"]
  node_plugin["Plugin entry<br/>QGIS plugin<br/>[plugin.py]"]
  node_qt_compat["Qt compat<br/>UI compat<br/>[qt_compat.py]"]
  node_responsive["Responsive UI<br/>layout helper<br/>[responsive.py]"]
  node_aep_dialog["AEP dialog<br/>workflow UI"]
  node_hub_dialog["Hub dialog<br/>ui shell<br/>[hub_dialog.py]"]
  node_noise_page["Noise page<br/>workflow UI<br/>[noise_page.py]"]
  node_shadow_page["Shadow page<br/>workflow UI<br/>[shadow_page.py]"]
end

subgraph group_energy["Energy"]
  node_energy_ctrl["Energy control<br/>dialog flow"]
  node_resource_loader["Resource load<br/>input orchestration<br/>[resource_loader.py]"]
  node_sim_runner["Simulation run<br/>aep engine<br/>[runner.py]"]
  node_physics_factories["Physics core<br/>numerical factories"]
  node_result_builder["Energy results<br/>result assembly<br/>[result_builder.py]"]
  node_energy_qgis["Energy I/O<br/>QGIS adapter"]
end

subgraph group_noise["Noise"]
  node_noise_ctrl["Noise control<br/>dialog flow"]
  node_source_receiver["Inputs collect<br/>data intake<br/>[collector.py]"]
  node_noise_task["Noise task<br/>engine orchestration<br/>[noise_task.py]"]
  node_propagation_engine["Propagation<br/>acoustic compute"]
  node_noise_results["Noise results<br/>result assembly"]
  node_noise_qgis["Noise I/O<br/>QGIS adapter"]
end

subgraph group_shadow["Shadow"]
  node_shadow_ctrl["Shadow control<br/>dialog flow"]
  node_shadow_inputs["Shadow inputs<br/>data intake<br/>[collector.py]"]
  node_shadow_calc["Shadow calc<br/>flicker engine<br/>[executor.py]"]
  node_shadow_env["Terrain/time<br/>geo-temporal model<br/>[solar_geometry.py]"]
  node_shadow_results["Shadow results<br/>result summary<br/>[summary.py]"]
  node_shadow_qgis["Shadow I/O<br/>QGIS adapter"]
end

node_plugin -->|"opens"| node_hub_dialog
node_hub_dialog -->|"launches"| node_aep_dialog
node_hub_dialog -->|"launches"| node_noise_page
node_hub_dialog -->|"launches"| node_shadow_page
node_qt_compat -.->|"supports"| node_plugin
node_responsive -.->|"supports"| node_aep_dialog
node_responsive -.->|"supports"| node_noise_page
node_responsive -.->|"supports"| node_shadow_page
node_aep_dialog -->|"drives"| node_energy_ctrl
node_energy_ctrl -->|"loads"| node_resource_loader
node_resource_loader -->|"configures"| node_sim_runner
node_sim_runner -->|"uses"| node_physics_factories
node_sim_runner -->|"emits"| node_result_builder
node_result_builder -->|"exports"| node_energy_qgis
node_energy_ctrl -.->|"reports"| node_energy_qgis
node_noise_page -->|"drives"| node_noise_ctrl
node_noise_ctrl -->|"collects"| node_source_receiver
node_source_receiver -->|"prepares"| node_noise_task
node_noise_task -->|"computes"| node_propagation_engine
node_propagation_engine -->|"produces"| node_noise_results
node_noise_results -->|"exports"| node_noise_qgis
node_shadow_page -->|"drives"| node_shadow_ctrl
node_shadow_ctrl -->|"collects"| node_shadow_inputs
node_shadow_inputs -->|"prepares"| node_shadow_calc
node_shadow_calc -->|"depends on"| node_shadow_env
node_shadow_calc -->|"produces"| node_shadow_results
node_shadow_results -->|"exports"| node_shadow_qgis
node_shadow_env -->|"reads"| node_tzdata

click node_plugin "https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/plugin.py"
click node_qt_compat "https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/qt_compat.py"
click node_responsive "https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/ui_core/responsive.py"
click node_aep_dialog "https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/aep_setup_dialog.py"
click node_hub_dialog "https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/hub_dialog.py"
click node_noise_page "https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/noise_page.py"
click node_shadow_page "https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/shadow_page.py"
click node_energy_ctrl "https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/energy_core/dialog_controller.py"
click node_resource_loader "https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/ag_core/orchestration/resource_loader.py"
click node_sim_runner "https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/ag_core/simulation/runner.py"
click node_physics_factories "https://github.com/velantis-wind/velantiswind/tree/main/VelantisWind/ag_core/physics"
click node_result_builder "https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/ag_core/orchestration/result_builder.py"
click node_energy_qgis "https://github.com/velantis-wind/velantiswind/tree/main/VelantisWind/ag_core/qgis_io"
click node_noise_ctrl "https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/noise_core/dialog_controller.py"
click node_source_receiver "https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/noise_core/sources/collector.py"
click node_noise_task "https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/noise_core/tasks/noise_task.py"
click node_propagation_engine "https://github.com/velantis-wind/velantiswind/tree/main/VelantisWind/noise_core/propagation"
click node_noise_results "https://github.com/velantis-wind/velantiswind/tree/main/VelantisWind/noise_core/results"
click node_noise_qgis "https://github.com/velantis-wind/velantiswind/tree/main/VelantisWind/noise_core/qgis_io"
click node_shadow_ctrl "https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/shadow_core/dialog_controller.py"
click node_shadow_inputs "https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/shadow_core/turbines/collector.py"
click node_shadow_calc "https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/shadow_core/calculation/executor.py"
click node_shadow_env "https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/shadow_core/solar_geometry.py"
click node_shadow_results "https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/shadow_core/results/summary.py"
click node_shadow_qgis "https://github.com/velantis-wind/velantiswind/tree/main/VelantisWind/shadow_core/qgis_io"
click node_tzdata "https://github.com/velantis-wind/velantiswind/tree/main/VelantisWind/assets/tzdata"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_tzdata toneBlue
class node_plugin,node_qt_compat,node_responsive,node_aep_dialog,node_hub_dialog,node_noise_page,node_shadow_page toneAmber
class node_energy_ctrl,node_resource_loader,node_sim_runner,node_physics_factories,node_result_builder,node_energy_qgis toneMint
class node_noise_ctrl,node_source_receiver,node_noise_task,node_propagation_engine,node_noise_results,node_noise_qgis toneRose
class node_shadow_ctrl,node_shadow_inputs,node_shadow_calc,node_shadow_env,node_shadow_results,node_shadow_qgis toneIndigo
```

## Components

### QGIS Host

Plugin runs inside QGIS and exchanges project layers, rasters, and dialogs.

- [TZ data](https://github.com/velantis-wind/velantiswind/tree/main/VelantisWind/assets/tzdata): packaged data · Bundled timezone database used by shadow calculations. · `VelantisWind/assets/tzdata`

### UI Shell

Plugin entry points, shared compatibility, and presentation surfaces.

- [Plugin entry](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/plugin.py): QGIS plugin · Registers the plugin with QGIS and dispatches major workflows. · `VelantisWind/plugin.py`
- [Qt compat](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/qt_compat.py): UI compat · Bridges Qt/QGIS version differences across supported runtimes. · `VelantisWind/qt_compat.py`
- [Responsive UI](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/ui_core/responsive.py): layout helper · Shared UI behavior for dialogs and panels. · `VelantisWind/ui_core/responsive.py`
- [AEP dialog](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/aep_setup_dialog.py): workflow UI · Presentation shell for the energy/AEP workflow. · `VelantisWind/aep_setup_dialog.py`
- [Hub dialog](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/hub_dialog.py): ui shell · Main plugin dialog or landing surface for navigation. · `VelantisWind/hub_dialog.py`
- [Noise page](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/noise_page.py): workflow UI · Presentation entry for the noise workflow. · `VelantisWind/noise_page.py`
- [Shadow page](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/shadow_page.py): workflow UI · Presentation entry for the shadow workflow. · `VelantisWind/shadow_page.py`

### Energy

Wake-aware AEP workflow from input layers to simulation and export.

- [Energy control](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/energy_core/dialog_controller.py): dialog flow · Validates state, manages runner lifecycle, and feeds UI feedback. · `VelantisWind/energy_core/dialog_controller.py`
- [Resource load](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/ag_core/orchestration/resource_loader.py): input orchestration · Loads project data, site context, and simulation resources for AEP. · `VelantisWind/ag_core/orchestration/resource_loader.py`
- [Simulation run](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/ag_core/simulation/runner.py): aep engine · Executes energy calculations and orchestrates the AEP compute pipeline. · `VelantisWind/ag_core/simulation/runner.py`
- [Physics core](https://github.com/velantis-wind/velantiswind/tree/main/VelantisWind/ag_core/physics): numerical factories · Wake, turbulence, blockage, and rotor-average factories behind the energy engine. · `VelantisWind/ag_core/physics`
- [Energy results](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/ag_core/orchestration/result_builder.py): result assembly · Builds arrays, tables, summaries, and export-ready outputs. · `VelantisWind/ag_core/orchestration/result_builder.py`
- [Energy I/O](https://github.com/velantis-wind/velantiswind/tree/main/VelantisWind/ag_core/qgis_io): QGIS adapter · Writes AEP outputs back into layers and project artifacts. · `VelantisWind/ag_core/qgis_io`

### Noise

Acoustic assessment workflow with alternate engine paths and GIS outputs.

- [Noise control](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/noise_core/dialog_controller.py): dialog flow · Coordinates validation, task execution, and result feedback for noise runs. · `VelantisWind/noise_core/dialog_controller.py`
- [Inputs collect](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/noise_core/sources/collector.py): data intake · Collects noise sources and receivers from QGIS layers. · `VelantisWind/noise_core/sources/collector.py`
- [Noise task](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/noise_core/tasks/noise_task.py): engine orchestration · Prepares snapshots and executes the acoustic computation path. · `VelantisWind/noise_core/tasks/noise_task.py`
- [Propagation](https://github.com/velantis-wind/velantiswind/tree/main/VelantisWind/noise_core/propagation): acoustic compute · Handles ground effects, spectra, and alternate fast/ISO engine modes. · `VelantisWind/noise_core/propagation`
- [Noise results](https://github.com/velantis-wind/velantiswind/tree/main/VelantisWind/noise_core/results): result assembly · Evaluates and summarizes acoustic outputs for review. · `VelantisWind/noise_core/results`
- [Noise I/O](https://github.com/velantis-wind/velantiswind/tree/main/VelantisWind/noise_core/qgis_io): QGIS adapter · Applies noise results to project layers and GIS outputs. · `VelantisWind/noise_core/qgis_io`

### Shadow

Shadow flicker workflow with terrain and timezone-aware calculation.

- [Shadow control](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/shadow_core/dialog_controller.py): dialog flow · Coordinates validation, runner control, and feedback for shadow flicker runs. · `VelantisWind/shadow_core/dialog_controller.py`
- [Shadow inputs](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/shadow_core/turbines/collector.py): data intake · Collects turbines and receptors for shadow analysis. · `VelantisWind/shadow_core/turbines/collector.py`
- [Shadow calc](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/shadow_core/calculation/executor.py): flicker engine · Runs point-based and batch shadow flicker calculations. · `VelantisWind/shadow_core/calculation/executor.py`
- [Terrain/time](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/shadow_core/solar_geometry.py): geo-temporal model · Provides solar geometry, DEM terrain, and timezone logic for shading timing. · `VelantisWind/shadow_core/solar_geometry.py`
- [Shadow results](https://github.com/velantis-wind/velantiswind/blob/main/VelantisWind/shadow_core/results/summary.py): result summary · Summarizes flicker outputs for UI review. · `VelantisWind/shadow_core/results/summary.py`
- [Shadow I/O](https://github.com/velantis-wind/velantiswind/tree/main/VelantisWind/shadow_core/qgis_io): QGIS adapter · Publishes shadow outputs into layers and raster/map products. · `VelantisWind/shadow_core/qgis_io`

## Connections

- Plugin entry → Hub dialog: opens · Routes users into the main plugin surface.
- Hub dialog → AEP dialog: launches · Opens the energy workflow shell.
- Hub dialog → Noise page: launches · Opens the noise workflow shell.
- Hub dialog → Shadow page: launches · Opens the shadow workflow shell.
- Qt compat → Plugin entry: supports · Keeps plugin UI code compatible across Qt/QGIS versions.
- Responsive UI → AEP dialog: supports · Shared layout behavior for workflow pages and dialogs.
- Responsive UI → Noise page: supports · Shared layout behavior for workflow pages and dialogs.
- Responsive UI → Shadow page: supports · Shared layout behavior for workflow pages and dialogs.
- AEP dialog → Energy control: drives · Presentation layer hands state and actions to the controller.
- Energy control → Resource load: loads · Builds simulation context from QGIS project inputs.
- Resource load → Simulation run: configures · Feeds the AEP simulation runner.
- Simulation run → Physics core: uses · Computes wake-aware energy using modular physics implementations.
- Simulation run → Energy results: emits · Passes computed arrays and intermediate outputs for assembly.
- Energy results → Energy I/O: exports · Sends final energy products to QGIS adapters.
- Energy control → Energy I/O: reports · Surfaces outputs and status to the host environment.
- Noise page → Noise control: drives · Presentation layer hands state and actions to the controller.
- Noise control → Inputs collect: collects · Builds the noise input set from project layers.
- Inputs collect → Noise task: prepares · Creates task-ready source and receiver inputs.
- Noise task → Propagation: computes · Runs acoustic propagation and spectral calculations.
- Propagation → Noise results: produces · Outputs payloads for evaluation and summaries.
- Noise results → Noise I/O: exports · Applies noise results back into QGIS.
- Shadow page → Shadow control: drives · Presentation layer hands state and actions to the controller.
- Shadow control → Shadow inputs: collects · Builds the shadow input set from project layers.
- Shadow inputs → Shadow calc: prepares · Creates calculation-ready turbine and receptor inputs.
- Shadow calc → Terrain/time: depends on · Uses terrain, solar geometry, and timezone-aware timing.
- Shadow calc → Shadow results: produces · Generates flicker outputs for summarization.
- Shadow results → Shadow I/O: exports · Publishes shadow products to QGIS.
- Terrain/time → TZ data: reads · Uses packaged timezone data for local-time calculations.
