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,399 @@
|
|
|
1
|
+
"""Emission database models."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from django.contrib.gis.db import models
|
|
5
|
+
|
|
6
|
+
from cetk.edb.const import CHAR_FIELD_LENGTH, WGS84_SRID
|
|
7
|
+
from cetk.edb.ltreefield import LtreeField
|
|
8
|
+
from cetk.edb.models.base import BaseNamedModel, NaturalKeyManager
|
|
9
|
+
|
|
10
|
+
SRID = WGS84_SRID
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SourceSubstance(models.Model):
|
|
14
|
+
"""An abstract models for source substance emissions."""
|
|
15
|
+
|
|
16
|
+
value = models.FloatField(default=0, verbose_name="source emission")
|
|
17
|
+
substance = models.ForeignKey(
|
|
18
|
+
"Substance", on_delete=models.PROTECT, related_name="+"
|
|
19
|
+
)
|
|
20
|
+
updated = models.DateTimeField(verbose_name="date of last update", auto_now=True)
|
|
21
|
+
|
|
22
|
+
class Meta:
|
|
23
|
+
abstract = True
|
|
24
|
+
default_related_name = "substances"
|
|
25
|
+
unique_together = ("source", "substance")
|
|
26
|
+
|
|
27
|
+
def __str__(self):
|
|
28
|
+
return self.substance.name
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def default_vertical_dist():
|
|
32
|
+
return "[[5.0, 1.0]]"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class VerticalDist(BaseNamedModel):
|
|
36
|
+
"""Vertical distribution of GridSource emissions."""
|
|
37
|
+
|
|
38
|
+
objects = NaturalKeyManager()
|
|
39
|
+
|
|
40
|
+
name = models.CharField(max_length=64)
|
|
41
|
+
slug = models.SlugField(max_length=64, unique=True)
|
|
42
|
+
weights = models.CharField(
|
|
43
|
+
max_length=CHAR_FIELD_LENGTH, default=default_vertical_dist
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
class Meta:
|
|
47
|
+
db_table = "vertical_distributions"
|
|
48
|
+
default_related_name = "vertical_distributions"
|
|
49
|
+
|
|
50
|
+
natural_key_fields = "slug"
|
|
51
|
+
|
|
52
|
+
def natural_key(self):
|
|
53
|
+
return self.slug
|
|
54
|
+
|
|
55
|
+
def __str__(self):
|
|
56
|
+
return self.name
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Activity(models.Model):
|
|
60
|
+
"""An emitting activity."""
|
|
61
|
+
|
|
62
|
+
name = models.CharField(
|
|
63
|
+
verbose_name="name of activity", max_length=CHAR_FIELD_LENGTH, unique=True
|
|
64
|
+
)
|
|
65
|
+
unit = models.CharField(
|
|
66
|
+
verbose_name="unit of activity", max_length=CHAR_FIELD_LENGTH
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
class Meta:
|
|
70
|
+
default_related_name = "activities"
|
|
71
|
+
|
|
72
|
+
def __str__(self):
|
|
73
|
+
"""Return a unicode representation of this activity."""
|
|
74
|
+
return self.name
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class CodeSet(BaseNamedModel):
|
|
78
|
+
"""A set of activity codes."""
|
|
79
|
+
|
|
80
|
+
objects = NaturalKeyManager()
|
|
81
|
+
|
|
82
|
+
name = models.CharField(max_length=64)
|
|
83
|
+
slug = models.SlugField(max_length=64, unique=True)
|
|
84
|
+
description = models.CharField(
|
|
85
|
+
verbose_name="description", max_length=200, null=True, blank=True
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
class Meta:
|
|
89
|
+
db_table = "codesets"
|
|
90
|
+
default_related_name = "codesets"
|
|
91
|
+
|
|
92
|
+
natural_key_fields = "slug"
|
|
93
|
+
|
|
94
|
+
def natural_key(self):
|
|
95
|
+
return self.slug
|
|
96
|
+
|
|
97
|
+
def __str__(self):
|
|
98
|
+
return self.name
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class ActivityCode(models.Model):
|
|
102
|
+
"""An abstract model for an activity code."""
|
|
103
|
+
|
|
104
|
+
objects = NaturalKeyManager()
|
|
105
|
+
code = LtreeField(verbose_name="activity code")
|
|
106
|
+
label = models.CharField(verbose_name="activity code label", max_length=100)
|
|
107
|
+
code_set = models.ForeignKey(
|
|
108
|
+
CodeSet, on_delete=models.CASCADE, related_name="codes"
|
|
109
|
+
)
|
|
110
|
+
vertical_dist = models.ForeignKey(
|
|
111
|
+
VerticalDist, on_delete=models.SET_NULL, related_name="+", null=True, blank=True
|
|
112
|
+
)
|
|
113
|
+
natural_key_fields = ("code_set__slug", "code")
|
|
114
|
+
|
|
115
|
+
class Meta:
|
|
116
|
+
constraints = [
|
|
117
|
+
models.UniqueConstraint(
|
|
118
|
+
fields=["code_set", "code"], name="unique code in codeset"
|
|
119
|
+
)
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
def natural_key(self):
|
|
123
|
+
return (self.code_set.slug, self.code)
|
|
124
|
+
|
|
125
|
+
def __lt__(self, other):
|
|
126
|
+
return self.code < other.code
|
|
127
|
+
|
|
128
|
+
def __str__(self):
|
|
129
|
+
"""Return a unicode representation of this activity code."""
|
|
130
|
+
return self.code
|
|
131
|
+
|
|
132
|
+
def matches(self, filters):
|
|
133
|
+
"""Compare with a (list of) filter code(s).
|
|
134
|
+
args
|
|
135
|
+
filters: list of accepted codes
|
|
136
|
+
Filters should be '.'-separated codes
|
|
137
|
+
comparison is only made for code levels included in filter
|
|
138
|
+
i.e. the code 1.A.2.i will match the filter 1.A
|
|
139
|
+
"""
|
|
140
|
+
code_parts = self.code.split(".")
|
|
141
|
+
for f in filters:
|
|
142
|
+
matches = True
|
|
143
|
+
filter_parts = f.code.split(".")
|
|
144
|
+
# filter has more code-parts than code
|
|
145
|
+
if len(filter_parts) > len(code_parts):
|
|
146
|
+
matches = False
|
|
147
|
+
continue
|
|
148
|
+
# compare code with filter part by part
|
|
149
|
+
for i, filter_part in enumerate(filter_parts):
|
|
150
|
+
if filter_part != code_parts[i]:
|
|
151
|
+
matches = False
|
|
152
|
+
break
|
|
153
|
+
if matches:
|
|
154
|
+
return matches
|
|
155
|
+
return matches
|
|
156
|
+
|
|
157
|
+
def get_decendents(self):
|
|
158
|
+
return self.get_decendents_and_self().exclude(pk=self.pk)
|
|
159
|
+
|
|
160
|
+
def get_decendents_and_self(self):
|
|
161
|
+
return ActivityCode.objects.filter(code__dore=self.code).filter(
|
|
162
|
+
code_set=self.code_set
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def get_ancestors(self):
|
|
166
|
+
return self.get_ancestors_and_self().exclude(pk=self.pk)
|
|
167
|
+
|
|
168
|
+
def get_ancestors_and_self(self):
|
|
169
|
+
return ActivityCode.objects.filter(code__aore=self.code).filter(
|
|
170
|
+
code_set=self.code_set
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def get_parent(self):
|
|
174
|
+
if "." not in self.code:
|
|
175
|
+
raise RuntimeError(
|
|
176
|
+
f"The code: {self} cannot have a parent as it is a root node"
|
|
177
|
+
)
|
|
178
|
+
return ActivityCode.objects.get(
|
|
179
|
+
code__match=".".join(self.code.split(".")[:-1]), code_set=self.code_set
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
def get_siblings_and_self(self):
|
|
183
|
+
return ActivityCode.objects.filter(
|
|
184
|
+
code__match=".".join(self.code.split(".")[:-1]) + "._",
|
|
185
|
+
code_set=self.code_set,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def get_siblings(self):
|
|
189
|
+
return self.get_siblings_and_self().exclude(pk=self.pk)
|
|
190
|
+
|
|
191
|
+
def get_children(self):
|
|
192
|
+
return self.code_set.codes.filter(code__match=self.code + "._")
|
|
193
|
+
|
|
194
|
+
def is_leaf(self):
|
|
195
|
+
"""Return True if code is a leaf (i.e. has no sub-codes)."""
|
|
196
|
+
return not self.get_decendents().exists()
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class SourceBase(models.Model):
|
|
200
|
+
"""Abstract base model for an emission source."""
|
|
201
|
+
|
|
202
|
+
name = models.CharField("name", max_length=CHAR_FIELD_LENGTH, blank=False)
|
|
203
|
+
created = models.DateTimeField(verbose_name="time of creation", auto_now_add=True)
|
|
204
|
+
updated = models.DateTimeField(verbose_name="time of last update", auto_now=True)
|
|
205
|
+
# TODO: do such tags work for Spatialite? Best to keep for CLAIR compatability?
|
|
206
|
+
tags = models.JSONField(
|
|
207
|
+
verbose_name="user-defined key-value pairs", blank=True, null=True
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
class Meta:
|
|
211
|
+
abstract = True
|
|
212
|
+
|
|
213
|
+
def __str__(self):
|
|
214
|
+
"""Return a unicode representation of this source."""
|
|
215
|
+
return self.name
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class PointAreaGridSourceBase(SourceBase):
|
|
219
|
+
"""Abstract base model for point, area and grid sources"""
|
|
220
|
+
|
|
221
|
+
timevar = models.ForeignKey(
|
|
222
|
+
"Timevar", on_delete=models.SET_NULL, related_name="+", null=True, blank=True
|
|
223
|
+
)
|
|
224
|
+
activitycode1 = models.ForeignKey(
|
|
225
|
+
"ActivityCode",
|
|
226
|
+
on_delete=models.PROTECT,
|
|
227
|
+
related_name="+",
|
|
228
|
+
null=True,
|
|
229
|
+
blank=True,
|
|
230
|
+
)
|
|
231
|
+
activitycode2 = models.ForeignKey(
|
|
232
|
+
"ActivityCode",
|
|
233
|
+
on_delete=models.PROTECT,
|
|
234
|
+
related_name="+",
|
|
235
|
+
null=True,
|
|
236
|
+
blank=True,
|
|
237
|
+
)
|
|
238
|
+
activitycode3 = models.ForeignKey(
|
|
239
|
+
"ActivityCode",
|
|
240
|
+
on_delete=models.PROTECT,
|
|
241
|
+
related_name="+",
|
|
242
|
+
null=True,
|
|
243
|
+
blank=True,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
class Meta:
|
|
247
|
+
abstract = True
|
|
248
|
+
indexes = [
|
|
249
|
+
models.Index(
|
|
250
|
+
fields=("activitycode1", "activitycode2", "activitycode3"),
|
|
251
|
+
name="%(class)s_activities_idx",
|
|
252
|
+
),
|
|
253
|
+
]
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class Facility(SourceBase):
|
|
257
|
+
"""A facility."""
|
|
258
|
+
|
|
259
|
+
official_id = models.CharField(
|
|
260
|
+
"official_id",
|
|
261
|
+
max_length=CHAR_FIELD_LENGTH,
|
|
262
|
+
blank=False,
|
|
263
|
+
db_index=True,
|
|
264
|
+
unique=True,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
class Meta:
|
|
268
|
+
default_related_name = "facilities"
|
|
269
|
+
|
|
270
|
+
def __str__(self):
|
|
271
|
+
return str(self.official_id)
|
|
272
|
+
|
|
273
|
+
def __repr__(self):
|
|
274
|
+
return str(self)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class PointSource(PointAreaGridSourceBase):
|
|
278
|
+
"""A point-source."""
|
|
279
|
+
|
|
280
|
+
sourcetype = "point"
|
|
281
|
+
|
|
282
|
+
chimney_height = models.FloatField("chimney height [m]", default=0)
|
|
283
|
+
chimney_outer_diameter = models.FloatField(
|
|
284
|
+
"chimney outer diameter [m]", default=1.0
|
|
285
|
+
)
|
|
286
|
+
chimney_inner_diameter = models.FloatField(
|
|
287
|
+
"chimney inner diameter [m]", default=0.9
|
|
288
|
+
)
|
|
289
|
+
chimney_gas_speed = models.FloatField("chimney gas speed [m/s]", default=1.0)
|
|
290
|
+
chimney_gas_temperature = models.FloatField(
|
|
291
|
+
"chimney gas temperature [K]", default=373.0
|
|
292
|
+
)
|
|
293
|
+
house_width = models.IntegerField(
|
|
294
|
+
"house width [m] (to estimate down draft)", default=0
|
|
295
|
+
)
|
|
296
|
+
house_height = models.IntegerField(
|
|
297
|
+
"house height [m] (to estimate down draft)", default=0
|
|
298
|
+
)
|
|
299
|
+
geom = models.PointField(
|
|
300
|
+
"the position of the point-source",
|
|
301
|
+
srid=WGS84_SRID,
|
|
302
|
+
geography=True,
|
|
303
|
+
db_index=True,
|
|
304
|
+
)
|
|
305
|
+
facility = models.ForeignKey(
|
|
306
|
+
"Facility", on_delete=models.SET_NULL, null=True, blank=True
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
class Meta(PointAreaGridSourceBase.Meta):
|
|
310
|
+
default_related_name = "pointsources"
|
|
311
|
+
unique_together = ("facility", "name")
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class PointSourceSubstance(SourceSubstance):
|
|
315
|
+
"""A point-source substance emission."""
|
|
316
|
+
|
|
317
|
+
source = models.ForeignKey("PointSource", on_delete=models.CASCADE)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class SourceActivity(models.Model):
|
|
321
|
+
"""Base class for an emitting activity."""
|
|
322
|
+
|
|
323
|
+
rate = models.FloatField(verbose_name="activity rate")
|
|
324
|
+
activity = models.ForeignKey("Activity", on_delete=models.PROTECT, related_name="+")
|
|
325
|
+
|
|
326
|
+
class Meta:
|
|
327
|
+
abstract = True
|
|
328
|
+
constraints = [
|
|
329
|
+
models.UniqueConstraint(
|
|
330
|
+
fields=("source", "activity"),
|
|
331
|
+
name="%(class)s_unique_activity_in_source",
|
|
332
|
+
),
|
|
333
|
+
]
|
|
334
|
+
default_related_name = "activities"
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
class PointSourceActivity(SourceActivity):
|
|
338
|
+
"""An emitting activity of a point source."""
|
|
339
|
+
|
|
340
|
+
source = models.ForeignKey("PointSource", on_delete=models.CASCADE)
|
|
341
|
+
|
|
342
|
+
def __str__(self):
|
|
343
|
+
return "{}".format(self.activity.name)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
class EmissionFactor(models.Model):
|
|
347
|
+
"""An emission factor."""
|
|
348
|
+
|
|
349
|
+
factor = models.FloatField(default=0)
|
|
350
|
+
activity = models.ForeignKey("Activity", on_delete=models.PROTECT)
|
|
351
|
+
substance = models.ForeignKey(
|
|
352
|
+
"Substance", on_delete=models.PROTECT, related_name="+"
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
class Meta:
|
|
356
|
+
constraints = [
|
|
357
|
+
models.UniqueConstraint(
|
|
358
|
+
fields=("activity", "substance"),
|
|
359
|
+
name="emissionfactor_activity_substance_unique_together",
|
|
360
|
+
),
|
|
361
|
+
]
|
|
362
|
+
default_related_name = "emissionfactors"
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class AreaSource(PointAreaGridSourceBase):
|
|
366
|
+
"""An area source."""
|
|
367
|
+
|
|
368
|
+
sourcetype = "area"
|
|
369
|
+
|
|
370
|
+
geom = models.PolygonField(
|
|
371
|
+
"the extent of the area source", srid=WGS84_SRID, geography=True, db_index=True
|
|
372
|
+
)
|
|
373
|
+
facility = models.ForeignKey(
|
|
374
|
+
"Facility", on_delete=models.SET_NULL, null=True, blank=True
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
class Meta(PointAreaGridSourceBase.Meta):
|
|
378
|
+
constraints = [
|
|
379
|
+
models.UniqueConstraint(
|
|
380
|
+
fields=("facility", "name"),
|
|
381
|
+
name="areasource_unique_facility_and_name",
|
|
382
|
+
),
|
|
383
|
+
]
|
|
384
|
+
default_related_name = "areasources"
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
class AreaSourceActivity(SourceActivity):
|
|
388
|
+
"""An emitting activity of an area source."""
|
|
389
|
+
|
|
390
|
+
source = models.ForeignKey("AreaSource", on_delete=models.CASCADE)
|
|
391
|
+
|
|
392
|
+
def __str__(self):
|
|
393
|
+
return f"{self.activity.name}"
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
class AreaSourceSubstance(SourceSubstance):
|
|
397
|
+
"""An area-source substance emission."""
|
|
398
|
+
|
|
399
|
+
source = models.ForeignKey("AreaSource", on_delete=models.CASCADE)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from django.contrib.gis.db import models
|
|
6
|
+
|
|
7
|
+
from cetk.edb.const import CHAR_FIELD_LENGTH
|
|
8
|
+
from cetk.edb.models.common_models import Settings
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def default_timevar_typeday():
|
|
12
|
+
return str(24 * [7 * [100.0]])
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def default_timevar_month():
|
|
16
|
+
return str(12 * [100.0])
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_normalization_constant(typeday, month, timezone):
|
|
20
|
+
commonyear = pd.date_range("2018", periods=24 * 365, freq="h", tz=timezone)
|
|
21
|
+
values = typeday[commonyear.hour, commonyear.weekday] * month[commonyear.month - 1]
|
|
22
|
+
return len(values) / values.sum()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def timevar_to_series(time_index, *timevars, timezone=None):
|
|
26
|
+
"""Produce normalized time series from one or more timevar instances."""
|
|
27
|
+
if not timevars:
|
|
28
|
+
raise TypeError("at least one timevar must be given")
|
|
29
|
+
if timezone is None:
|
|
30
|
+
timezone = Settings.get_current().timezone
|
|
31
|
+
typeday = np.multiply.reduce([ast.literal_eval(t.typeday) for t in timevars])
|
|
32
|
+
month = np.multiply.reduce([ast.literal_eval(t.month) for t in timevars])
|
|
33
|
+
if len(timevars) > 1:
|
|
34
|
+
normalization_constant = get_normalization_constant(typeday, month, timezone)
|
|
35
|
+
else:
|
|
36
|
+
normalization_constant = timevars[0].normalization_constant
|
|
37
|
+
|
|
38
|
+
local_time_index = time_index.tz_convert(timezone)
|
|
39
|
+
values = (
|
|
40
|
+
typeday[local_time_index.hour, local_time_index.weekday]
|
|
41
|
+
* month[local_time_index.month - 1]
|
|
42
|
+
)
|
|
43
|
+
return pd.Series(normalization_constant * values, index=time_index)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def timevar_normalize(timevar, timezone=None):
|
|
47
|
+
"""Set the normalization constants on a timevar instance."""
|
|
48
|
+
if timezone is None:
|
|
49
|
+
timezone = Settings.get_current().timezone
|
|
50
|
+
typeday = np.array(ast.literal_eval(timevar.typeday))
|
|
51
|
+
month = np.array(ast.literal_eval(timevar.month))
|
|
52
|
+
timevar.typeday_sum = typeday.sum()
|
|
53
|
+
timevar._normalization_constant = get_normalization_constant(
|
|
54
|
+
typeday, month, timezone
|
|
55
|
+
)
|
|
56
|
+
return timevar
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TimevarBase(models.Model):
|
|
60
|
+
name = models.CharField(max_length=CHAR_FIELD_LENGTH, unique=True)
|
|
61
|
+
|
|
62
|
+
typeday = models.CharField(
|
|
63
|
+
max_length=10 * len(default_timevar_typeday()),
|
|
64
|
+
default=default_timevar_typeday(),
|
|
65
|
+
)
|
|
66
|
+
month = models.CharField(
|
|
67
|
+
max_length=10 * len(default_timevar_month()), default=default_timevar_month()
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# pre-calculated normalization constants
|
|
71
|
+
typeday_sum = models.FloatField(editable=False)
|
|
72
|
+
_normalization_constant = models.FloatField(editable=False)
|
|
73
|
+
|
|
74
|
+
class Meta:
|
|
75
|
+
abstract = True
|
|
76
|
+
|
|
77
|
+
def __str__(self):
|
|
78
|
+
"""Return a unicode representation of this timevariation."""
|
|
79
|
+
return self.name
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def normalization_constant(self):
|
|
83
|
+
if self._normalization_constant is None:
|
|
84
|
+
timevar_normalize(self)
|
|
85
|
+
return self._normalization_constant
|
|
86
|
+
|
|
87
|
+
def save(self, *args, **kwargs):
|
|
88
|
+
"""Overloads save to ensure normalizing factors are calculated."""
|
|
89
|
+
timevar_normalize(self)
|
|
90
|
+
super(TimevarBase, self).save(*args, **kwargs)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class Timevar(TimevarBase):
|
|
94
|
+
"""A source time-variation profile."""
|
|
95
|
+
|
|
96
|
+
class Meta(TimevarBase.Meta):
|
|
97
|
+
default_related_name = "timevars"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class FlowTimevar(TimevarBase):
|
|
101
|
+
"""A road time-variation profile."""
|
|
102
|
+
|
|
103
|
+
class Meta(TimevarBase.Meta):
|
|
104
|
+
default_related_name = "flow_timevars"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class ColdstartTimevar(TimevarBase):
|
|
108
|
+
"""A vehicle cold start time-variation profile."""
|
|
109
|
+
|
|
110
|
+
class Meta(TimevarBase.Meta):
|
|
111
|
+
default_related_name = "coldstart_timevars"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .rasterizer import EmissionRasterizer, Output # noqa: F401
|