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
@@ -0,0 +1,163 @@
1
+ """Gridsource models"""
2
+
3
+ import numpy as np
4
+ import rasterio as rio
5
+ import shapely
6
+ from django import db
7
+ from django.contrib.gis.db import models
8
+ from rasterio.mask import mask
9
+
10
+ from cetk.edb.const import CHAR_FIELD_LENGTH, NODATA
11
+ from cetk.utils import path_from_connection, slugify_keep_case
12
+
13
+ from .source_models import PointAreaGridSourceBase, SourceActivity, SourceSubstance
14
+
15
+ GRIDSOURCE_RASTER_PREFIX = "raster_"
16
+
17
+
18
+ class OutsideExtentError(Exception):
19
+ pass
20
+
21
+
22
+ class GridSource(PointAreaGridSourceBase):
23
+
24
+ sourcetype = "grid"
25
+
26
+ class Meta(PointAreaGridSourceBase.Meta):
27
+ constraints = [
28
+ models.UniqueConstraint(fields=("name",), name="gridsource_unique_name"),
29
+ ]
30
+ default_related_name = "gridsources"
31
+
32
+
33
+ class GridSourceActivity(SourceActivity):
34
+ """An emitting activity for a grid-source."""
35
+
36
+ source = models.ForeignKey("GridSource", on_delete=models.CASCADE)
37
+ raster = models.CharField(
38
+ verbose_name="distribution raster", max_length=CHAR_FIELD_LENGTH
39
+ )
40
+
41
+
42
+ class GridSourceSubstance(SourceSubstance):
43
+ """A substance emission of a grid-source ."""
44
+
45
+ source = models.ForeignKey("GridSource", on_delete=models.CASCADE)
46
+ raster = models.CharField(
47
+ verbose_name="distribution raster", max_length=CHAR_FIELD_LENGTH
48
+ )
49
+
50
+
51
+ def raster_table(name: str):
52
+ """get name of table where raster is stored."""
53
+ return f"{GRIDSOURCE_RASTER_PREFIX}{slugify_keep_case(name)}"
54
+
55
+
56
+ def list_gridsource_rasters():
57
+ """return names of gridsource rasters in database."""
58
+ cur = db.connection.cursor()
59
+ names = [
60
+ rec[0]
61
+ for rec in cur.execute(
62
+ """
63
+ select table_name from gpkg_contents where
64
+ data_type = '2d-gridded-coverage'
65
+ """
66
+ ).fetchall()
67
+ ]
68
+ return [
69
+ name[len(GRIDSOURCE_RASTER_PREFIX) :]
70
+ for name in names
71
+ if name.startswith(GRIDSOURCE_RASTER_PREFIX)
72
+ ]
73
+
74
+
75
+ def get_gridsource_raster(name, clip_by=None):
76
+ """Return (data, tranform, metadata) of raster
77
+ option:
78
+ clip_by: clip raster by polygon
79
+ """
80
+ with rio.open(path_from_connection(), table=raster_table(name)) as dset:
81
+ _, srid = dset.profile["crs"].to_authority()
82
+ nodata = dset.profile.get("nodata_value", NODATA)
83
+ metadata = {"srid": srid, "nodata": nodata}
84
+ if clip_by is not None:
85
+ geoms = [shapely.from_wkt(clip_by.transform(srid, clone=True).wkt)]
86
+ try:
87
+ data, transform = mask(
88
+ dset, geoms, all_touched=True, crop=True, indexes=1
89
+ )
90
+ except ValueError:
91
+ raise OutsideExtentError("polygon is completely outside extent")
92
+ metadata["transform"] = transform
93
+ rows, cols = data.shape
94
+ x_bl, y_bl = transform * (0, rows)
95
+ x_tr, y_tr = transform * (cols, 0)
96
+ metadata["extent"] = (x_bl, y_bl, x_tr, y_tr)
97
+ else:
98
+ metadata["extent"] = (
99
+ (
100
+ dset.bounds.left,
101
+ dset.bounds.bottom,
102
+ dset.bounds.right,
103
+ dset.bounds.top,
104
+ ),
105
+ )
106
+ metadata["transform"] = dset.transform
107
+ data = dset.read()
108
+ return data, metadata
109
+
110
+
111
+ def write_gridsource_raster(raster, name):
112
+ """write a rasterio taset to db in geopackage format."""
113
+ try:
114
+ _ = int(raster.crs.to_authority()[1])
115
+ except ValueError:
116
+ raise ValueError(
117
+ f"Raster '{name}' has an invalid projection, could not retrieve epsg code"
118
+ )
119
+ if name in list_gridsource_rasters():
120
+ drop_gridsource_raster(name)
121
+
122
+ with rio.Env():
123
+ gpkg = rio.open(
124
+ path_from_connection(),
125
+ "w",
126
+ driver="GPKG",
127
+ height=raster.height,
128
+ width=raster.width,
129
+ count=raster.count,
130
+ dtype="float32",
131
+ RASTER_TABLE=raster_table(name),
132
+ transform=raster.transform,
133
+ nodata=NODATA,
134
+ crs=raster.crs,
135
+ APPEND_SUBDATASET="YES",
136
+ )
137
+ data = raster.read()
138
+ total = data[data != raster.nodata].sum()
139
+ if total != 0:
140
+ data = np.where(data != raster.nodata, data / total, -9999.0)
141
+ gpkg.write(np.float32(data))
142
+ gpkg.close()
143
+ # reload_database()
144
+
145
+
146
+ def drop_gridsource_raster(name):
147
+ """drop grid source raster and related entries in gpkg-tables."""
148
+ table_name = f"{GRIDSOURCE_RASTER_PREFIX}{slugify_keep_case(name)}"
149
+ cur = db.connection.cursor()
150
+
151
+ sql_operations = [
152
+ f"delete from gpkg_2d_gridded_tile_ancillary where tpudt_name='{table_name}'",
153
+ f"delete from gpkg_2d_gridded_coverage_ancillary where tile_matrix_set_name='{table_name}'", # noqa
154
+ f"delete from gpkg_extensions where table_name='{table_name}'",
155
+ f"delete from gpkg_tile_matrix_set where table_name='{table_name}'",
156
+ f"delete from gpkg_tile_matrix where table_name='{table_name}'",
157
+ f"delete from gpkg_metadata_reference where table_name='{table_name}'",
158
+ f"delete from gpkg_contents where table_name='{table_name}'",
159
+ f"drop table {table_name}",
160
+ ]
161
+ for sql in sql_operations:
162
+ cur.execute(sql)
163
+ # reload_database()
@@ -0,0 +1,197 @@
1
+ """A road class model."""
2
+
3
+ import functools
4
+ import itertools
5
+ from collections import OrderedDict
6
+ from operator import itemgetter
7
+
8
+ from django.db import models
9
+
10
+ from .road_models import TrafficSituation
11
+ from .source_models import BaseNamedModel
12
+
13
+
14
+ class RoadAttribute(BaseNamedModel):
15
+ """A road attribute."""
16
+
17
+ name = models.CharField(max_length=64, unique=True)
18
+ slug = models.SlugField(max_length=64, unique=True)
19
+ order = models.PositiveSmallIntegerField(unique=True)
20
+
21
+ class Meta:
22
+ default_related_name = "road_attributes"
23
+ ordering = ["order"]
24
+
25
+
26
+ class RoadAttributeValue(models.Model):
27
+ """A valid road attribute value."""
28
+
29
+ value = models.CharField(max_length=64)
30
+ attribute = models.ForeignKey(
31
+ RoadAttribute, on_delete=models.CASCADE, related_name="values"
32
+ )
33
+
34
+ class Meta:
35
+ constraints = [
36
+ models.UniqueConstraint(
37
+ fields=["value", "attribute"], name="unique_road_attribute_value"
38
+ )
39
+ ]
40
+
41
+ def __str__(self):
42
+ return self.value
43
+
44
+
45
+ def get_valid_road_attribute_values():
46
+ """Return a nested dict with ordered road attributes and their values."""
47
+ return OrderedDict(
48
+ (a, {v.value: v for v in a.values.all()})
49
+ for a in RoadAttribute.objects.prefetch_related("values")
50
+ )
51
+
52
+
53
+ class PrefetchRoadClassAttributes(models.Prefetch):
54
+ """Custom Prefetch class to prefetch road class attributes."""
55
+
56
+ def __init__(
57
+ self, lookup="attribute_values", to_attr="prefetched_attribute_values"
58
+ ):
59
+ if not lookup.endswith("attribute_values"):
60
+ lookup = f"{lookup}__attribute_values"
61
+ super().__init__(
62
+ lookup,
63
+ queryset=RoadAttributeValue.objects.select_related("attribute").order_by(
64
+ "attribute__order"
65
+ ),
66
+ to_attr=to_attr,
67
+ )
68
+
69
+
70
+ class RoadClassQuerySet(models.QuerySet):
71
+ """A custom query set for road classes."""
72
+
73
+ def filter_on_attributes(self, attributes):
74
+ """Return the road classes that have all of the given attributes."""
75
+
76
+ def filter_one_attribute(qs, item):
77
+ attr, value = item
78
+ return qs.filter(
79
+ attribute_values__attribute__slug=attr, attribute_values__value=value
80
+ )
81
+
82
+ return functools.reduce(filter_one_attribute, attributes.items(), self.all())
83
+
84
+
85
+ class RoadClassManager(models.Manager.from_queryset(RoadClassQuerySet)):
86
+ """A custom database manager for road classes."""
87
+
88
+ use_in_migrations = True
89
+
90
+ def get_value_model(self):
91
+ # Don't use RoadClassAttributeValue directly since we may be in a migration
92
+ return self.model._meta.get_field("attribute_values").related_model
93
+
94
+ def create_from_attributes(self, attributes, **kwargs):
95
+ """Create a road class from an attribute mapping."""
96
+ road_class = self.create(**kwargs)
97
+ slugs = {k for k in attributes if not isinstance(k, RoadAttribute)}
98
+ if slugs:
99
+ attribute_instances = {
100
+ a.slug: a for a in RoadAttribute.objects.filter(slug__in=slugs)
101
+ }
102
+ try:
103
+ attributes = {
104
+ k if isinstance(k, RoadAttribute) else attribute_instances[k]: v
105
+ for k, v in attributes.items()
106
+ }
107
+ except KeyError as exc:
108
+ raise RoadAttribute.DoesNotExist(
109
+ f"A road attribute with slug {exc} does not exist"
110
+ )
111
+
112
+ value_model = self.get_value_model()
113
+ valid_values = get_valid_road_attribute_values()
114
+ # check if values are given by names, then replace by RoadAttributeValues
115
+ attribute_values = []
116
+ for attr, val in attributes.items():
117
+ if not isinstance(val, value_model):
118
+ try:
119
+ attribute_values.append(valid_values[attr][val])
120
+ except KeyError as exc:
121
+ raise value_model.DoesNotExist(
122
+ f"A value {exc} for road attribute '{attr.slug}' does not exist"
123
+ )
124
+ else:
125
+ attribute_values.append(val)
126
+
127
+ road_class.attribute_values.set(attribute_values)
128
+ return road_class
129
+
130
+ def bulk_create_from_attribute_table(self, table, *, create_values=False):
131
+ """Create road classes in bulk given an attribute *table*."""
132
+ ts_ids = list(map(itemgetter(-1), table))
133
+ traffic_situations = {
134
+ ts.ts_id: ts
135
+ for ts in TrafficSituation.objects.filter(ts_id__in=set(ts_ids))
136
+ }
137
+ road_classes = [
138
+ self.model(traffic_situation=traffic_situations[ts_id]) for ts_id in ts_ids
139
+ ]
140
+ self.bulk_create(road_classes)
141
+ ids = set([ts.id for ts in traffic_situations.values()])
142
+ road_classes = self.model.objects.filter(traffic_situation_id__in=ids)
143
+ if create_values:
144
+ attributes = RoadAttribute.objects.all()
145
+ value_model = self.get_value_model()
146
+ valid_values = OrderedDict(
147
+ (
148
+ a,
149
+ {
150
+ v: value_model(attribute=a, value=v)
151
+ for v in set(map(itemgetter(i), table))
152
+ },
153
+ )
154
+ for i, a in enumerate(attributes)
155
+ )
156
+ value_model.objects.bulk_create(
157
+ [v for values in valid_values.values() for v in values.values()]
158
+ )
159
+ # need to define valid values even if create_values,
160
+ # otherwise unsaved related object 'roadattributevalue'
161
+ valid_values = get_valid_road_attribute_values()
162
+
163
+ through_model = self.model.attribute_values.through
164
+ try:
165
+ values = [
166
+ through_model(roadclass=c, roadattributevalue=valid_values[a][v])
167
+ for c, row in zip(road_classes, table)
168
+ for a, v in itertools.zip_longest(valid_values, row[:-1])
169
+ ]
170
+ except KeyError as err:
171
+ raise RoadAttributeValue.DoesNotExist(f"invalid road attribute value {err}")
172
+ through_model.objects.bulk_create(values)
173
+
174
+ return road_classes
175
+
176
+
177
+ class RoadClass(models.Model): # noqa: DJ008
178
+ """A road class defined by a unique set of road attributes."""
179
+
180
+ traffic_situation = models.ForeignKey("TrafficSituation", on_delete=models.CASCADE)
181
+ attribute_values = models.ManyToManyField(RoadAttributeValue)
182
+
183
+ objects = RoadClassManager()
184
+
185
+ class Meta:
186
+ default_related_name = "road_classes"
187
+
188
+ @property
189
+ def attributes(self):
190
+ """An ordered dictionary mapping road attribute slugs to their values."""
191
+ try:
192
+ values = self.prefetched_attribute_values
193
+ except AttributeError:
194
+ values = self.attribute_values.select_related("attribute").order_by(
195
+ "attribute__order"
196
+ )
197
+ return OrderedDict((v.attribute.slug, v.value) for v in values)