cetk 0.0.4.dev0__py3-none-any.whl
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.
- cetk/__init__.py +87 -0
- cetk/db.py +41 -0
- cetk/edb/__init__.py +0 -0
- cetk/edb/apps.py +9 -0
- cetk/edb/cache.py +249 -0
- cetk/edb/const.py +26 -0
- cetk/edb/copy.py +18 -0
- cetk/edb/exporters.py +551 -0
- cetk/edb/importers/__init__.py +36 -0
- cetk/edb/importers/activity_import.py +279 -0
- cetk/edb/importers/codeset_import.py +124 -0
- cetk/edb/importers/gridsource_import.py +256 -0
- cetk/edb/importers/roadsource_import.py +1602 -0
- cetk/edb/importers/source_import.py +958 -0
- cetk/edb/importers/timevar_import.py +115 -0
- cetk/edb/importers/utils.py +92 -0
- cetk/edb/importers/validation.py +207 -0
- cetk/edb/ltreefield.py +76 -0
- cetk/edb/migrations/0001_initial.py +1731 -0
- cetk/edb/migrations/0002_data.py +156 -0
- cetk/edb/migrations/0003_create_gpkg_tables.py +14 -0
- cetk/edb/migrations/__init__.py +0 -0
- cetk/edb/models/__init__.py +49 -0
- cetk/edb/models/base.py +52 -0
- cetk/edb/models/common_models.py +144 -0
- cetk/edb/models/fleets.py +160 -0
- cetk/edb/models/gridsource_models.py +163 -0
- cetk/edb/models/road_classes.py +197 -0
- cetk/edb/models/road_models.py +413 -0
- cetk/edb/models/source_models.py +399 -0
- cetk/edb/models/timevar_models.py +111 -0
- cetk/edb/rasterize/__init__.py +1 -0
- cetk/edb/rasterize/rasterizer.py +1544 -0
- cetk/edb/signals.py +43 -0
- cetk/edb/traffic.py +75 -0
- cetk/edb/units.py +160 -0
- cetk/emissions/__init__.py +0 -0
- cetk/emissions/aggregate_emissions.sql +303 -0
- cetk/emissions/areasource_emissions.sql +53 -0
- cetk/emissions/calc.py +148 -0
- cetk/emissions/filters.py +179 -0
- cetk/emissions/gridsource_emissions.sql +52 -0
- cetk/emissions/pointsource_emissions.sql +53 -0
- cetk/emissions/queries.py +221 -0
- cetk/emissions/roadsource_emissions.sql +211 -0
- cetk/emissions/used_substances.sql +56 -0
- cetk/emissions/views.py +89 -0
- cetk/logging.py +68 -0
- cetk/settings.py +85 -0
- cetk/tools/__init__.py +1 -0
- cetk/tools/cetk_command.py +599 -0
- cetk/tools/manage.py +24 -0
- cetk/tools/utils.py +334 -0
- cetk/utils.py +62 -0
- cetk-0.0.4.dev0.dist-info/LICENSE +201 -0
- cetk-0.0.4.dev0.dist-info/METADATA +297 -0
- cetk-0.0.4.dev0.dist-info/RECORD +60 -0
- cetk-0.0.4.dev0.dist-info/WHEEL +5 -0
- cetk-0.0.4.dev0.dist-info/entry_points.txt +3 -0
- cetk-0.0.4.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""import activities and emission-factors."""
|
|
2
|
+
|
|
3
|
+
from django.db import IntegrityError
|
|
4
|
+
|
|
5
|
+
from cetk.edb.cache import cache_queryset
|
|
6
|
+
from cetk.edb.models import (
|
|
7
|
+
Activity,
|
|
8
|
+
AreaSource,
|
|
9
|
+
AreaSourceActivity,
|
|
10
|
+
EmissionFactor,
|
|
11
|
+
Facility,
|
|
12
|
+
GridSource,
|
|
13
|
+
GridSourceActivity,
|
|
14
|
+
PointSource,
|
|
15
|
+
PointSourceActivity,
|
|
16
|
+
Substance,
|
|
17
|
+
)
|
|
18
|
+
from cetk.edb.units import activity_ef_unit_to_si
|
|
19
|
+
|
|
20
|
+
from .utils import import_error, worksheet_to_dataframe
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def import_emissionfactorsheet(workbook, validation):
|
|
24
|
+
return_dict = {}
|
|
25
|
+
return_message = []
|
|
26
|
+
activities = cache_queryset(
|
|
27
|
+
Activity.objects.prefetch_related("emissionfactors").all(), "name"
|
|
28
|
+
)
|
|
29
|
+
data = workbook["EmissionFactor"].values
|
|
30
|
+
df_activity = worksheet_to_dataframe(data)
|
|
31
|
+
|
|
32
|
+
activity_names = df_activity["activity_name"]
|
|
33
|
+
update_activities = {}
|
|
34
|
+
create_activities = {}
|
|
35
|
+
drop_emfacs = []
|
|
36
|
+
for row_nr, activity_name in enumerate(activity_names):
|
|
37
|
+
try:
|
|
38
|
+
activity = activities[activity_name]
|
|
39
|
+
if activity_name not in update_activities.keys():
|
|
40
|
+
if df_activity["activity_unit"][row_nr] != activity.unit:
|
|
41
|
+
# check if all sources with this activity are updated.
|
|
42
|
+
error_message = (
|
|
43
|
+
"Changing activity rate unit for activity "
|
|
44
|
+
f"{activity_name} without updating all sources"
|
|
45
|
+
" in inventory with this activity. Define as new"
|
|
46
|
+
" activity by giving new name, or update all"
|
|
47
|
+
" sources with this activity simultaneously."
|
|
48
|
+
)
|
|
49
|
+
pointsource_ids = [
|
|
50
|
+
psa.source_id
|
|
51
|
+
for psa in PointSourceActivity.objects.filter(
|
|
52
|
+
activity_id=activity.id
|
|
53
|
+
)
|
|
54
|
+
]
|
|
55
|
+
if len(pointsource_ids) > 0:
|
|
56
|
+
pointsources = [
|
|
57
|
+
PointSource.objects.get(id=i) for i in pointsource_ids
|
|
58
|
+
]
|
|
59
|
+
facility_names = [
|
|
60
|
+
Facility.objects.get(id=source.facility_id).name
|
|
61
|
+
for source in pointsources
|
|
62
|
+
]
|
|
63
|
+
source_names = [source.name for source in pointsources]
|
|
64
|
+
if "PointSource" in workbook.sheetnames:
|
|
65
|
+
new_pointsources = worksheet_to_dataframe(
|
|
66
|
+
workbook["PointSource"].values
|
|
67
|
+
)
|
|
68
|
+
for facility, source in zip(facility_names, source_names):
|
|
69
|
+
row_exists = (
|
|
70
|
+
(new_pointsources["source_name"] == source)
|
|
71
|
+
& (new_pointsources["facility_name"] == facility)
|
|
72
|
+
).any()
|
|
73
|
+
if not row_exists:
|
|
74
|
+
return_message.append(
|
|
75
|
+
import_error(
|
|
76
|
+
error_message, validation=validation
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
else:
|
|
80
|
+
return_message.append(
|
|
81
|
+
import_error(error_message, validation=validation)
|
|
82
|
+
)
|
|
83
|
+
areasource_ids = [
|
|
84
|
+
psa.source_id
|
|
85
|
+
for psa in AreaSourceActivity.objects.filter(
|
|
86
|
+
activity_id=activity.id
|
|
87
|
+
)
|
|
88
|
+
]
|
|
89
|
+
if len(areasource_ids) > 0:
|
|
90
|
+
areasources = [
|
|
91
|
+
AreaSource.objects.get(id=i) for i in areasource_ids
|
|
92
|
+
]
|
|
93
|
+
facility_names = [
|
|
94
|
+
Facility.objects.get(id=source.facility_id).name
|
|
95
|
+
for source in areasources
|
|
96
|
+
]
|
|
97
|
+
source_names = [source.name for source in areasources]
|
|
98
|
+
if "AreaSource" in workbook.sheetnames:
|
|
99
|
+
new_areasources = worksheet_to_dataframe(
|
|
100
|
+
workbook["AreaSource"].values
|
|
101
|
+
)
|
|
102
|
+
for facility, source in zip(facility_names, source_names):
|
|
103
|
+
row_exists = (
|
|
104
|
+
(new_areasources["source_name"] == source)
|
|
105
|
+
& (new_areasources["facility_name"] == facility)
|
|
106
|
+
).any()
|
|
107
|
+
if not row_exists:
|
|
108
|
+
return_message.append(
|
|
109
|
+
import_error(
|
|
110
|
+
error_message, validation=validation
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
else:
|
|
114
|
+
return_message.append(
|
|
115
|
+
import_error(error_message, validation=validation)
|
|
116
|
+
)
|
|
117
|
+
gridsource_ids = [
|
|
118
|
+
psa.source_id
|
|
119
|
+
for psa in GridSourceActivity.objects.filter(
|
|
120
|
+
activity_id=activity.id
|
|
121
|
+
)
|
|
122
|
+
]
|
|
123
|
+
if len(gridsource_ids) > 0:
|
|
124
|
+
gridsources = [
|
|
125
|
+
GridSource.objects.get(id=i) for i in gridsource_ids
|
|
126
|
+
]
|
|
127
|
+
source_names = [source.name for source in gridsources]
|
|
128
|
+
if "GridSource" in workbook.sheetnames:
|
|
129
|
+
update_sources = [
|
|
130
|
+
name
|
|
131
|
+
in worksheet_to_dataframe(
|
|
132
|
+
workbook["GridSource"].values
|
|
133
|
+
)["name"]
|
|
134
|
+
for name in gridsources
|
|
135
|
+
]
|
|
136
|
+
if not all(update_sources):
|
|
137
|
+
return_message.append(
|
|
138
|
+
import_error(
|
|
139
|
+
error_message,
|
|
140
|
+
validation=validation,
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
else:
|
|
144
|
+
return_message.append(
|
|
145
|
+
import_error(error_message, validation=validation)
|
|
146
|
+
)
|
|
147
|
+
setattr(activity, "unit", df_activity["activity_unit"][row_nr])
|
|
148
|
+
update_activities[activity_name] = activity
|
|
149
|
+
drop_emfacs += list(activities[activity_name].emissionfactors.all())
|
|
150
|
+
elif (
|
|
151
|
+
df_activity["activity_unit"][row_nr]
|
|
152
|
+
!= update_activities[activity_name].unit
|
|
153
|
+
):
|
|
154
|
+
return_message.append(
|
|
155
|
+
import_error(
|
|
156
|
+
f"conflicting units for activity '{activity_name}'",
|
|
157
|
+
validation=validation,
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
except KeyError:
|
|
161
|
+
if activity_name not in create_activities:
|
|
162
|
+
activity = Activity(
|
|
163
|
+
name=activity_name, unit=df_activity["activity_unit"][row_nr]
|
|
164
|
+
)
|
|
165
|
+
create_activities[activity_name] = activity
|
|
166
|
+
else:
|
|
167
|
+
if (
|
|
168
|
+
df_activity["activity_unit"][row_nr]
|
|
169
|
+
!= create_activities[activity_name].unit
|
|
170
|
+
):
|
|
171
|
+
return_message.append(
|
|
172
|
+
import_error(
|
|
173
|
+
"multiple rows for the same activity " + str(activity_name),
|
|
174
|
+
validation=validation,
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
Activity.objects.bulk_create(create_activities.values())
|
|
178
|
+
Activity.objects.bulk_update(
|
|
179
|
+
update_activities.values(),
|
|
180
|
+
[
|
|
181
|
+
"name",
|
|
182
|
+
"unit",
|
|
183
|
+
],
|
|
184
|
+
)
|
|
185
|
+
# drop existing emfacs of activities that will be updated
|
|
186
|
+
EmissionFactor.objects.filter(pk__in=[inst.id for inst in drop_emfacs]).delete()
|
|
187
|
+
return_dict.update(
|
|
188
|
+
{
|
|
189
|
+
"activity": {
|
|
190
|
+
"updated": len(update_activities),
|
|
191
|
+
"created": len(create_activities),
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
data = workbook["EmissionFactor"].values
|
|
196
|
+
df_emfac = worksheet_to_dataframe(data)
|
|
197
|
+
substances = cache_queryset(Substance.objects.all(), "slug")
|
|
198
|
+
activities = cache_queryset(Activity.objects.all(), "name")
|
|
199
|
+
# unique together activity_name and substance
|
|
200
|
+
emissionfactors = cache_queryset(
|
|
201
|
+
EmissionFactor.objects.all(), ["activity", "substance"]
|
|
202
|
+
)
|
|
203
|
+
update_emfacs = []
|
|
204
|
+
create_emfacs = []
|
|
205
|
+
for row_nr in range(len(df_emfac)):
|
|
206
|
+
activity_name = df_emfac.iloc[row_nr]["activity_name"]
|
|
207
|
+
try:
|
|
208
|
+
activity = activities[activity_name]
|
|
209
|
+
subst = df_emfac.iloc[row_nr]["substance"]
|
|
210
|
+
try:
|
|
211
|
+
substance = substances[subst]
|
|
212
|
+
factor = df_emfac.iloc[row_nr]["factor"]
|
|
213
|
+
factor_unit = df_emfac.iloc[row_nr]["emissionfactor_unit"]
|
|
214
|
+
activity_quantity_unit, time_unit = activity.unit.split("/")
|
|
215
|
+
mass_unit, factor_quantity_unit = factor_unit.split("/")
|
|
216
|
+
if activity_quantity_unit != factor_quantity_unit:
|
|
217
|
+
# emission factor and activity need to have the same unit
|
|
218
|
+
# for quantity, eg GJ, m3 "pellets", number of produces bottles
|
|
219
|
+
return_message.append(
|
|
220
|
+
import_error(
|
|
221
|
+
"Units for emission factor and activity rate for"
|
|
222
|
+
f" '{activity_name}'"
|
|
223
|
+
" are inconsistent, convert units before importing.",
|
|
224
|
+
validation=validation,
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
else:
|
|
228
|
+
factor = activity_ef_unit_to_si(factor, factor_unit)
|
|
229
|
+
try:
|
|
230
|
+
emfac = emissionfactors[(activity, substance)]
|
|
231
|
+
setattr(emfac, "activity", activity)
|
|
232
|
+
setattr(emfac, "substance", substance)
|
|
233
|
+
setattr(emfac, "factor", factor)
|
|
234
|
+
update_emfacs.append(emfac)
|
|
235
|
+
except KeyError:
|
|
236
|
+
emfac = EmissionFactor(
|
|
237
|
+
activity=activity, substance=substance, factor=factor
|
|
238
|
+
)
|
|
239
|
+
create_emfacs.append(emfac)
|
|
240
|
+
except KeyError:
|
|
241
|
+
if subst == "PM2.5":
|
|
242
|
+
substance = substances["PM25"]
|
|
243
|
+
else:
|
|
244
|
+
return_message.append(
|
|
245
|
+
import_error(
|
|
246
|
+
f"unknown substance '{subst}'"
|
|
247
|
+
f" for emission factor on row '{row_nr}'",
|
|
248
|
+
validation=validation,
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
except KeyError:
|
|
252
|
+
return_message.append(
|
|
253
|
+
import_error(
|
|
254
|
+
f"unknown activity '{activity_name}'"
|
|
255
|
+
f" for emission factor on row '{row_nr}'",
|
|
256
|
+
validation=validation,
|
|
257
|
+
)
|
|
258
|
+
)
|
|
259
|
+
try:
|
|
260
|
+
EmissionFactor.objects.bulk_create(create_emfacs)
|
|
261
|
+
except IntegrityError:
|
|
262
|
+
return_message.append(
|
|
263
|
+
import_error(
|
|
264
|
+
"Two emission factors for same activity and substance are given.",
|
|
265
|
+
validation=validation,
|
|
266
|
+
)
|
|
267
|
+
)
|
|
268
|
+
EmissionFactor.objects.bulk_update(
|
|
269
|
+
update_emfacs, ["activity", "substance", "factor"]
|
|
270
|
+
)
|
|
271
|
+
return_dict.update(
|
|
272
|
+
{
|
|
273
|
+
"emission_factors": {
|
|
274
|
+
"updated": len(update_emfacs),
|
|
275
|
+
"created": len(create_emfacs),
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
)
|
|
279
|
+
return return_dict, return_message
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from cetk.edb.models import ActivityCode, CodeSet, VerticalDist
|
|
2
|
+
|
|
3
|
+
from .utils import import_error, worksheet_to_dataframe
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def import_codesetsheet(workbook, validation):
|
|
7
|
+
return_message = []
|
|
8
|
+
return_dict = {}
|
|
9
|
+
nr_codesets = CodeSet.objects.count()
|
|
10
|
+
data = workbook["CodeSet"].values
|
|
11
|
+
df_codeset = worksheet_to_dataframe(data)
|
|
12
|
+
slugs = df_codeset["slug"]
|
|
13
|
+
update_codesets = []
|
|
14
|
+
create_codesets = {}
|
|
15
|
+
for row_nr, slug in enumerate(slugs):
|
|
16
|
+
try:
|
|
17
|
+
codeset = CodeSet.objects.get(slug=slug)
|
|
18
|
+
setattr(codeset, "name", df_codeset["name"][row_nr])
|
|
19
|
+
setattr(codeset, "description", df_codeset["description"][row_nr])
|
|
20
|
+
update_codesets.append(codeset)
|
|
21
|
+
except CodeSet.DoesNotExist:
|
|
22
|
+
if nr_codesets + len(create_codesets) < 3:
|
|
23
|
+
codeset = CodeSet(
|
|
24
|
+
name=df_codeset["name"][row_nr],
|
|
25
|
+
slug=slug,
|
|
26
|
+
description=df_codeset["description"][row_nr],
|
|
27
|
+
)
|
|
28
|
+
if slug not in create_codesets:
|
|
29
|
+
create_codesets[slug] = codeset
|
|
30
|
+
else:
|
|
31
|
+
return_message.append(
|
|
32
|
+
import_error(
|
|
33
|
+
"Trying to import a new codeset," " but can have maximum 3.",
|
|
34
|
+
validation=validation,
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
CodeSet.objects.bulk_create(create_codesets.values())
|
|
39
|
+
CodeSet.objects.bulk_update(
|
|
40
|
+
update_codesets,
|
|
41
|
+
[
|
|
42
|
+
"name",
|
|
43
|
+
"description",
|
|
44
|
+
],
|
|
45
|
+
)
|
|
46
|
+
return_dict = {
|
|
47
|
+
"codeset": {
|
|
48
|
+
"updated": len(update_codesets),
|
|
49
|
+
"created": len(create_codesets),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return return_dict, return_message
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def import_activitycodesheet(workbook, validation):
|
|
56
|
+
return_message = []
|
|
57
|
+
data = workbook["ActivityCode"].values
|
|
58
|
+
df_activitycode = worksheet_to_dataframe(data)
|
|
59
|
+
update_activitycodes = []
|
|
60
|
+
create_activitycodes = {}
|
|
61
|
+
for row_key, row in df_activitycode.iterrows():
|
|
62
|
+
row_dict = row.to_dict()
|
|
63
|
+
try:
|
|
64
|
+
codeset = CodeSet.objects.get(slug=row_dict["codeset_slug"])
|
|
65
|
+
if "vertical_distribution_slug" in row_dict:
|
|
66
|
+
if row_dict["vertical_distribution_slug"] is not None:
|
|
67
|
+
try:
|
|
68
|
+
vdist = VerticalDist.objects.get(
|
|
69
|
+
slug=row_dict["vertical_distribution_slug"]
|
|
70
|
+
)
|
|
71
|
+
vdist_id = vdist.id
|
|
72
|
+
except VerticalDist.DoesNotExist:
|
|
73
|
+
return_message.append(
|
|
74
|
+
import_error(
|
|
75
|
+
f"Trying to import an activity-code "
|
|
76
|
+
"from row '{row_key}' but "
|
|
77
|
+
"Vertical Distribution "
|
|
78
|
+
f"'{row_dict['vertical_distribution_slug']}'"
|
|
79
|
+
" is not defined.",
|
|
80
|
+
validation=validation,
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
else:
|
|
84
|
+
vdist_id = None
|
|
85
|
+
else:
|
|
86
|
+
vdist_id = None
|
|
87
|
+
try:
|
|
88
|
+
activitycode = ActivityCode.objects.get(
|
|
89
|
+
code_set_id=codeset.id, code=row_dict["activitycode"]
|
|
90
|
+
)
|
|
91
|
+
setattr(activitycode, "label", row_dict["label"])
|
|
92
|
+
setattr(activitycode, "vertical_dist_id", vdist_id)
|
|
93
|
+
update_activitycodes.append(activitycode)
|
|
94
|
+
except ActivityCode.DoesNotExist:
|
|
95
|
+
activitycode = ActivityCode(
|
|
96
|
+
code=row_dict["activitycode"],
|
|
97
|
+
label=row_dict["label"],
|
|
98
|
+
code_set_id=codeset.id,
|
|
99
|
+
vertical_dist_id=vdist_id,
|
|
100
|
+
)
|
|
101
|
+
create_activitycodes[row_dict["activitycode"]] = activitycode
|
|
102
|
+
except CodeSet.DoesNotExist:
|
|
103
|
+
return_message.append(
|
|
104
|
+
import_error(
|
|
105
|
+
f"Trying to import an activity code from row '{row_key}'"
|
|
106
|
+
f"but CodeSet '{row_dict['codeset_slug']}' is not defined.",
|
|
107
|
+
validation=validation,
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
ActivityCode.objects.bulk_create(create_activitycodes.values())
|
|
111
|
+
ActivityCode.objects.bulk_update(
|
|
112
|
+
update_activitycodes,
|
|
113
|
+
[
|
|
114
|
+
"label",
|
|
115
|
+
"vertical_dist_id",
|
|
116
|
+
],
|
|
117
|
+
)
|
|
118
|
+
return_dict = {
|
|
119
|
+
"activitycode": {
|
|
120
|
+
"updated": len(update_activitycodes),
|
|
121
|
+
"created": len(create_activitycodes),
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return return_dict, return_message
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from os.path import dirname
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import rasterio as rio
|
|
7
|
+
from openpyxl import load_workbook
|
|
8
|
+
|
|
9
|
+
from cetk.edb.cache import cache_queryset
|
|
10
|
+
from cetk.edb.models import (
|
|
11
|
+
Activity,
|
|
12
|
+
GridSource,
|
|
13
|
+
Substance,
|
|
14
|
+
Timevar,
|
|
15
|
+
drop_gridsource_raster,
|
|
16
|
+
list_gridsource_rasters,
|
|
17
|
+
write_gridsource_raster,
|
|
18
|
+
)
|
|
19
|
+
from cetk.edb.units import activity_rate_unit_to_si, emission_unit_to_si
|
|
20
|
+
|
|
21
|
+
from .utils import (
|
|
22
|
+
cache_codesets,
|
|
23
|
+
get_activity_rate_columns,
|
|
24
|
+
get_substance_emission_columns,
|
|
25
|
+
nan2None,
|
|
26
|
+
worksheet_to_dataframe,
|
|
27
|
+
)
|
|
28
|
+
from .validation import (
|
|
29
|
+
data_to_raster,
|
|
30
|
+
validate_activity,
|
|
31
|
+
validate_activitycodes,
|
|
32
|
+
validate_columns,
|
|
33
|
+
validate_emission,
|
|
34
|
+
validate_raster,
|
|
35
|
+
validate_timevar,
|
|
36
|
+
validate_unit,
|
|
37
|
+
with_rownr_and_substance,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
REQUIRED_COLUMNS_GRID = ["name", "rastername", "timevar", "path", "emission_unit"]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
log = logging.getLogger(__name__)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def read_csv(filepath, encoding=None):
|
|
47
|
+
with open(filepath, encoding=encoding or "utf-8") as csvfile:
|
|
48
|
+
log.debug("reading point-sources from csv-file")
|
|
49
|
+
df = pd.read_csv(
|
|
50
|
+
csvfile,
|
|
51
|
+
sep=";",
|
|
52
|
+
skip_blank_lines=True,
|
|
53
|
+
comment="#",
|
|
54
|
+
dtype=np._str,
|
|
55
|
+
)
|
|
56
|
+
for col in REQUIRED_COLUMNS_GRID:
|
|
57
|
+
if col not in df.columns:
|
|
58
|
+
raise ImportError(f"Missing required column '{col}'")
|
|
59
|
+
return df
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def read_xlsx(filepath):
|
|
63
|
+
try:
|
|
64
|
+
workbook = load_workbook(filename=filepath, data_only=True, read_only=True)
|
|
65
|
+
except Exception as exc:
|
|
66
|
+
raise ImportError(f"could not read workbook: {exc}")
|
|
67
|
+
if "GridSource" not in workbook:
|
|
68
|
+
raise ImportError("no 'GridSource' sheet found")
|
|
69
|
+
|
|
70
|
+
worksheet = workbook["GridSource"]
|
|
71
|
+
data = worksheet.values
|
|
72
|
+
df = worksheet_to_dataframe(data)
|
|
73
|
+
df = df.astype(dtype="string")
|
|
74
|
+
workbook.close()
|
|
75
|
+
return df
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def read_import_file(filepath, encoding=None):
|
|
79
|
+
"""read import file."""
|
|
80
|
+
if filepath.suffix == ".csv":
|
|
81
|
+
df = read_csv(filepath, encoding=encoding)
|
|
82
|
+
elif filepath.suffix == ".xlsx":
|
|
83
|
+
df = read_xlsx(filepath)
|
|
84
|
+
else:
|
|
85
|
+
raise ImportError("only xlsx and csv files are supported for import")
|
|
86
|
+
if "name" not in df.columns:
|
|
87
|
+
raise ImportError("Missing column 'name' for in GridSource sheet")
|
|
88
|
+
df = df.set_index("name")
|
|
89
|
+
return df
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def validate_gridsources(df, timevars, code_sets, raster_names, datadir):
|
|
93
|
+
"""validate grid-sources in dataframe but do not write to database.
|
|
94
|
+
returns (rasters, messages)
|
|
95
|
+
where rasters is a nested dict with all gridsource rasters
|
|
96
|
+
and messages is a list of all validation error messages.
|
|
97
|
+
"""
|
|
98
|
+
row_nr = 2
|
|
99
|
+
rasters = {}
|
|
100
|
+
messages = []
|
|
101
|
+
act_cols = get_activity_rate_columns(df)
|
|
102
|
+
subst_cols = get_substance_emission_columns(df)
|
|
103
|
+
for _, row in df.iterrows():
|
|
104
|
+
row_dict = nan2None(row.to_dict())
|
|
105
|
+
messages += validate_activitycodes(row_dict, code_sets, row_nr=row_nr)
|
|
106
|
+
messages += validate_timevar(row_dict, timevars, row_nr=row_nr)
|
|
107
|
+
messages += validate_unit(row_dict, row_nr)
|
|
108
|
+
for col in filter(lambda x: row_dict[x] is not None, subst_cols):
|
|
109
|
+
subst_slug = col[6:]
|
|
110
|
+
messages += validate_raster(
|
|
111
|
+
row_dict, raster_names, datadir, rasters, row_nr, subst_slug
|
|
112
|
+
)
|
|
113
|
+
messages += validate_emission(row_dict, row_nr, subst_slug)
|
|
114
|
+
# if sum is specified instead of an emission total, the emission value
|
|
115
|
+
# is calculated as the sum of the emission raster
|
|
116
|
+
if row_dict[col] == "sum" and row_dict["path"] is None:
|
|
117
|
+
messages.append(
|
|
118
|
+
with_rownr_and_substance(
|
|
119
|
+
"raster path required for emission specified by 'sum'",
|
|
120
|
+
row_nr,
|
|
121
|
+
subst_slug,
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
for col in filter(lambda x: row_dict[x] is not None, act_cols):
|
|
125
|
+
act_name = col[4:].strip()
|
|
126
|
+
messages += validate_raster(
|
|
127
|
+
row_dict, raster_names, datadir, rasters, row_nr, subst_slug
|
|
128
|
+
)
|
|
129
|
+
messages += validate_activity(row_dict, row_nr, act_name)
|
|
130
|
+
# if sum is specified instead of an emission total, the emission value
|
|
131
|
+
# is calculated as the sum of the emission raster
|
|
132
|
+
if row_dict[col] == "sum" and row_dict["path"] is None:
|
|
133
|
+
messages.append(
|
|
134
|
+
with_rownr_and_substance(
|
|
135
|
+
"raster path required for activity rate specified by 'sum'",
|
|
136
|
+
row_nr,
|
|
137
|
+
act_name,
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
row_nr += 1
|
|
141
|
+
return rasters, messages
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def create_or_update_rasters(rasters, raster_names):
|
|
145
|
+
"""
|
|
146
|
+
Write all rasters with a path specified to the database.
|
|
147
|
+
Existing rasters will be replaced.
|
|
148
|
+
"""
|
|
149
|
+
for raster_name, raster_data in rasters.items():
|
|
150
|
+
if raster_data["path"] is not None:
|
|
151
|
+
if raster_name in raster_names:
|
|
152
|
+
drop_gridsource_raster(raster_name)
|
|
153
|
+
with rio.open(raster_data["path"]) as raster:
|
|
154
|
+
write_gridsource_raster(raster, raster_name)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def import_gridsources(filepath, encoding=None):
|
|
158
|
+
"""validate and/or import grid-sources from file."""
|
|
159
|
+
|
|
160
|
+
substances = cache_queryset(Substance.objects.all(), "slug")
|
|
161
|
+
timevars = cache_queryset(Timevar.objects.all(), "name")
|
|
162
|
+
code_sets = cache_codesets()
|
|
163
|
+
activities = cache_queryset(Activity.objects.all(), "name")
|
|
164
|
+
raster_names = list_gridsource_rasters()
|
|
165
|
+
datadir = dirname(filepath)
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
df = read_import_file(filepath, encoding)
|
|
169
|
+
except ImportError as err:
|
|
170
|
+
return {}, [f"{err}"]
|
|
171
|
+
|
|
172
|
+
messages = []
|
|
173
|
+
messages += validate_columns(
|
|
174
|
+
df,
|
|
175
|
+
REQUIRED_COLUMNS_GRID,
|
|
176
|
+
substances=substances,
|
|
177
|
+
activities=activities,
|
|
178
|
+
code_sets=code_sets,
|
|
179
|
+
)
|
|
180
|
+
if len(messages) > 0:
|
|
181
|
+
messages += ["Could not validate gridsources, due to error in columns."]
|
|
182
|
+
return {}, messages
|
|
183
|
+
|
|
184
|
+
row_nr = 2
|
|
185
|
+
rasters, messages_sources = validate_gridsources(
|
|
186
|
+
df, timevars, code_sets, raster_names, datadir
|
|
187
|
+
)
|
|
188
|
+
messages += messages_sources
|
|
189
|
+
if len(messages) > 0:
|
|
190
|
+
return {}, messages
|
|
191
|
+
|
|
192
|
+
create_or_update_rasters(rasters, raster_names)
|
|
193
|
+
nr_created_sources = 0
|
|
194
|
+
nr_updated_sources = 0
|
|
195
|
+
|
|
196
|
+
subst_cols = get_substance_emission_columns(df)
|
|
197
|
+
act_cols = get_activity_rate_columns(df)
|
|
198
|
+
for name, row in df.iterrows():
|
|
199
|
+
row_dict = nan2None(row.to_dict())
|
|
200
|
+
src, created = GridSource.objects.get_or_create(name=name)
|
|
201
|
+
if created:
|
|
202
|
+
nr_created_sources += 1
|
|
203
|
+
else:
|
|
204
|
+
nr_updated_sources += 1
|
|
205
|
+
# set tags
|
|
206
|
+
tag_keys = [key for key in row_dict.keys() if key.startswith("tag:")]
|
|
207
|
+
src.tags = {
|
|
208
|
+
key[4:]: row_dict[key] for key in tag_keys if row_dict[key] is not None
|
|
209
|
+
}
|
|
210
|
+
validate_activitycodes(row_dict, code_sets, row_nr, src)
|
|
211
|
+
validate_timevar(row_dict, timevars, row_nr, src)
|
|
212
|
+
src.save()
|
|
213
|
+
|
|
214
|
+
# remove any existing emissions
|
|
215
|
+
if not created:
|
|
216
|
+
src.substances.all().delete()
|
|
217
|
+
src.activities.all().delete()
|
|
218
|
+
|
|
219
|
+
for col in filter(lambda x: row_dict[x] is not None, subst_cols):
|
|
220
|
+
subst = col[6:]
|
|
221
|
+
# if sum is specified instead of an emission total,
|
|
222
|
+
# the emission value is calculated as the sum of the raster
|
|
223
|
+
rname, rpath = data_to_raster(
|
|
224
|
+
row_dict["rastername"], row_dict["path"], datadir, subst
|
|
225
|
+
)
|
|
226
|
+
emis = {"substance": substances[subst], "raster": rname}
|
|
227
|
+
emis_value = row_dict[col]
|
|
228
|
+
unit = row_dict["emission_unit"]
|
|
229
|
+
if emis_value == "sum":
|
|
230
|
+
emis["value"] = emission_unit_to_si(rasters[rname]["sum"], unit)
|
|
231
|
+
else:
|
|
232
|
+
emis["value"] = emission_unit_to_si(float(emis_value), unit)
|
|
233
|
+
src.substances.create(**emis)
|
|
234
|
+
|
|
235
|
+
for col in filter(lambda x: row_dict[x] is not None, act_cols):
|
|
236
|
+
activity_name = col[4:]
|
|
237
|
+
rname, rpath = data_to_raster(
|
|
238
|
+
row_dict["rastername"], row_dict["path"], datadir, subst
|
|
239
|
+
)
|
|
240
|
+
emis = {"activity": activities[activity_name], "raster": rname}
|
|
241
|
+
rate = row_dict[col]
|
|
242
|
+
if rate == "sum":
|
|
243
|
+
emis["rate"] = activity_rate_unit_to_si(
|
|
244
|
+
rasters[rname]["sum"], emis["activity"].unit
|
|
245
|
+
)
|
|
246
|
+
else:
|
|
247
|
+
emis["rate"] = activity_rate_unit_to_si(
|
|
248
|
+
float(rate), emis["activity"].unit
|
|
249
|
+
)
|
|
250
|
+
src.activities.create(**emis)
|
|
251
|
+
|
|
252
|
+
row_nr += 1
|
|
253
|
+
db_updates = {
|
|
254
|
+
"gridsources": {"updated": nr_updated_sources, "created": nr_created_sources}
|
|
255
|
+
}
|
|
256
|
+
return db_updates, messages
|