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
cetk/emissions/calc.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Calculate emissions."""
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from django.db import connection
|
|
5
|
+
|
|
6
|
+
from cetk.edb.const import DEFAULT_EMISSION_UNIT
|
|
7
|
+
from cetk.edb.models import CodeSet, Settings, Substance
|
|
8
|
+
from cetk.edb.units import emis_conversion_factor_from_si
|
|
9
|
+
from cetk.emissions.queries import (
|
|
10
|
+
create_aggregate_emis_query,
|
|
11
|
+
create_source_emis_query,
|
|
12
|
+
create_used_substances_query,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_used_substances():
|
|
17
|
+
"""return list of substances with emissions or emission factors."""
|
|
18
|
+
sql = create_used_substances_query()
|
|
19
|
+
cur = connection.cursor()
|
|
20
|
+
return [Substance.objects.get(slug=rec[0]) for rec in cur.execute(sql).fetchall()]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def calculate_source_emissions(
|
|
24
|
+
sourcetype,
|
|
25
|
+
substances=None,
|
|
26
|
+
srid=None,
|
|
27
|
+
name=None,
|
|
28
|
+
ids=None,
|
|
29
|
+
tags=None,
|
|
30
|
+
polygon=None,
|
|
31
|
+
unit=DEFAULT_EMISSION_UNIT,
|
|
32
|
+
):
|
|
33
|
+
settings = Settings.get_current()
|
|
34
|
+
srid = srid or settings.srid
|
|
35
|
+
sql = create_source_emis_query(
|
|
36
|
+
sourcetype=sourcetype,
|
|
37
|
+
srid=srid,
|
|
38
|
+
substances=substances,
|
|
39
|
+
name=name,
|
|
40
|
+
ids=ids,
|
|
41
|
+
tags=tags,
|
|
42
|
+
polygon=polygon,
|
|
43
|
+
)
|
|
44
|
+
cur = connection.cursor()
|
|
45
|
+
cur.execute(sql)
|
|
46
|
+
return cur
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def calculate_source_emissions_df(
|
|
50
|
+
sourcetype,
|
|
51
|
+
substances=None,
|
|
52
|
+
name=None,
|
|
53
|
+
ids=None,
|
|
54
|
+
tags=None,
|
|
55
|
+
polygon=None,
|
|
56
|
+
unit=DEFAULT_EMISSION_UNIT,
|
|
57
|
+
):
|
|
58
|
+
cur = calculate_source_emissions(
|
|
59
|
+
sourcetype=sourcetype,
|
|
60
|
+
substances=substances,
|
|
61
|
+
name=name,
|
|
62
|
+
ids=ids,
|
|
63
|
+
tags=tags,
|
|
64
|
+
polygon=polygon,
|
|
65
|
+
unit=unit,
|
|
66
|
+
)
|
|
67
|
+
df = pd.DataFrame(cur.fetchall(), columns=[col[0] for col in cur.description])
|
|
68
|
+
if sourcetype == "grid":
|
|
69
|
+
df.set_index(["source_id", "substance", "raster"], inplace=True)
|
|
70
|
+
elif sourcetype == "road":
|
|
71
|
+
df.set_index(["source_id", "substance", "vehicle"], inplace=True)
|
|
72
|
+
traffic_work = df.loc[(slice(None), "traffic_work"), :]["emis"]
|
|
73
|
+
traffic_work.name = "traffic_work"
|
|
74
|
+
traffic_work.index = traffic_work.index.droplevel("substance")
|
|
75
|
+
df.drop(level="substance", labels=["traffic_work"], inplace=True)
|
|
76
|
+
df = df.join(traffic_work)
|
|
77
|
+
df = df.reorder_levels(["source_id", "substance", "vehicle"])
|
|
78
|
+
else:
|
|
79
|
+
df.set_index(["source_id", "substance"], inplace=True)
|
|
80
|
+
df.loc[:, "emis"] *= emis_conversion_factor_from_si(unit)
|
|
81
|
+
df.sort_index(inplace=True)
|
|
82
|
+
return df
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def aggregate_emissions(
|
|
86
|
+
substances=None,
|
|
87
|
+
sourcetypes=None,
|
|
88
|
+
codeset=None,
|
|
89
|
+
polygon=None,
|
|
90
|
+
tags=None,
|
|
91
|
+
point_ids=None,
|
|
92
|
+
area_ids=None,
|
|
93
|
+
unit=DEFAULT_EMISSION_UNIT,
|
|
94
|
+
name=None,
|
|
95
|
+
):
|
|
96
|
+
|
|
97
|
+
settings = Settings.get_current()
|
|
98
|
+
codeset_index = None if codeset is None else settings.get_codeset_index(codeset)
|
|
99
|
+
sql = create_aggregate_emis_query(
|
|
100
|
+
substances=substances,
|
|
101
|
+
sourcetypes=sourcetypes,
|
|
102
|
+
codeset_index=codeset_index,
|
|
103
|
+
polygon=polygon,
|
|
104
|
+
name=name,
|
|
105
|
+
tags=tags,
|
|
106
|
+
point_ids=point_ids,
|
|
107
|
+
area_ids=area_ids,
|
|
108
|
+
)
|
|
109
|
+
cur = connection.cursor()
|
|
110
|
+
cur.execute(sql)
|
|
111
|
+
df = pd.DataFrame(cur.fetchall(), columns=[col[0] for col in cur.description])
|
|
112
|
+
if codeset is not None:
|
|
113
|
+
try:
|
|
114
|
+
codeset = CodeSet.objects.get(slug=codeset)
|
|
115
|
+
except CodeSet.DoesNotExist:
|
|
116
|
+
raise ValueError(f"Codeset {codeset} does not exist, choose valid slug.")
|
|
117
|
+
if codeset is not None:
|
|
118
|
+
# add code labels to dataframe
|
|
119
|
+
df.insert(1, "activity", "")
|
|
120
|
+
code_labels = dict(codeset.codes.values_list("code", "label"))
|
|
121
|
+
for ind in df.index:
|
|
122
|
+
code = df.loc[ind, "activitycode"]
|
|
123
|
+
if code is not None:
|
|
124
|
+
df.loc[ind, "activity"] = code_labels[code]
|
|
125
|
+
# add to index (to remain also after pivoting)
|
|
126
|
+
df.set_index(["activitycode", "activity"], inplace=True)
|
|
127
|
+
df = df.pivot(columns="substance")
|
|
128
|
+
else:
|
|
129
|
+
df.insert(1, "activity", "total")
|
|
130
|
+
df.set_index(["activity"], inplace=True)
|
|
131
|
+
df = df.pivot(columns="substance")
|
|
132
|
+
df.columns = df.columns.set_names(["quantity", "substance"])
|
|
133
|
+
if "traffic_work" in df.columns.get_level_values("substance"):
|
|
134
|
+
# set quantity to traffic if substance is traffic_work
|
|
135
|
+
df.columns = pd.MultiIndex.from_tuples(
|
|
136
|
+
[
|
|
137
|
+
(q, s) if s != "traffic_work" else ("traffic", "veh*km")
|
|
138
|
+
for q, s in df.columns
|
|
139
|
+
]
|
|
140
|
+
)
|
|
141
|
+
df.columns.names = ["quantity", "substance"]
|
|
142
|
+
# convert from veh*m to veh*km
|
|
143
|
+
df.loc[:, ("traffic", "veh*km")] /= 1000.0
|
|
144
|
+
|
|
145
|
+
# convert emission unit
|
|
146
|
+
if len(df) > 0:
|
|
147
|
+
df.loc[:, ("emission", slice(None))] *= emis_conversion_factor_from_si(unit)
|
|
148
|
+
return df
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""Functions to dynamically create SQL where clauses for emission calculations."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
|
|
5
|
+
from django.contrib.gis.geos import Polygon
|
|
6
|
+
|
|
7
|
+
from cetk.edb.models import Settings, get_gridsource_raster, list_gridsource_rasters
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def create_name_where_clause(name):
|
|
11
|
+
"""create SQL to filter sources by source name"""
|
|
12
|
+
return f"sources.name = '{name}'"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def create_ids_where_clause(ids):
|
|
16
|
+
"""create SQL to filter sources by source id's
|
|
17
|
+
args:
|
|
18
|
+
ids: iterable of source id's
|
|
19
|
+
"""
|
|
20
|
+
source_ids = ",".join(ids)
|
|
21
|
+
return f"sources.id IN ({source_ids})"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def create_polygon_where_clause(polygon):
|
|
25
|
+
"""create SQL to filter sources by polygon
|
|
26
|
+
args:
|
|
27
|
+
sourcetype: "grid", "point" or "area"
|
|
28
|
+
polygon: a polygon in Polygon or EWKT-format
|
|
29
|
+
"""
|
|
30
|
+
polygon_ewkt = polygon.ewkt if isinstance(polygon, Polygon) else polygon
|
|
31
|
+
settings = Settings.get_current()
|
|
32
|
+
sql = f"""\
|
|
33
|
+
ST_Intersects(
|
|
34
|
+
ST_transform(sources.geom, {settings.srid}),
|
|
35
|
+
ST_Transform(GeomFromEWKT('{polygon_ewkt}'), {settings.srid})
|
|
36
|
+
)
|
|
37
|
+
"""
|
|
38
|
+
return sql
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def create_tag_where_clause(tags):
|
|
42
|
+
"""create SQL to filter sources on tags
|
|
43
|
+
args:
|
|
44
|
+
tags: a dictionary with key value pairs
|
|
45
|
+
|
|
46
|
+
To exclude all sources with a specific tag, specify operator as part of value:
|
|
47
|
+
"tag": "!=value"
|
|
48
|
+
"""
|
|
49
|
+
conds = []
|
|
50
|
+
for tag, cond in tags.items():
|
|
51
|
+
if cond.startswith("!="):
|
|
52
|
+
val = cond[2:].strip()
|
|
53
|
+
conds.append(
|
|
54
|
+
f"(sources.tags IS NULL OR NOT sources.tags ? {tag} OR sources.tags->>{tag} != {val})" # noqa
|
|
55
|
+
)
|
|
56
|
+
else:
|
|
57
|
+
if cond.startswith("="):
|
|
58
|
+
val = cond[1:].strip()
|
|
59
|
+
else:
|
|
60
|
+
val = cond
|
|
61
|
+
conds.append(f"sources.tags->>%{tag} = {val}")
|
|
62
|
+
return " AND ".join(conds)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def create_ef_substance_where_clause(substances):
|
|
66
|
+
"""filter to only include specified substances.
|
|
67
|
+
args:
|
|
68
|
+
substances: list of Substance model instances
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
if not isinstance(substances, Iterable):
|
|
72
|
+
substance_ids = str(substances.id)
|
|
73
|
+
else:
|
|
74
|
+
substance_ids = ",".join([str(s.id) for s in substances])
|
|
75
|
+
return f"ef.substance_id IN ({substance_ids})"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# def create_activitycode_where_clauses(ac1, ac2, ac3, first_cond=True):
|
|
79
|
+
# """filter to only include specified activitycodes.
|
|
80
|
+
# args:
|
|
81
|
+
# ac1: iterable with activitycode instances
|
|
82
|
+
# ac2: iterable with activitycode instances
|
|
83
|
+
# ac3: iterable with activitycode instances
|
|
84
|
+
|
|
85
|
+
# kwargs:
|
|
86
|
+
# first_cond: True adds WHERE, False adds AND
|
|
87
|
+
|
|
88
|
+
# returns sql, params
|
|
89
|
+
|
|
90
|
+
# returned where clause includes place-holders for codes named:
|
|
91
|
+
# ac1_code_1, ac1_code_2, ac2_code_1, ac2_code_2 etc.
|
|
92
|
+
# """
|
|
93
|
+
|
|
94
|
+
# # ac1, ac2, ac3 can be either a single code or an iterable
|
|
95
|
+
# sql = ""
|
|
96
|
+
# params = {}
|
|
97
|
+
# if ac1 is not None:
|
|
98
|
+
# if not isinstance(ac1, Iterable):
|
|
99
|
+
# ac1 = [ac1]
|
|
100
|
+
# else:
|
|
101
|
+
# ac1 = list(ac1)
|
|
102
|
+
# if not first_cond:
|
|
103
|
+
# sql += " AND ("
|
|
104
|
+
# else:
|
|
105
|
+
# sql += " WHERE ("
|
|
106
|
+
# first_cond = False
|
|
107
|
+
# sql += " OR ".join(
|
|
108
|
+
# [f"ac1.code <@ %(ac1_code_{i})s" for i, ac in enumerate(ac1)]
|
|
109
|
+
# )
|
|
110
|
+
# sql += ")"
|
|
111
|
+
# params.update({f"ac1_code_{i}": ac.code for i, ac in enumerate(ac1)})
|
|
112
|
+
|
|
113
|
+
# if ac2 is not None:
|
|
114
|
+
# if not isinstance(ac2, Iterable):
|
|
115
|
+
# ac2 = [ac2]
|
|
116
|
+
# else:
|
|
117
|
+
# ac2 = list(ac2)
|
|
118
|
+
# if not first_cond:
|
|
119
|
+
# sql += " AND ("
|
|
120
|
+
# else:
|
|
121
|
+
# sql += " WHERE ("
|
|
122
|
+
# first_cond = False
|
|
123
|
+
# sql += " OR ".join(
|
|
124
|
+
# [f"ac2.code <@ %(ac2_code_{i})s" for i, ac in enumerate(ac2)]
|
|
125
|
+
# )
|
|
126
|
+
# sql += ")"
|
|
127
|
+
# params.update({f"ac2_code_{i}": ac.code for i, ac in enumerate(ac2)})
|
|
128
|
+
|
|
129
|
+
# if ac3 is not None:
|
|
130
|
+
# if not isinstance(ac3, Iterable):
|
|
131
|
+
# ac3 = [ac3]
|
|
132
|
+
# else:
|
|
133
|
+
# ac3 = list(ac3)
|
|
134
|
+
|
|
135
|
+
# if not first_cond:
|
|
136
|
+
# sql += " AND ("
|
|
137
|
+
# else:
|
|
138
|
+
# sql += " WHERE ("
|
|
139
|
+
# first_cond = False
|
|
140
|
+
|
|
141
|
+
# sql += " OR ".join(
|
|
142
|
+
# [f"ac3.code <@ %(ac3_code_{i})s" for i, ac in enumerate(ac3)]
|
|
143
|
+
# )
|
|
144
|
+
# sql += ")"
|
|
145
|
+
# params.update({f"ac3_code_{i}": ac.code for i, ac in enumerate(ac3)})
|
|
146
|
+
|
|
147
|
+
# return sql, params
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def create_substance_emis_where_clause(substances):
|
|
151
|
+
"""filter to only include emissions of specified substances.
|
|
152
|
+
args:
|
|
153
|
+
substances: list of Substance model instances
|
|
154
|
+
"""
|
|
155
|
+
substance_ids = (
|
|
156
|
+
str(substances.id)
|
|
157
|
+
if not isinstance(substances, Iterable)
|
|
158
|
+
else ",".join([str(s.id) for s in substances])
|
|
159
|
+
)
|
|
160
|
+
return f"emis.substance_id IN ({substance_ids})"
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def create_raster_share_in_polygon_sql(polygon):
|
|
164
|
+
"""create inline sql table for fractions of rasters inside polygon."""
|
|
165
|
+
rasters = {}
|
|
166
|
+
for raster_name in list_gridsource_rasters():
|
|
167
|
+
if polygon is not None:
|
|
168
|
+
raster_data, metadata = get_gridsource_raster(raster_name, clip_by=polygon)
|
|
169
|
+
rasters[raster_name] = raster_data.sum()
|
|
170
|
+
else:
|
|
171
|
+
rasters[raster_name] = 1.0
|
|
172
|
+
if len(rasters) > 0:
|
|
173
|
+
sql = "\nUNION ALL\n".join(
|
|
174
|
+
f"SELECT '{name}' as name, {total} as total"
|
|
175
|
+
for name, total in rasters.items()
|
|
176
|
+
)
|
|
177
|
+
else:
|
|
178
|
+
sql = """SELECT "dummy" as name, 1.0 as total"""
|
|
179
|
+
return sql
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Calculate emissions.
|
|
3
|
+
Substance emissions and activity emissions are calculated separately and
|
|
4
|
+
are then aggregated by source, raster and substance.
|
|
5
|
+
*/
|
|
6
|
+
WITH sources as (
|
|
7
|
+
SELECT sources.id as source_id, name as source_name, tags, timevar_id,
|
|
8
|
+
ac1.code as ac1, ac2.code as ac2, ac3.code as ac3
|
|
9
|
+
FROM edb_gridsource as sources
|
|
10
|
+
LEFT JOIN edb_activitycode as ac1 ON ac1.id = sources.activitycode1_id
|
|
11
|
+
LEFT JOIN edb_activitycode as ac2 ON ac2.id = sources.activitycode2_id
|
|
12
|
+
LEFT JOIN edb_activitycode as ac3 ON ac3.id = sources.activitycode3_id
|
|
13
|
+
{source_filters}
|
|
14
|
+
)
|
|
15
|
+
SELECT sources.source_id, aggr_emis.substance_id, aggr_emis.raster, source_name, ac1, ac2, ac3,
|
|
16
|
+
sources.timevar_id, substances.slug as substance, aggr_emis.emis
|
|
17
|
+
FROM
|
|
18
|
+
(
|
|
19
|
+
SELECT substance_id, source_id, raster, SUM(emis) as emis
|
|
20
|
+
FROM
|
|
21
|
+
(
|
|
22
|
+
SELECT
|
|
23
|
+
sources.source_id,
|
|
24
|
+
emis.substance_id,
|
|
25
|
+
emis.raster,
|
|
26
|
+
emis.value as emis
|
|
27
|
+
FROM edb_gridsourcesubstance as emis
|
|
28
|
+
JOIN sources ON sources.source_id = emis.source_id
|
|
29
|
+
WHERE emis.value > 0
|
|
30
|
+
{emis_substance_filter}
|
|
31
|
+
UNION ALL
|
|
32
|
+
SELECT
|
|
33
|
+
sources.source_id,
|
|
34
|
+
ef_subst.substance_id,
|
|
35
|
+
act.raster,
|
|
36
|
+
act.rate * ef_subst.factor as emis
|
|
37
|
+
FROM edb_gridsourceactivity as act
|
|
38
|
+
JOIN sources ON act.source_id=sources.source_id
|
|
39
|
+
JOIN (
|
|
40
|
+
SELECT *
|
|
41
|
+
FROM edb_emissionfactor as ef
|
|
42
|
+
JOIN edb_activity ON edb_activity.id=ef.activity_id
|
|
43
|
+
WHERE ef.factor > 0
|
|
44
|
+
{ef_substance_filter}
|
|
45
|
+
) as ef_subst ON act.activity_id=ef_subst.activity_id
|
|
46
|
+
WHERE act.rate > 0
|
|
47
|
+
) all_emis
|
|
48
|
+
GROUP BY substance_id, source_id, raster
|
|
49
|
+
) aggr_emis
|
|
50
|
+
JOIN sources ON aggr_emis.source_id = sources.source_id
|
|
51
|
+
JOIN substances ON aggr_emis.substance_id = substances.id
|
|
52
|
+
ORDER BY sources.source_id
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Calculate emissions, optionally filtered by a polygon.
|
|
3
|
+
Substance emissions and activity emissions are calculated separately and
|
|
4
|
+
are then aggregated by source, raster and substance.
|
|
5
|
+
*/
|
|
6
|
+
WITH sources as (
|
|
7
|
+
SELECT sources.id as source_id, name as source_name, tags, timevar_id,
|
|
8
|
+
ac1.code as ac1, ac2.code as ac2, ac3.code as ac3,
|
|
9
|
+
ST_Transform(geom, {srid}) as geom
|
|
10
|
+
FROM edb_pointsource as sources
|
|
11
|
+
LEFT JOIN edb_activitycode as ac1 ON ac1.id = sources.activitycode1_id
|
|
12
|
+
LEFT JOIN edb_activitycode as ac2 ON ac2.id = sources.activitycode2_id
|
|
13
|
+
LEFT JOIN edb_activitycode as ac3 ON ac3.id = sources.activitycode3_id
|
|
14
|
+
{source_filters}
|
|
15
|
+
)
|
|
16
|
+
SELECT sources.source_id, aggr_emis.substance_id, source_name, ac1, ac2, ac3,
|
|
17
|
+
sources.geom as geom,
|
|
18
|
+
ST_AsText(sources.geom) as wkt,
|
|
19
|
+
sources.timevar_id, substances.slug as substance, aggr_emis.emis
|
|
20
|
+
FROM
|
|
21
|
+
(
|
|
22
|
+
SELECT substance_id, source_id, SUM(emis) as emis
|
|
23
|
+
FROM
|
|
24
|
+
(
|
|
25
|
+
SELECT
|
|
26
|
+
sources.source_id,
|
|
27
|
+
emis.substance_id as substance_id,
|
|
28
|
+
emis.value as emis
|
|
29
|
+
FROM edb_pointsourcesubstance as emis
|
|
30
|
+
JOIN sources ON sources.source_id = emis.source_id
|
|
31
|
+
WHERE emis.value > 0
|
|
32
|
+
{emis_substance_filter}
|
|
33
|
+
UNION ALL
|
|
34
|
+
SELECT
|
|
35
|
+
sources.source_id,
|
|
36
|
+
ef_subst.substance_id as substance_id,
|
|
37
|
+
act.rate * ef_subst.factor as emis
|
|
38
|
+
FROM edb_pointsourceactivity as act
|
|
39
|
+
JOIN sources ON act.source_id=sources.source_id
|
|
40
|
+
JOIN (
|
|
41
|
+
SELECT *
|
|
42
|
+
FROM edb_emissionfactor as ef
|
|
43
|
+
JOIN edb_activity ON edb_activity.id=ef.activity_id
|
|
44
|
+
WHERE ef.factor > 0
|
|
45
|
+
{ef_substance_filter}
|
|
46
|
+
) as ef_subst ON act.activity_id=ef_subst.activity_id
|
|
47
|
+
WHERE act.rate > 0
|
|
48
|
+
) all_emis
|
|
49
|
+
GROUP BY substance_id, source_id
|
|
50
|
+
) aggr_emis
|
|
51
|
+
JOIN sources ON aggr_emis.source_id = sources.source_id
|
|
52
|
+
JOIN substances ON aggr_emis.substance_id = substances.id
|
|
53
|
+
ORDER BY sources.source_id
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""Functions to create queries for emissions."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from importlib import resources
|
|
5
|
+
|
|
6
|
+
from cetk.edb.models import Settings, Substance
|
|
7
|
+
from cetk.emissions.filters import (
|
|
8
|
+
create_ef_substance_where_clause,
|
|
9
|
+
create_ids_where_clause,
|
|
10
|
+
create_name_where_clause,
|
|
11
|
+
create_polygon_where_clause,
|
|
12
|
+
create_raster_share_in_polygon_sql,
|
|
13
|
+
create_substance_emis_where_clause,
|
|
14
|
+
create_tag_where_clause,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load_sql(filename):
|
|
19
|
+
return resources.files("cetk.emissions").joinpath(filename).read_text()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def create_source_emis_query(
|
|
23
|
+
sourcetype,
|
|
24
|
+
srid=None,
|
|
25
|
+
name=None,
|
|
26
|
+
ids=None,
|
|
27
|
+
tags=None,
|
|
28
|
+
polygon=None,
|
|
29
|
+
substances=None,
|
|
30
|
+
# ac1=None,
|
|
31
|
+
# ac2=None,
|
|
32
|
+
# ac3=None,
|
|
33
|
+
):
|
|
34
|
+
"""
|
|
35
|
+
Create sql for emissions grouped by source, timevar & substance
|
|
36
|
+
|
|
37
|
+
Emissions are returned in kg/s.
|
|
38
|
+
|
|
39
|
+
optional arguments:
|
|
40
|
+
srid: sources will be transformed to this srid, default is inventory srid
|
|
41
|
+
ids: sequence of source id's for which to include sources
|
|
42
|
+
name: filter by source name(accepts regexp)
|
|
43
|
+
tags: filter by tags(specify as dictionary)
|
|
44
|
+
polygon: only include sources within polygon (EWKT or Polygon)
|
|
45
|
+
substances: iterable of substance model instances(default is all)
|
|
46
|
+
# ac1: iterable of activitycode instances
|
|
47
|
+
# ac2: iterable of activitycode instances
|
|
48
|
+
# ac3: iterable of activitycode instances
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
sql = load_sql(f"{sourcetype}source_emissions.sql")
|
|
52
|
+
source_filters = []
|
|
53
|
+
if tags is not None:
|
|
54
|
+
source_filters.append(create_tag_where_clause(tags))
|
|
55
|
+
if ids is not None:
|
|
56
|
+
source_filters.append(create_ids_where_clause(ids))
|
|
57
|
+
if name is not None:
|
|
58
|
+
source_filters.append(create_name_where_clause(name))
|
|
59
|
+
if polygon is not None and sourcetype != "grid":
|
|
60
|
+
source_filters.append(create_polygon_where_clause(polygon))
|
|
61
|
+
if len(source_filters) > 0:
|
|
62
|
+
source_filter_sql = "WHERE " + " AND ".join(source_filters)
|
|
63
|
+
else:
|
|
64
|
+
source_filter_sql = ""
|
|
65
|
+
|
|
66
|
+
traffic_work = Substance.objects.get(slug="traffic_work")
|
|
67
|
+
if substances is not None:
|
|
68
|
+
emis_subst_filter = " AND " + create_substance_emis_where_clause(substances)
|
|
69
|
+
if sourcetype == "road":
|
|
70
|
+
ef_subst_filter = " AND " + create_ef_substance_where_clause(
|
|
71
|
+
substances + [traffic_work]
|
|
72
|
+
)
|
|
73
|
+
else:
|
|
74
|
+
ef_subst_filter = " AND " + create_ef_substance_where_clause(substances)
|
|
75
|
+
else:
|
|
76
|
+
ef_subst_filter = "AND 1=1"
|
|
77
|
+
emis_subst_filter = ""
|
|
78
|
+
|
|
79
|
+
# replace place-holders by generated sql
|
|
80
|
+
sql = sql.format(
|
|
81
|
+
srid=srid,
|
|
82
|
+
source_filters=source_filter_sql,
|
|
83
|
+
ef_substance_filter=ef_subst_filter,
|
|
84
|
+
emis_substance_filter=emis_subst_filter,
|
|
85
|
+
traffic_work_subst_id=traffic_work.id,
|
|
86
|
+
)
|
|
87
|
+
return sql
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def create_aggregate_emis_query(
|
|
91
|
+
substances=None,
|
|
92
|
+
sourcetypes=None,
|
|
93
|
+
codeset_index=None,
|
|
94
|
+
polygon=None,
|
|
95
|
+
tags=None,
|
|
96
|
+
name=None,
|
|
97
|
+
point_ids=None,
|
|
98
|
+
area_ids=None,
|
|
99
|
+
grid_ids=None,
|
|
100
|
+
road_ids=None,
|
|
101
|
+
raster_share_in_polygon=None,
|
|
102
|
+
):
|
|
103
|
+
sql = load_sql("aggregate_emissions.sql")
|
|
104
|
+
traffic_work_subst_id = Substance.objects.values_list("id", flat=True).get(
|
|
105
|
+
slug="traffic_work"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if isinstance(substances, Substance):
|
|
109
|
+
substances = [substances]
|
|
110
|
+
|
|
111
|
+
sourcetypes = sourcetypes or ("point", "area", "grid", "road")
|
|
112
|
+
if not isinstance(sourcetypes, Sequence):
|
|
113
|
+
sourcetypes = [sourcetypes]
|
|
114
|
+
|
|
115
|
+
settings = Settings.get_current()
|
|
116
|
+
|
|
117
|
+
# adapt query to group by requested code-set
|
|
118
|
+
if codeset_index is not None:
|
|
119
|
+
if codeset_index not in (1, 2, 3):
|
|
120
|
+
raise ValueError("Invalid code-set index, must be in range 1-3")
|
|
121
|
+
ac_groupby = f"ac{codeset_index},"
|
|
122
|
+
ac_column = f"ac{codeset_index} as activitycode,"
|
|
123
|
+
else:
|
|
124
|
+
ac_groupby = ""
|
|
125
|
+
ac_column = ""
|
|
126
|
+
|
|
127
|
+
# general source filters
|
|
128
|
+
source_filters = []
|
|
129
|
+
if tags is not None:
|
|
130
|
+
source_filters.append(create_tag_where_clause(tags))
|
|
131
|
+
if name is not None:
|
|
132
|
+
source_filters.append(create_name_where_clause(name))
|
|
133
|
+
|
|
134
|
+
# point source filters
|
|
135
|
+
point_source_filters = source_filters.copy()
|
|
136
|
+
if polygon is not None:
|
|
137
|
+
point_source_filters.append(create_polygon_where_clause(polygon))
|
|
138
|
+
if point_ids is not None:
|
|
139
|
+
point_source_filters.append(create_ids_where_clause(point_ids))
|
|
140
|
+
if "point" not in sourcetypes:
|
|
141
|
+
point_source_filters.append("1=0")
|
|
142
|
+
|
|
143
|
+
if len(point_source_filters) > 0:
|
|
144
|
+
point_source_filter_sql = "WHERE " + " AND ".join(point_source_filters)
|
|
145
|
+
else:
|
|
146
|
+
point_source_filter_sql = ""
|
|
147
|
+
|
|
148
|
+
# area source filters
|
|
149
|
+
area_source_filters = source_filters.copy()
|
|
150
|
+
if polygon is not None:
|
|
151
|
+
area_source_filters.append(create_polygon_where_clause(polygon))
|
|
152
|
+
if area_ids is not None:
|
|
153
|
+
area_source_filters.append(create_ids_where_clause(area_ids))
|
|
154
|
+
if "area" not in sourcetypes:
|
|
155
|
+
area_source_filters.append("1=0")
|
|
156
|
+
|
|
157
|
+
if len(area_source_filters) > 0:
|
|
158
|
+
area_source_filter_sql = "WHERE " + " AND ".join(area_source_filters)
|
|
159
|
+
else:
|
|
160
|
+
area_source_filter_sql = ""
|
|
161
|
+
|
|
162
|
+
# road source filters
|
|
163
|
+
road_source_filters = ["sources.aadt > 0"] + source_filters
|
|
164
|
+
if polygon is not None:
|
|
165
|
+
road_source_filters.append(create_polygon_where_clause(polygon))
|
|
166
|
+
if road_ids is not None:
|
|
167
|
+
road_source_filters.append(create_ids_where_clause(road_ids))
|
|
168
|
+
if "road" not in sourcetypes:
|
|
169
|
+
road_source_filters.append("1=0")
|
|
170
|
+
if len(road_source_filters) > 0:
|
|
171
|
+
road_source_filter_sql = "WHERE " + " AND ".join(road_source_filters)
|
|
172
|
+
else:
|
|
173
|
+
road_source_filter_sql = ""
|
|
174
|
+
|
|
175
|
+
if substances is not None:
|
|
176
|
+
emis_subst_filter = " AND " + create_substance_emis_where_clause(substances)
|
|
177
|
+
ef_subst_filter = " AND " + create_ef_substance_where_clause(substances)
|
|
178
|
+
else:
|
|
179
|
+
emis_subst_filter = ""
|
|
180
|
+
ef_subst_filter = ""
|
|
181
|
+
|
|
182
|
+
# grid source filters
|
|
183
|
+
grid_source_filters = source_filters.copy()
|
|
184
|
+
|
|
185
|
+
if grid_ids is not None:
|
|
186
|
+
grid_source_filters.append(create_ids_where_clause(grid_ids))
|
|
187
|
+
if "grid" not in sourcetypes:
|
|
188
|
+
grid_source_filters.append("1=0")
|
|
189
|
+
if len(grid_source_filters) > 0:
|
|
190
|
+
grid_source_filter_sql = "WHERE " + " AND ".join(grid_source_filters)
|
|
191
|
+
else:
|
|
192
|
+
grid_source_filter_sql = ""
|
|
193
|
+
|
|
194
|
+
raster_share_sql = create_raster_share_in_polygon_sql(polygon)
|
|
195
|
+
if substances is not None:
|
|
196
|
+
substances_id = [s.id for s in substances]
|
|
197
|
+
else:
|
|
198
|
+
substances_id = [s.id for s in Substance.objects.all()]
|
|
199
|
+
sql = sql.format(
|
|
200
|
+
srid=settings.srid,
|
|
201
|
+
ac_column=ac_column,
|
|
202
|
+
ac_groupby=ac_groupby,
|
|
203
|
+
point_source_filter=point_source_filter_sql,
|
|
204
|
+
area_source_filter=area_source_filter_sql,
|
|
205
|
+
grid_source_filter=grid_source_filter_sql,
|
|
206
|
+
road_source_filter=road_source_filter_sql,
|
|
207
|
+
emis_substance_filter=emis_subst_filter,
|
|
208
|
+
ef_substance_filter=ef_subst_filter,
|
|
209
|
+
raster_share_sql=raster_share_sql,
|
|
210
|
+
traffic_work_subst_id=traffic_work_subst_id,
|
|
211
|
+
substances=substances_id,
|
|
212
|
+
)
|
|
213
|
+
return sql
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def create_used_substances_query():
|
|
217
|
+
"""
|
|
218
|
+
Create query to retrieve all substances for which there are emissions
|
|
219
|
+
in the inventory
|
|
220
|
+
"""
|
|
221
|
+
return load_sql("used_substances.sql")
|