building-eload 0.5.0__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 (54) hide show
  1. building_eload-0.5.0/LICENCE.md +27 -0
  2. building_eload-0.5.0/PKG-INFO +274 -0
  3. building_eload-0.5.0/README.md +197 -0
  4. building_eload-0.5.0/building_eload/__init__.py +31 -0
  5. building_eload-0.5.0/building_eload/core/__init__.py +0 -0
  6. building_eload-0.5.0/building_eload/core/building_loader.py +127 -0
  7. building_eload-0.5.0/building_eload/core/clustering.py +448 -0
  8. building_eload-0.5.0/building_eload/core/dynamic_simulation.py +1242 -0
  9. building_eload-0.5.0/building_eload/core/static_simulation.py +1035 -0
  10. building_eload-0.5.0/building_eload/core/validation.py +1046 -0
  11. building_eload-0.5.0/building_eload/models/__init__.py +0 -0
  12. building_eload-0.5.0/building_eload/models/cooking.py +225 -0
  13. building_eload-0.5.0/building_eload/models/dwelling.py +339 -0
  14. building_eload-0.5.0/building_eload/models/heat_pump.py +836 -0
  15. building_eload-0.5.0/building_eload/models/heating.py +728 -0
  16. building_eload-0.5.0/building_eload/models/hot_water.py +602 -0
  17. building_eload-0.5.0/building_eload/models/non_residential.py +495 -0
  18. building_eload-0.5.0/building_eload/models/occupancy.py +129 -0
  19. building_eload-0.5.0/building_eload/models/specific.py +123 -0
  20. building_eload-0.5.0/building_eload/models/vacations.py +397 -0
  21. building_eload-0.5.0/building_eload/models/weather.py +121 -0
  22. building_eload-0.5.0/building_eload/plots/__init__.py +0 -0
  23. building_eload-0.5.0/building_eload/plots/clustering.py +463 -0
  24. building_eload-0.5.0/building_eload/plots/dynamic_model.py +511 -0
  25. building_eload-0.5.0/building_eload/plots/static_calibration.py +268 -0
  26. building_eload-0.5.0/building_eload/plots/validation.py +1463 -0
  27. building_eload-0.5.0/building_eload/scripts/__init__.py +0 -0
  28. building_eload-0.5.0/building_eload/scripts/calibration/__init__.py +0 -0
  29. building_eload-0.5.0/building_eload/scripts/calibration/run_plot_static_calibration_results.py +124 -0
  30. building_eload-0.5.0/building_eload/scripts/climate/generate_climates.py +425 -0
  31. building_eload-0.5.0/building_eload/scripts/clustering/__init__.py +0 -0
  32. building_eload-0.5.0/building_eload/scripts/clustering/annual_energy_from_clustering.py +67 -0
  33. building_eload-0.5.0/building_eload/scripts/clustering/district_dhu.py +346 -0
  34. building_eload-0.5.0/building_eload/scripts/clustering/national_district_clustering.py +500 -0
  35. building_eload-0.5.0/building_eload/scripts/clustering/regional_district_clustering.py +393 -0
  36. building_eload-0.5.0/building_eload/scripts/clustering/run_multiple_clustering.py +212 -0
  37. building_eload-0.5.0/building_eload/scripts/simulation/dynamic/run.py +77 -0
  38. building_eload-0.5.0/building_eload/scripts/simulation/dynamic/run_parallel.py +205 -0
  39. building_eload-0.5.0/building_eload/scripts/simulation/static/run.py +37 -0
  40. building_eload-0.5.0/building_eload/scripts/simulation/static/run_parallel.py +182 -0
  41. building_eload-0.5.0/building_eload/scripts/validation/run.py +67 -0
  42. building_eload-0.5.0/building_eload/utils/__init__.py +0 -0
  43. building_eload-0.5.0/building_eload/utils/district_list.py +105 -0
  44. building_eload-0.5.0/building_eload/utils/epw_reader.py +135 -0
  45. building_eload-0.5.0/building_eload/utils/logger.py +54 -0
  46. building_eload-0.5.0/building_eload/utils/plot_utils.py +20 -0
  47. building_eload-0.5.0/building_eload/utils/seeding.py +102 -0
  48. building_eload-0.5.0/building_eload.egg-info/PKG-INFO +274 -0
  49. building_eload-0.5.0/building_eload.egg-info/SOURCES.txt +52 -0
  50. building_eload-0.5.0/building_eload.egg-info/dependency_links.txt +1 -0
  51. building_eload-0.5.0/building_eload.egg-info/requires.txt +30 -0
  52. building_eload-0.5.0/building_eload.egg-info/top_level.txt +1 -0
  53. building_eload-0.5.0/pyproject.toml +118 -0
  54. building_eload-0.5.0/setup.cfg +4 -0
