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.
Files changed (60) hide show
  1. cetk/__init__.py +87 -0
  2. cetk/db.py +41 -0
  3. cetk/edb/__init__.py +0 -0
  4. cetk/edb/apps.py +9 -0
  5. cetk/edb/cache.py +249 -0
  6. cetk/edb/const.py +26 -0
  7. cetk/edb/copy.py +18 -0
  8. cetk/edb/exporters.py +551 -0
  9. cetk/edb/importers/__init__.py +36 -0
  10. cetk/edb/importers/activity_import.py +279 -0
  11. cetk/edb/importers/codeset_import.py +124 -0
  12. cetk/edb/importers/gridsource_import.py +256 -0
  13. cetk/edb/importers/roadsource_import.py +1602 -0
  14. cetk/edb/importers/source_import.py +958 -0
  15. cetk/edb/importers/timevar_import.py +115 -0
  16. cetk/edb/importers/utils.py +92 -0
  17. cetk/edb/importers/validation.py +207 -0
  18. cetk/edb/ltreefield.py +76 -0
  19. cetk/edb/migrations/0001_initial.py +1731 -0
  20. cetk/edb/migrations/0002_data.py +156 -0
  21. cetk/edb/migrations/0003_create_gpkg_tables.py +14 -0
  22. cetk/edb/migrations/__init__.py +0 -0
  23. cetk/edb/models/__init__.py +49 -0
  24. cetk/edb/models/base.py +52 -0
  25. cetk/edb/models/common_models.py +144 -0
  26. cetk/edb/models/fleets.py +160 -0
  27. cetk/edb/models/gridsource_models.py +163 -0
  28. cetk/edb/models/road_classes.py +197 -0
  29. cetk/edb/models/road_models.py +413 -0
  30. cetk/edb/models/source_models.py +399 -0
  31. cetk/edb/models/timevar_models.py +111 -0
  32. cetk/edb/rasterize/__init__.py +1 -0
  33. cetk/edb/rasterize/rasterizer.py +1544 -0
  34. cetk/edb/signals.py +43 -0
  35. cetk/edb/traffic.py +75 -0
  36. cetk/edb/units.py +160 -0
  37. cetk/emissions/__init__.py +0 -0
  38. cetk/emissions/aggregate_emissions.sql +303 -0
  39. cetk/emissions/areasource_emissions.sql +53 -0
  40. cetk/emissions/calc.py +148 -0
  41. cetk/emissions/filters.py +179 -0
  42. cetk/emissions/gridsource_emissions.sql +52 -0
  43. cetk/emissions/pointsource_emissions.sql +53 -0
  44. cetk/emissions/queries.py +221 -0
  45. cetk/emissions/roadsource_emissions.sql +211 -0
  46. cetk/emissions/used_substances.sql +56 -0
  47. cetk/emissions/views.py +89 -0
  48. cetk/logging.py +68 -0
  49. cetk/settings.py +85 -0
  50. cetk/tools/__init__.py +1 -0
  51. cetk/tools/cetk_command.py +599 -0
  52. cetk/tools/manage.py +24 -0
  53. cetk/tools/utils.py +334 -0
  54. cetk/utils.py +62 -0
  55. cetk-0.0.4.dev0.dist-info/LICENSE +201 -0
  56. cetk-0.0.4.dev0.dist-info/METADATA +297 -0
  57. cetk-0.0.4.dev0.dist-info/RECORD +60 -0
  58. cetk-0.0.4.dev0.dist-info/WHEEL +5 -0
  59. cetk-0.0.4.dev0.dist-info/entry_points.txt +3 -0
  60. 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")