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,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)
|