@@ -0,0 +1,27 @@
1
+ The MIT License (MIT)
2
+ =====================
3
+
4
+ - Copyright © `2025` `Yoann Chiche`
5
+ - Copyright © `2025` `Seddik Yassine Abdelouadoud`
6
+ - Copyright © `2025` `Anna Cocchi`
7
+
8
+ Permission is hereby granted, free of charge, to any person
9
+ obtaining a copy of this software and associated documentation
10
+ files (the “Software”), to deal in the Software without
11
+ restriction, including without limitation the rights to use,
12
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the
14
+ Software is furnished to do so, subject to the following
15
+ conditions:
16
+
17
+ The above copyright notice and this permission notice shall be
18
+ included in all copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
21
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,274 @@
1
+ Metadata-Version: 2.4
2
+ Name: building_eload
3
+ Version: 0.5.0
4
+ Summary: District-level energy hourly simulation of buildings
5
+ Author-email: Yoann Chiche <yoann.chiche@minesparis.psl.eu>, Yassine Abdelouadoud <yassine.abdelouadoud@gmail.com>, Anna Cocchi <anna.cocchi@minesparis.psl.eu>
6
+ Maintainer-email: Yoann Chiche <yoann.chiche@minesparis.psl.eu>, Yassine Abdelouadoud <yassine.abdelouadoud@gmail.com>
7
+ License: The MIT License (MIT)
8
+ =====================
9
+
10
+ - Copyright © `2025` `Yoann Chiche`
11
+ - Copyright © `2025` `Seddik Yassine Abdelouadoud`
12
+ - Copyright © `2025` `Anna Cocchi`
13
+
14
+ Permission is hereby granted, free of charge, to any person
15
+ obtaining a copy of this software and associated documentation
16
+ files (the “Software”), to deal in the Software without
17
+ restriction, including without limitation the rights to use,
18
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
19
+ copies of the Software, and to permit persons to whom the
20
+ Software is furnished to do so, subject to the following
21
+ conditions:
22
+
23
+ The above copyright notice and this permission notice shall be
24
+ included in all copies or substantial portions of the Software.
25
+
26
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
27
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
28
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
30
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
31
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
32
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
33
+ OTHER DEALINGS IN THE SOFTWARE.
34
+
35
+ Classifier: Development Status :: 3 - Alpha
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Operating System :: MacOS :: MacOS X
38
+ Classifier: Operating System :: Microsoft :: Windows
39
+ Classifier: Operating System :: POSIX :: Linux
40
+ Classifier: Programming Language :: Python :: 3.10
41
+ Classifier: Programming Language :: Python :: 3.11
42
+ Classifier: Programming Language :: Python :: 3.12
43
+ Classifier: Programming Language :: Python :: 3.13
44
+ Classifier: Topic :: Scientific/Engineering :: Physics
45
+ Requires-Python: <4.0.0,>=3.10
46
+ Description-Content-Type: text/markdown
47
+ License-File: LICENCE.md
48
+ Requires-Dist: numpy
49
+ Requires-Dist: pandas
50
+ Requires-Dist: polars
51
+ Requires-Dist: scipy
52
+ Requires-Dist: matplotlib
53
+ Requires-Dist: seaborn
54
+ Requires-Dist: scikit-learn
55
+ Requires-Dist: geopandas
56
+ Requires-Dist: shapely
57
+ Requires-Dist: xarray
58
+ Requires-Dist: netcdf4
59
+ Requires-Dist: tqdm
60
+ Requires-Dist: fastcluster
61
+ Requires-Dist: ladybug-core
62
+ Requires-Dist: colorlog
63
+ Requires-Dist: requests
64
+ Requires-Dist: py7zr
65
+ Requires-Dist: pyproj
66
+ Requires-Dist: buildingmodel
67
+ Requires-Dist: buildingdata[era5]>=0.2.0
68
+ Provides-Extra: maps
69
+ Requires-Dist: contextily; extra == "maps"
70
+ Requires-Dist: folium; extra == "maps"
71
+ Provides-Extra: dev
72
+ Requires-Dist: pytest; extra == "dev"
73
+ Requires-Dist: pytest-cov; extra == "dev"
74
+ Requires-Dist: ruff; extra == "dev"
75
+ Requires-Dist: ipykernel; extra == "dev"
76
+ Dynamic: license-file
77
+
78
+ # Building_eload
79
+
80
+ **Version:** `0.4.0`
81
+
82
+ ![Python Version](https://img.shields.io/badge/python-3.10%2B-blue)
83
+ ![PyPI](https://img.shields.io/pypi/v/building_eload)
84
+ ![Pipeline](https://git.persee.minesparis.psl.eu/planeterr/building_eload/badges/main/pipeline.svg)
85
+
86
+ District-level building energy simulation at hourly resolution.
87
+
88
+ This model was first presented in the research article : <https://doi.org/10.1016/j.enbuild.2026.117409>
89
+
90
+ `building_eload` is developed as a **library**, released on PyPI. The Snakemake
91
+ workflow reproducing the paper's results lives in a separate repository
92
+ (`building_eload_paper`), pinned to the published `0.4.x` releases of this
93
+ package.
94
+
95
+ ## Overview
96
+
97
+ `building_eload` runs a two-stage pipeline:
98
+
99
+ 1. Static simulation: annual building-level energy estimates and calibration.
100
+ 2. Dynamic simulation: hourly electricity load profiles from static outputs.
101
+
102
+ Main dependency:
103
+
104
+ - `buildingmodel` (for static building physics simulation)
105
+
106
+ ## Installation
107
+
108
+ ```bash
109
+ git clone https://git.persee.minesparis.psl.eu/planeterr/building_eload.git
110
+ cd building_eload
111
+ pip install -e .
112
+ ```
113
+
114
+ If needed by your workflow, also install `buildingmodel` in editable mode.
115
+
116
+ ## Data Paths Exposed by the Package
117
+
118
+ Defined in `building_eload/__init__.py`:
119
+
120
+ - `data_path["data"]`
121
+ - `data_path["bdtopo"]`
122
+ - `data_path["elmas"]`
123
+ - `data_path["simulation"]`
124
+ - `data_path["activity_calendar"]`
125
+ - `data_path["validation"]`
126
+ - `data_path["climate"]`
127
+ - `data_path["representative_districts"]`
128
+ - `plot_path`
129
+ - `download_link`
130
+
131
+ ## Data Layout
132
+
133
+ The `data/` directory is **not tracked in git** (multi-GB). All paths in
134
+ `data_path` resolve relative to the repository root:
135
+
136
+ ```
137
+ data/
138
+ ├── activity_calendar/ # occupant activity calendars (10-min time step)
139
+ ├── bdtopo/ # BDTOPO building footprints (downloaded per region)
140
+ ├── calibration/ # static-model calibration outputs
141
+ ├── climate/ # ERA5-derived weather files (see scripts/climate)
142
+ ├── elecdom/ # Elecdom appliance panel data
143
+ ├── elmas/ # ELMAS dataset
144
+ ├── representative_districts/ # clustering outputs (medoid districts)
145
+ ├── simulation/ # static + dynamic simulation results
146
+ └── validation/ # ORE / Enedis / RTE measured consumption
147
+ ```
148
+
149
+ Download sources are listed in `building_eload.download_link` (BDTOPO regions,
150
+ district list, occupant diaries, ORE annual consumption, ELMAS); helpers live
151
+ in `building_eload.utils.data_download`. Reference data (districts, BDTOPO
152
+ fallback) is otherwise fetched through the `buildingdata` package. The unit
153
+ test suite runs without `data/`; the integration tests
154
+ (`StaticSimulation`/`DynamicSimulation`/validation runs) require it.
155
+
156
+ ## Current Core API
157
+
158
+ ### Static simulation (`building_eload.core.static_simulation`)
159
+
160
+ Main classes:
161
+
162
+ - `StaticParameters`
163
+ - `StaticSimulation`
164
+ - `StaticResults`
165
+ - `BuildingModelResults`
166
+ - `StaticProcessor`
167
+
168
+ Primary methods used by users:
169
+
170
+ - `StaticSimulation.run()`
171
+ - `StaticSimulation.run_energy_demand()`
172
+ - `StaticResults.save_results(save_dict: Optional[dict[str, bool]] = None)`
173
+
174
+ ### Dynamic simulation (`building_eload.core.dynamic_simulation`)
175
+
176
+ Main classes:
177
+
178
+ - `DynamicParameters`
179
+ - `DynamicSimulation`
180
+
181
+ Primary methods used by users:
182
+
183
+ - `DynamicSimulation.from_files(district_id, parameters)`
184
+ - `DynamicSimulation.from_static_results(static_results, parameters)`
185
+ - `DynamicSimulation.run()`
186
+ - `DynamicSimulation.save_results()`
187
+ - `DynamicSimulation.plot_results(...)`
188
+
189
+ ## Minimal Usage
190
+
191
+ Tutorials for both simulation processes are available in : `/doc/tutorials`
192
+
193
+ ### 1) Static simulation
194
+
195
+ ```python
196
+ import numpy as np
197
+ from building_eload.core.static_simulation import StaticParameters, StaticSimulation
198
+
199
+
200
+ district_id = "262320000"
201
+ eu = np.arange(0.7, 1.3, 0.05).round(2)
202
+ hs = np.arange(16.0, 22.5, 0.5).round(1)
203
+ energy_use_parameters = [
204
+ {"actual_heating_set_point": hs[i], "energy_use_factor": eu[i]}
205
+ for i in range(len(hs))
206
+ ]
207
+
208
+ params = StaticParameters(
209
+ n_inference=10,
210
+ calibration_year=2023,
211
+ climate_year=None,
212
+ energy_use_parameters=energy_use_parameters,
213
+ )
214
+
215
+ sim = StaticSimulation(district_id=district_id, parameters=params)
216
+ results = sim.run()
217
+ results.save_results()
218
+ ```
219
+
220
+ ### 2) Dynamic simulation
221
+
222
+ ```python
223
+ from building_eload.core.dynamic_simulation import DynamicParameters, DynamicSimulation
224
+
225
+
226
+ district_id = "262320000"
227
+ params = DynamicParameters(
228
+ year=2023,
229
+ run_non_residential=True,
230
+ run_again=True,
231
+ )
232
+
233
+ sim = DynamicSimulation.from_files(district_id=district_id, parameters=params)
234
+ sim.run()
235
+ sim.save_results()
236
+ ```
237
+
238
+ ## Script Entry Points (Current)
239
+
240
+ - Static simulation:
241
+
242
+ ```bash
243
+ python -m building_eload.scripts.simulation.static.run
244
+ ```
245
+
246
+ - Parallel static simulation:
247
+
248
+ ```bash
249
+ python -m building_eload.scripts.simulation.static.run_parallel
250
+ ```
251
+
252
+ - Dynamic simulation:
253
+
254
+ ```bash
255
+ python -m building_eload.scripts.simulation.dynamic.run
256
+ ```
257
+
258
+ - Parallel dynamic simulation:
259
+
260
+ ```bash
261
+ python -m building_eload.scripts.simulation.dynamic.run_parallel
262
+ ```
263
+
264
+ - Validation:
265
+
266
+ ```bash
267
+ python -m building_eload.scripts.validation.run
268
+ ```
269
+
270
+ - Climate preprocessing:
271
+
272
+ ```bash
273
+ python -m building_eload.scripts.climate.generate_climates
274
+ ```
@@ -0,0 +1,197 @@
1
+ # Building_eload
2
+
3
+ **Version:** `0.4.0`
4
+
5
+ ![Python Version](https://img.shields.io/badge/python-3.10%2B-blue)
6
+ ![PyPI](https://img.shields.io/pypi/v/building_eload)
7
+ ![Pipeline](https://git.persee.minesparis.psl.eu/planeterr/building_eload/badges/main/pipeline.svg)
8
+
9
+ District-level building energy simulation at hourly resolution.
10
+
11
+ This model was first presented in the research article : <https://doi.org/10.1016/j.enbuild.2026.117409>
12
+
13
+ `building_eload` is developed as a **library**, released on PyPI. The Snakemake
14
+ workflow reproducing the paper's results lives in a separate repository
15
+ (`building_eload_paper`), pinned to the published `0.4.x` releases of this
16
+ package.
17
+
18
+ ## Overview
19
+
20
+ `building_eload` runs a two-stage pipeline:
21
+
22
+ 1. Static simulation: annual building-level energy estimates and calibration.
23
+ 2. Dynamic simulation: hourly electricity load profiles from static outputs.
24
+
25
+ Main dependency:
26
+
27
+ - `buildingmodel` (for static building physics simulation)
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ git clone https://git.persee.minesparis.psl.eu/planeterr/building_eload.git
33
+ cd building_eload
34
+ pip install -e .
35
+ ```
36
+
37
+ If needed by your workflow, also install `buildingmodel` in editable mode.
38
+
39
+ ## Data Paths Exposed by the Package
40
+
41
+ Defined in `building_eload/__init__.py`:
42
+
43
+ - `data_path["data"]`
44
+ - `data_path["bdtopo"]`
45
+ - `data_path["elmas"]`
46
+ - `data_path["simulation"]`
47
+ - `data_path["activity_calendar"]`
48
+ - `data_path["validation"]`
49
+ - `data_path["climate"]`
50
+ - `data_path["representative_districts"]`
51
+ - `plot_path`
52
+ - `download_link`
53
+
54
+ ## Data Layout
55
+
56
+ The `data/` directory is **not tracked in git** (multi-GB). All paths in
57
+ `data_path` resolve relative to the repository root:
58
+
59
+ ```
60
+ data/
61
+ ├── activity_calendar/ # occupant activity calendars (10-min time step)
62
+ ├── bdtopo/ # BDTOPO building footprints (downloaded per region)
63
+ ├── calibration/ # static-model calibration outputs
64
+ ├── climate/ # ERA5-derived weather files (see scripts/climate)
65
+ ├── elecdom/ # Elecdom appliance panel data
66
+ ├── elmas/ # ELMAS dataset
67
+ ├── representative_districts/ # clustering outputs (medoid districts)
68
+ ├── simulation/ # static + dynamic simulation results
69
+ └── validation/ # ORE / Enedis / RTE measured consumption
70
+ ```
71
+
72
+ Download sources are listed in `building_eload.download_link` (BDTOPO regions,
73
+ district list, occupant diaries, ORE annual consumption, ELMAS); helpers live
74
+ in `building_eload.utils.data_download`. Reference data (districts, BDTOPO
75
+ fallback) is otherwise fetched through the `buildingdata` package. The unit
76
+ test suite runs without `data/`; the integration tests
77
+ (`StaticSimulation`/`DynamicSimulation`/validation runs) require it.
78
+
79
+ ## Current Core API
80
+
81
+ ### Static simulation (`building_eload.core.static_simulation`)
82
+
83
+ Main classes:
84
+
85
+ - `StaticParameters`
86
+ - `StaticSimulation`
87
+ - `StaticResults`
88
+ - `BuildingModelResults`
89
+ - `StaticProcessor`
90
+
91
+ Primary methods used by users:
92
+
93
+ - `StaticSimulation.run()`
94
+ - `StaticSimulation.run_energy_demand()`
95
+ - `StaticResults.save_results(save_dict: Optional[dict[str, bool]] = None)`
96
+
97
+ ### Dynamic simulation (`building_eload.core.dynamic_simulation`)
98
+
99
+ Main classes:
100
+
101
+ - `DynamicParameters`
102
+ - `DynamicSimulation`
103
+
104
+ Primary methods used by users:
105
+
106
+ - `DynamicSimulation.from_files(district_id, parameters)`
107
+ - `DynamicSimulation.from_static_results(static_results, parameters)`
108
+ - `DynamicSimulation.run()`
109
+ - `DynamicSimulation.save_results()`
110
+ - `DynamicSimulation.plot_results(...)`
111
+
112
+ ## Minimal Usage
113
+
114
+ Tutorials for both simulation processes are available in : `/doc/tutorials`
115
+
116
+ ### 1) Static simulation
117
+
118
+ ```python
119
+ import numpy as np
120
+ from building_eload.core.static_simulation import StaticParameters, StaticSimulation
121
+
122
+
123
+ district_id = "262320000"
124
+ eu = np.arange(0.7, 1.3, 0.05).round(2)
125
+ hs = np.arange(16.0, 22.5, 0.5).round(1)
126
+ energy_use_parameters = [
127
+ {"actual_heating_set_point": hs[i], "energy_use_factor": eu[i]}
128
+ for i in range(len(hs))
129
+ ]
130
+
131
+ params = StaticParameters(
132
+ n_inference=10,
133
+ calibration_year=2023,
134
+ climate_year=None,
135
+ energy_use_parameters=energy_use_parameters,
136
+ )
137
+
138
+ sim = StaticSimulation(district_id=district_id, parameters=params)
139
+ results = sim.run()
140
+ results.save_results()
141
+ ```
142
+
143
+ ### 2) Dynamic simulation
144
+
145
+ ```python
146
+ from building_eload.core.dynamic_simulation import DynamicParameters, DynamicSimulation
147
+
148
+
149
+ district_id = "262320000"
150
+ params = DynamicParameters(
151
+ year=2023,
152
+ run_non_residential=True,
153
+ run_again=True,
154
+ )
155
+
156
+ sim = DynamicSimulation.from_files(district_id=district_id, parameters=params)
157
+ sim.run()
158
+ sim.save_results()
159
+ ```
160
+
161
+ ## Script Entry Points (Current)
162
+
163
+ - Static simulation:
164
+
165
+ ```bash
166
+ python -m building_eload.scripts.simulation.static.run
167
+ ```
168
+
169
+ - Parallel static simulation:
170
+
171
+ ```bash
172
+ python -m building_eload.scripts.simulation.static.run_parallel
173
+ ```
174
+
175
+ - Dynamic simulation:
176
+
177
+ ```bash
178
+ python -m building_eload.scripts.simulation.dynamic.run
179
+ ```
180
+
181
+ - Parallel dynamic simulation:
182
+
183
+ ```bash
184
+ python -m building_eload.scripts.simulation.dynamic.run_parallel
185
+ ```
186
+
187
+ - Validation:
188
+
189
+ ```bash
190
+ python -m building_eload.scripts.validation.run
191
+ ```
192
+
193
+ - Climate preprocessing:
194
+
195
+ ```bash
196
+ python -m building_eload.scripts.climate.generate_climates
197
+ ```
@@ -0,0 +1,31 @@
1
+ from pathlib import Path
2
+
3
+ # Utilisation de Path(__file__).parents[1] pour naviguer vers le bon répertoire parent
4
+ data_path = {
5
+ "data": Path(__file__).parents[1] / "data",
6
+ "bdtopo": Path(__file__).parents[1] / "data" / "bdtopo",
7
+ "elmas": Path(__file__).parents[1] / "data" / "elmas",
8
+ "simulation": Path(__file__).parents[1] / "data" / "simulation",
9
+ "activity_calendar": Path(__file__).parents[1] / "data" / "activity_calendar",
10
+ "validation": Path(__file__).parents[1] / "data" / "validation",
11
+ "climate": Path(__file__).parents[1] / "data" / "climate",
12
+ "representative_districts": Path(__file__).parents[1] / "data" / "representative_districts",
13
+ }
14
+
15
+ plot_path = Path(__file__).parents[1] / "plot"
16
+
17
+ # NOTE: the former ``download_link`` dict and ``utils.data_download`` helpers
18
+ # (``download_and_extract``/``flatten_folder``) were removed in 0.5.0. Reference
19
+ # dataset acquisition now belongs entirely to the ``buildingdata`` package,
20
+ # which owns the GCS-cached download+schema contract for every dataset this
21
+ # model consumes:
22
+ # - ELMAS non-residential load curves -> ``buildingdata.get_elmas(table=...)``
23
+ # (wired into ``models.non_residential.NonResidentialModel``)
24
+ # - occupant activity diaries -> ``buildingdata.get_occupant_diaries()``
25
+ # - ORE per-IRIS annual consumption -> ``buildingdata.get_ore()``
26
+ # - Enedis measured conso-inf36 -> ``buildingdata.get_enedis_national()`` /
27
+ # ``buildingdata.get_enedis_regional()``
28
+ # - IRIS districts / BDTOPO / census / DPE -> already via ``buildingdata``.
29
+ # The old Google-Drive links silently saved Google's "file too large to scan"
30
+ # HTML interstitial instead of the archive; moving to ``buildingdata`` closes
31
+ # that bug. See the buildingdata CHANGELOG (0.2.0) and its doc/pending_uploads.md.
File without changes
@@ -0,0 +1,127 @@
1
+ # building_loader.py
2
+
3
+ import logging
4
+ import warnings
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ import buildingdata
9
+ import geopandas as gpd
10
+ from shapely import geometry
11
+
12
+ from building_eload import data_path
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+
18
+ class BuildingLoader:
19
+ """Load buildings from a district and filter them based on intersection with the district geometry."""
20
+
21
+ INTERSECTION_THRESHOLD = 0.5
22
+ BUILDING_LAYER_NAME = "batiment"
23
+ GIS_ENGINE = "pyogrio"
24
+ BUILDING_FILE_PREFIX = "BDTOPO_R"
25
+ BUILDING_FILE_SUFFIX = ".gpkg"
26
+ DEFAULT_DISTRICT_DATA_FILE = None
27
+ DEFAULT_BUILDING_FOOTPRINT_FOLDER = Path(data_path["bdtopo"])
28
+
29
+ def __init__(self, district_id: str, district_data_file: str = None, building_footprint_folder: str = None):
30
+
31
+ self.district_id = str(district_id)
32
+ self.district_data_file = (
33
+ Path(district_data_file) if district_data_file is not None else self.DEFAULT_DISTRICT_DATA_FILE
34
+ )
35
+ self.building_footprint_folder = Path(
36
+ building_footprint_folder or self.DEFAULT_BUILDING_FOOTPRINT_FOLDER
37
+ )
38
+ logger.debug(f"{self.logger_header} District data file: {self.district_data_file}.")
39
+ logger.debug(f"{self.logger_header} Building footprint folder: {self.building_footprint_folder}.")
40
+
41
+ @property
42
+ def logger_header(self):
43
+ if self.district_id is None:
44
+ return "[BuildingLoader]"
45
+ return f"[District {self.district_id}]"
46
+
47
+ # -------------------------------------------------------
48
+ # Main function
49
+ # -------------------------------------------------------
50
+
51
+ def load_buildings(self) -> Optional[gpd.GeoDataFrame]:
52
+ """Load buildings for a specific district."""
53
+ logger.info(f"{self.logger_header} Loading buildings.")
54
+
55
+ district_data = self._load_district(self.district_id)
56
+ if district_data.empty:
57
+ logger.warning(f"{self.logger_header} District {self.district_id} not found in district file.")
58
+ return None
59
+ district_geometry = self._get_district_geometry(district_data)
60
+ buildings = self._load_buildings_in_district_bounds(district_geometry, district_data)
61
+ filtered_buildings = self._filter_buildings_on_bounds(buildings, district_geometry)
62
+ filtered_buildings["district"] = self.district_id
63
+
64
+
65
+ logger.info(f"{self.logger_header} Loaded {len(filtered_buildings)} buildings.")
66
+
67
+ return filtered_buildings
68
+
69
+ # -------------------------------------------------------
70
+ # Utilis
71
+ # -------------------------------------------------------
72
+
73
+ def _load_district(self, district_id: str) -> gpd.GeoDataFrame:
74
+ district_id = str(district_id)
75
+ if self.district_data_file is not None:
76
+ districts = gpd.read_parquet(self.district_data_file)
77
+ else:
78
+ districts = buildingdata.get_districts()
79
+
80
+ district_codes = districts["district"].astype(str).str.zfill(len(district_id))
81
+ return districts[district_codes == district_id]
82
+
83
+ @staticmethod
84
+ def _get_district_geometry(district_data: gpd.GeoDataFrame) -> geometry.Polygon:
85
+ return district_data["geometry"].iloc[0]
86
+
87
+ def _load_buildings_in_district_bounds(
88
+ self, district_geometry: geometry.Polygon, district_data: gpd.GeoDataFrame
89
+ ) -> gpd.GeoDataFrame:
90
+ bounds = district_geometry.convex_hull.bounds
91
+ building_path = self._get_building_path(district_data)
92
+
93
+ if not building_path.exists():
94
+ logger.debug(
95
+ f"{self.logger_header} Local BDTOPO file {building_path} not found; "
96
+ "loading buildings through buildingdata."
97
+ )
98
+ return buildingdata.get_bdtopo(self.district_id)
99
+
100
+ with warnings.catch_warnings():
101
+ warnings.filterwarnings("ignore")
102
+ logger.debug(f"{self.logger_header} Loading buildings from {building_path}.")
103
+ return gpd.read_file(
104
+ building_path,
105
+ layer=self.BUILDING_LAYER_NAME,
106
+ engine=self.GIS_ENGINE,
107
+ bbox=bounds,
108
+ )
109
+
110
+ def _filter_buildings_on_bounds(
111
+ self, buildings: gpd.GeoDataFrame, district_geometry: geometry.Polygon
112
+ ) -> gpd.GeoDataFrame:
113
+ buildings["intersection_share"] = (
114
+ buildings["geometry"].intersection(district_geometry).area / buildings["geometry"].area
115
+ )
116
+ filtered_buildings = buildings.loc[
117
+ buildings.intersection_share >= self.INTERSECTION_THRESHOLD
118
+ ].copy()
119
+ filtered_buildings.drop(columns=["intersection_share"], inplace=True)
120
+ return filtered_buildings
121
+
122
+ def _get_building_path(self, district_data: gpd.GeoDataFrame) -> Path:
123
+ region_id = int(district_data["region"].iloc[0])
124
+ return (
125
+ self.building_footprint_folder
126
+ / f"{self.BUILDING_FILE_PREFIX}{region_id}{self.BUILDING_FILE_SUFFIX}"
127
+ )