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,599 @@
|
|
|
1
|
+
"""Command line interface for managing a Clair emission inventory offline."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import datetime
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from math import ceil
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from django.core import serializers
|
|
11
|
+
from openpyxl import load_workbook
|
|
12
|
+
from pyproj import CRS
|
|
13
|
+
from pyproj.exceptions import CRSError
|
|
14
|
+
|
|
15
|
+
import cetk
|
|
16
|
+
from cetk import logging
|
|
17
|
+
from cetk.db import run_migrate
|
|
18
|
+
from cetk.tools.utils import (
|
|
19
|
+
CalledProcessError,
|
|
20
|
+
SubprocessError,
|
|
21
|
+
add_standard_command_options,
|
|
22
|
+
check_and_get_path,
|
|
23
|
+
create_from_template,
|
|
24
|
+
get_db,
|
|
25
|
+
get_template_db,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
log = logging.getLogger("cetk")
|
|
29
|
+
|
|
30
|
+
settings = cetk.configure()
|
|
31
|
+
|
|
32
|
+
from cetk.edb.const import DEFAULT_SRID # noqa
|
|
33
|
+
from cetk.edb.const import DEFAULT_EMISSION_UNIT, SHEET_NAMES, WGS84_SRID # noqa
|
|
34
|
+
from cetk.edb.exporters import export_sources # noqa
|
|
35
|
+
from cetk.edb.importers import import_activitycodesheet # noqa
|
|
36
|
+
from cetk.edb.importers import ( # noqa
|
|
37
|
+
import_gridsources,
|
|
38
|
+
import_sourceactivities,
|
|
39
|
+
import_traffic,
|
|
40
|
+
)
|
|
41
|
+
from cetk.edb.importers.utils import ValidationError # noqa
|
|
42
|
+
from cetk.edb.models import Settings, Substance # noqa
|
|
43
|
+
from cetk.edb.rasterize.rasterizer import EmissionRasterizer, Output # noqa
|
|
44
|
+
from cetk.emissions.calc import aggregate_emissions # noqa
|
|
45
|
+
from cetk.emissions.calc import get_used_substances # noqa
|
|
46
|
+
from cetk.emissions.views import create_emission_table # noqa
|
|
47
|
+
|
|
48
|
+
SOURCETYPES = ("point", "area", "grid", "road")
|
|
49
|
+
|
|
50
|
+
sheet_choices = ["All"]
|
|
51
|
+
sheet_choices.extend(SHEET_NAMES)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def adjust_extent(extent, srid, cellsize):
|
|
55
|
+
"""adjust extent to include an integer nr of cells."""
|
|
56
|
+
x1, y1, x2, y2 = (
|
|
57
|
+
extent or Settings.get_current().extent.transform(srid, clone=True).extent
|
|
58
|
+
)
|
|
59
|
+
nx = ceil((x2 - x1) / cellsize)
|
|
60
|
+
ny = ceil((y2 - y1) / cellsize)
|
|
61
|
+
x2 = x1 + nx * cellsize
|
|
62
|
+
y2 = y1 + ny * cellsize
|
|
63
|
+
extent = (x1, y1, x2, y2)
|
|
64
|
+
# Settings.extent is a Polygon, Settings.extent.extent a tuple (x1, y1, x2, y2)
|
|
65
|
+
if extent is None:
|
|
66
|
+
log.error("could not rasterize emissions, default extent not set")
|
|
67
|
+
sys.exit(1)
|
|
68
|
+
return extent, ny, nx
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Editor(object):
|
|
72
|
+
def __init__(self):
|
|
73
|
+
self.db_path = settings.DATABASES["default"]["NAME"]
|
|
74
|
+
|
|
75
|
+
def migrate(self, template=False, db_path=None):
|
|
76
|
+
if db_path is None:
|
|
77
|
+
if template:
|
|
78
|
+
db_path = get_template_db()
|
|
79
|
+
else:
|
|
80
|
+
db_path = get_db()
|
|
81
|
+
log.debug(f"Running migrations for database {db_path}")
|
|
82
|
+
try:
|
|
83
|
+
std_out, std_err = run_migrate(db_path=db_path)
|
|
84
|
+
except (CalledProcessError, SubprocessError) as err:
|
|
85
|
+
log.error(f"Error while migrating {db_path}: {err}")
|
|
86
|
+
else:
|
|
87
|
+
log.debug(f"Successfully migrated database {db_path}")
|
|
88
|
+
|
|
89
|
+
def info(self, **kwargs):
|
|
90
|
+
settings = Settings.get_current()
|
|
91
|
+
print(serializers.serialize("json", [settings]))
|
|
92
|
+
|
|
93
|
+
def update_settings(self, srid=None, codeset1=None, codeset2=None, codeset3=None):
|
|
94
|
+
from cetk.edb.models import CodeSet, Settings
|
|
95
|
+
|
|
96
|
+
settings = Settings.get_current()
|
|
97
|
+
if srid == WGS84_SRID:
|
|
98
|
+
log.error(
|
|
99
|
+
"SRID in settings must be a "
|
|
100
|
+
"projected coordinate system in meters, not lat/lon.\n"
|
|
101
|
+
)
|
|
102
|
+
sys.exit(1)
|
|
103
|
+
|
|
104
|
+
elif srid is not None:
|
|
105
|
+
try:
|
|
106
|
+
crs = CRS.from_epsg(srid) # noqa
|
|
107
|
+
except CRSError:
|
|
108
|
+
log.error(f"SRID={srid} does not define a valid CRS.\n")
|
|
109
|
+
sys.exit(1)
|
|
110
|
+
settings.srid = srid
|
|
111
|
+
log.info(f"Updated srid to {srid}.\n")
|
|
112
|
+
try:
|
|
113
|
+
if codeset1 is not None:
|
|
114
|
+
slug = codeset1
|
|
115
|
+
settings.codeset1 = CodeSet.objects.get(slug=slug)
|
|
116
|
+
log.info(f"Updated code-set1 to {slug}.\n")
|
|
117
|
+
if codeset2 is not None:
|
|
118
|
+
slug = codeset2
|
|
119
|
+
settings.codeset2 = CodeSet.objects.get(slug=slug)
|
|
120
|
+
log.info(f"Updated code-set2 to {slug}.\n")
|
|
121
|
+
if codeset3 is not None:
|
|
122
|
+
slug = codeset3
|
|
123
|
+
settings.codeset3 = CodeSet.objects.get(slug=slug)
|
|
124
|
+
log.info(f"Updated code-set3 to {slug}.\n")
|
|
125
|
+
except CodeSet.DoesNotExist:
|
|
126
|
+
log.error(f"No code-set defined with slug: '{slug}'")
|
|
127
|
+
sys.exit(1)
|
|
128
|
+
settings.save()
|
|
129
|
+
|
|
130
|
+
def import_workbook(self, filename, sheets=SHEET_NAMES, dry_run=False):
|
|
131
|
+
return_msg = []
|
|
132
|
+
db_updates = {}
|
|
133
|
+
workbook = load_workbook(filename=filename, data_only=True, read_only=True)
|
|
134
|
+
import_sheets = [
|
|
135
|
+
s
|
|
136
|
+
for s in SHEET_NAMES
|
|
137
|
+
if s in frozenset(sheets) and s in frozenset(workbook.sheetnames)
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
missing_sheets = set(import_sheets).difference(workbook.sheetnames)
|
|
142
|
+
if len(missing_sheets) > 0:
|
|
143
|
+
log.info(f"Workbook has no sheets named: {missing_sheets}")
|
|
144
|
+
|
|
145
|
+
# PointSource and AreaSource
|
|
146
|
+
sourceact = [
|
|
147
|
+
"CodeSet",
|
|
148
|
+
"ActivityCode",
|
|
149
|
+
"EmissionFactor",
|
|
150
|
+
"Timevar",
|
|
151
|
+
"PointSource",
|
|
152
|
+
"AreaSource",
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
if any(name in sourceact for name in import_sheets):
|
|
156
|
+
updates, msgs = import_sourceactivities(
|
|
157
|
+
filename,
|
|
158
|
+
import_sheets=import_sheets,
|
|
159
|
+
validation=dry_run,
|
|
160
|
+
)
|
|
161
|
+
db_updates.update(updates)
|
|
162
|
+
return_msg += msgs
|
|
163
|
+
|
|
164
|
+
traffic = [
|
|
165
|
+
"RoadSource",
|
|
166
|
+
"VehicleFuel",
|
|
167
|
+
"Fleet",
|
|
168
|
+
"CongestionProfile",
|
|
169
|
+
"FlowTimevar",
|
|
170
|
+
"ColdstartTimevar",
|
|
171
|
+
"RoadAttribute",
|
|
172
|
+
"TrafficSituation",
|
|
173
|
+
"VehicleEmissionFactor",
|
|
174
|
+
]
|
|
175
|
+
if any(name in traffic for name in import_sheets):
|
|
176
|
+
updates, msgs = import_traffic(
|
|
177
|
+
filename, sheets=import_sheets, validation=dry_run
|
|
178
|
+
)
|
|
179
|
+
return_msg += msgs
|
|
180
|
+
db_updates.update(updates)
|
|
181
|
+
if "GridSource" in import_sheets:
|
|
182
|
+
updates, msgs = import_gridsources(filename)
|
|
183
|
+
db_updates.update(updates)
|
|
184
|
+
return_msg += msgs
|
|
185
|
+
# skip empty messages
|
|
186
|
+
# return_msg = [entry.strip() for entry in return_msg if entry]
|
|
187
|
+
if len(return_msg) > 0:
|
|
188
|
+
raise ValidationError()
|
|
189
|
+
except ValidationError as err:
|
|
190
|
+
return_msg.append(str(err))
|
|
191
|
+
if not dry_run:
|
|
192
|
+
run_type = "import"
|
|
193
|
+
else:
|
|
194
|
+
run_type = "validation"
|
|
195
|
+
if len(return_msg) > 10:
|
|
196
|
+
log.error(
|
|
197
|
+
f">10 errors during {run_type}, first 10:"
|
|
198
|
+
f"{os.linesep}{return_msg[:10]}"
|
|
199
|
+
)
|
|
200
|
+
else:
|
|
201
|
+
log.error(f"Errors during {run_type}:" f"{os.linesep}{return_msg}")
|
|
202
|
+
else:
|
|
203
|
+
log.info(f"getting here {datetime.datetime.now()}")
|
|
204
|
+
if not dry_run:
|
|
205
|
+
log.info(
|
|
206
|
+
f"{datetime.datetime.now()} successfully imported {db_updates}"
|
|
207
|
+
)
|
|
208
|
+
else:
|
|
209
|
+
log.info(
|
|
210
|
+
f"{datetime.datetime.now()} successfully validated {db_updates}"
|
|
211
|
+
)
|
|
212
|
+
workbook.close()
|
|
213
|
+
return db_updates, return_msg
|
|
214
|
+
|
|
215
|
+
def update_emission_tables(
|
|
216
|
+
self,
|
|
217
|
+
sourcetypes=None,
|
|
218
|
+
unit=DEFAULT_EMISSION_UNIT,
|
|
219
|
+
substances=None,
|
|
220
|
+
):
|
|
221
|
+
sourcetypes = sourcetypes or ("point", "area", "road")
|
|
222
|
+
substances = substances or get_used_substances()
|
|
223
|
+
if len(substances) == 0:
|
|
224
|
+
log.error("No emission factors or direct emissions found in database")
|
|
225
|
+
sys.exit(1)
|
|
226
|
+
|
|
227
|
+
for sourcetype in sourcetypes:
|
|
228
|
+
create_emission_table(sourcetype, substances=substances, unit=unit)
|
|
229
|
+
|
|
230
|
+
def aggregate_emissions(
|
|
231
|
+
self,
|
|
232
|
+
filename,
|
|
233
|
+
sourcetypes=None,
|
|
234
|
+
unit=DEFAULT_EMISSION_UNIT,
|
|
235
|
+
codeset=None,
|
|
236
|
+
substances=None,
|
|
237
|
+
):
|
|
238
|
+
df = aggregate_emissions(
|
|
239
|
+
sourcetypes=sourcetypes, unit=unit, codeset=codeset, substances=substances
|
|
240
|
+
)
|
|
241
|
+
try:
|
|
242
|
+
df.to_excel(filename)
|
|
243
|
+
except Exception as err:
|
|
244
|
+
log.error(
|
|
245
|
+
f"could not write aggregated emission to file {filename}: {str(err)}"
|
|
246
|
+
)
|
|
247
|
+
sys.exit(1)
|
|
248
|
+
|
|
249
|
+
def rasterize_emissions(
|
|
250
|
+
self,
|
|
251
|
+
outputpath,
|
|
252
|
+
cellsize,
|
|
253
|
+
sourcetypes=None,
|
|
254
|
+
point_ids=None,
|
|
255
|
+
area_ids=None,
|
|
256
|
+
grid_ids=None,
|
|
257
|
+
road_ids=None,
|
|
258
|
+
unit=DEFAULT_EMISSION_UNIT,
|
|
259
|
+
codeset=None,
|
|
260
|
+
substances=None,
|
|
261
|
+
begin=None,
|
|
262
|
+
end=None,
|
|
263
|
+
extent=None,
|
|
264
|
+
srid=None,
|
|
265
|
+
timezone=None,
|
|
266
|
+
):
|
|
267
|
+
substances = substances or get_used_substances()
|
|
268
|
+
timezone = timezone or datetime.timezone.utc
|
|
269
|
+
srid = srid or DEFAULT_SRID
|
|
270
|
+
extent, ny, nx = adjust_extent(extent, srid, cellsize)
|
|
271
|
+
try:
|
|
272
|
+
output = Output(
|
|
273
|
+
extent=extent, timezone=timezone, path=outputpath, srid=srid
|
|
274
|
+
)
|
|
275
|
+
rasterizer = EmissionRasterizer(output, nx=nx, ny=ny)
|
|
276
|
+
rasterizer.process(
|
|
277
|
+
substances,
|
|
278
|
+
begin=begin,
|
|
279
|
+
end=end,
|
|
280
|
+
unit=unit,
|
|
281
|
+
sourcetypes=sourcetypes,
|
|
282
|
+
point_ids=point_ids,
|
|
283
|
+
area_ids=area_ids,
|
|
284
|
+
grid_ids=grid_ids,
|
|
285
|
+
road_ids=road_ids,
|
|
286
|
+
)
|
|
287
|
+
except Exception as err:
|
|
288
|
+
log.error(f"could not rasterize emissions: {str(err)}")
|
|
289
|
+
sys.exit(1)
|
|
290
|
+
|
|
291
|
+
def export_data(self, filename):
|
|
292
|
+
export_sources(filename)
|
|
293
|
+
return True
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def main():
|
|
297
|
+
db_path = get_db() or "unspecified"
|
|
298
|
+
parser = argparse.ArgumentParser(
|
|
299
|
+
description="Manage Clair offline emission inventories",
|
|
300
|
+
usage=f"""cetk <command> [<args>]
|
|
301
|
+
|
|
302
|
+
Main commands are:
|
|
303
|
+
create create an sqlite inventory
|
|
304
|
+
migrate migrate an sqlite inventory
|
|
305
|
+
info print settings
|
|
306
|
+
import import data
|
|
307
|
+
export export data
|
|
308
|
+
calc calculate emissions
|
|
309
|
+
settings change database settings
|
|
310
|
+
|
|
311
|
+
Current database is {db_path} (set by $CETK_DATABASE_PATH)
|
|
312
|
+
""",
|
|
313
|
+
)
|
|
314
|
+
add_standard_command_options(parser)
|
|
315
|
+
parser.add_argument(
|
|
316
|
+
"command",
|
|
317
|
+
help="Subcommand to run",
|
|
318
|
+
choices=("migrate", "create", "info", "import", "export", "calc", "settings"),
|
|
319
|
+
)
|
|
320
|
+
verbosity = [arg for arg in sys.argv if arg == "-v"]
|
|
321
|
+
sys_args = [arg for arg in sys.argv if arg != "-v"]
|
|
322
|
+
sub_args = sys_args[2:]
|
|
323
|
+
main_args = parser.parse_args(args=sys_args[1:2] + verbosity)
|
|
324
|
+
editor = Editor()
|
|
325
|
+
if main_args.command == "create":
|
|
326
|
+
sub_parser = argparse.ArgumentParser(
|
|
327
|
+
description="Create database from template.",
|
|
328
|
+
usage="cetk create <filename>",
|
|
329
|
+
)
|
|
330
|
+
sub_parser.add_argument(
|
|
331
|
+
"filename",
|
|
332
|
+
help="Path of new database",
|
|
333
|
+
)
|
|
334
|
+
args = sub_parser.parse_args(sub_args)
|
|
335
|
+
create_from_template(args.filename)
|
|
336
|
+
log.debug(
|
|
337
|
+
f"Created new database '{args.filename}' "
|
|
338
|
+
f"from template '{get_template_db()}'"
|
|
339
|
+
)
|
|
340
|
+
sys.exit(0)
|
|
341
|
+
|
|
342
|
+
if (
|
|
343
|
+
len(sys.argv) < 2
|
|
344
|
+
and sys.argv[2] not in ("-h", "--help", "--version")
|
|
345
|
+
and db_path == "unspecified"
|
|
346
|
+
):
|
|
347
|
+
sys.stderr.write("No database specified, set by $CETK_DATABASE_PATH\n")
|
|
348
|
+
sys.exit(1)
|
|
349
|
+
|
|
350
|
+
if main_args.command == "info":
|
|
351
|
+
sub_parser = argparse.ArgumentParser(
|
|
352
|
+
description="Print or update settings.",
|
|
353
|
+
usage="usage: cetk info ",
|
|
354
|
+
)
|
|
355
|
+
args = sub_parser.parse_args(sub_args)
|
|
356
|
+
editor.info()
|
|
357
|
+
|
|
358
|
+
if main_args.command == "migrate":
|
|
359
|
+
sub_parser = argparse.ArgumentParser(
|
|
360
|
+
description=f"Migrate database {db_path}.",
|
|
361
|
+
usage="usage: cetk migrate",
|
|
362
|
+
)
|
|
363
|
+
sub_parser.add_argument(
|
|
364
|
+
"--template",
|
|
365
|
+
action="store_true",
|
|
366
|
+
help="Migrate the template database",
|
|
367
|
+
)
|
|
368
|
+
sub_parser.add_argument(
|
|
369
|
+
"--dbpath",
|
|
370
|
+
help="Specify database path manually",
|
|
371
|
+
)
|
|
372
|
+
args = sub_parser.parse_args(sub_args)
|
|
373
|
+
editor.migrate(template=args.template, db_path=args.dbpath)
|
|
374
|
+
elif main_args.command == "import":
|
|
375
|
+
sub_parser = argparse.ArgumentParser(
|
|
376
|
+
description="Import data from an xlsx-file",
|
|
377
|
+
usage="cetk import <filename> [options]",
|
|
378
|
+
)
|
|
379
|
+
sub_parser.add_argument(
|
|
380
|
+
"filename", help="Path to xslx-file", type=check_and_get_path
|
|
381
|
+
)
|
|
382
|
+
sub_parser.add_argument(
|
|
383
|
+
"--sheets",
|
|
384
|
+
nargs="+",
|
|
385
|
+
default=SHEET_NAMES,
|
|
386
|
+
help=f"List of sheets to import, valid names {SHEET_NAMES}",
|
|
387
|
+
)
|
|
388
|
+
sub_parser.add_argument(
|
|
389
|
+
"--dryrun",
|
|
390
|
+
action="store_true",
|
|
391
|
+
help="Do dry run to validate import file without actually importing data",
|
|
392
|
+
)
|
|
393
|
+
args = sub_parser.parse_args(sub_args)
|
|
394
|
+
if not Path(db_path).exists():
|
|
395
|
+
sys.stderr.write(
|
|
396
|
+
f"Database {db_path} does not exist, first run "
|
|
397
|
+
"'cetk create' or 'cetk migrate'\n"
|
|
398
|
+
)
|
|
399
|
+
sys.exit(1)
|
|
400
|
+
|
|
401
|
+
editor.import_workbook(args.filename, sheets=args.sheets, dry_run=args.dryrun)
|
|
402
|
+
sys.exit(0)
|
|
403
|
+
elif main_args.command == "calc":
|
|
404
|
+
sub_parser = argparse.ArgumentParser(
|
|
405
|
+
description="Calculate emissions",
|
|
406
|
+
usage="cetk calc [options]",
|
|
407
|
+
)
|
|
408
|
+
sub_parser.add_argument(
|
|
409
|
+
"--unit",
|
|
410
|
+
default=DEFAULT_EMISSION_UNIT,
|
|
411
|
+
help="Unit of emissions, default=%(default)s",
|
|
412
|
+
)
|
|
413
|
+
sub_parser.add_argument(
|
|
414
|
+
"--sourcetypes", nargs="*", help="Only sourcetypes", choices=SOURCETYPES
|
|
415
|
+
)
|
|
416
|
+
sub_parser.add_argument(
|
|
417
|
+
"--substances",
|
|
418
|
+
nargs="*",
|
|
419
|
+
help="Only substances (default is all with emissions)",
|
|
420
|
+
choices=Substance.objects.values_list("slug", flat=True),
|
|
421
|
+
metavar=("NOx", "PM10"),
|
|
422
|
+
)
|
|
423
|
+
calc_grp = sub_parser.add_mutually_exclusive_group()
|
|
424
|
+
calc_grp.add_argument(
|
|
425
|
+
"--update", help="Create/update emission tables", action="store_true"
|
|
426
|
+
)
|
|
427
|
+
calc_grp.add_argument(
|
|
428
|
+
"--aggregate", help="Aggregate emissions", metavar="FILENAME"
|
|
429
|
+
)
|
|
430
|
+
aggregate_grp = sub_parser.add_argument_group(
|
|
431
|
+
"aggregate emissions", description="Options to aggregate emissions"
|
|
432
|
+
)
|
|
433
|
+
aggregate_grp.add_argument(
|
|
434
|
+
"--codeset", help="Aggregate emissions by codeset", metavar="SLUG"
|
|
435
|
+
)
|
|
436
|
+
calc_grp.add_argument(
|
|
437
|
+
"--rasterize", help="Rasterize emissions", metavar="OUTPUTPATH"
|
|
438
|
+
)
|
|
439
|
+
rasterize_grp = sub_parser.add_argument_group(
|
|
440
|
+
"rasterize emissions", description="Settings to rasterize emissions"
|
|
441
|
+
)
|
|
442
|
+
rasterize_grp.add_argument(
|
|
443
|
+
"--cellsize", help="Cellsize (meter) in output raster", type=float
|
|
444
|
+
)
|
|
445
|
+
rasterize_grp.add_argument(
|
|
446
|
+
"--extent",
|
|
447
|
+
help="Extent of output raster. Settings.extent is taken otherwise",
|
|
448
|
+
nargs=4,
|
|
449
|
+
type=float,
|
|
450
|
+
metavar=("x1", "y1", "x2", "y2"),
|
|
451
|
+
)
|
|
452
|
+
rasterize_grp.add_argument(
|
|
453
|
+
"--srid",
|
|
454
|
+
help="EPSG of output raster. 4-5 digits integer",
|
|
455
|
+
metavar="EPSG",
|
|
456
|
+
)
|
|
457
|
+
rasterize_grp.add_argument(
|
|
458
|
+
"--begin",
|
|
459
|
+
help="when hourly rasters are desired, specify begin date."
|
|
460
|
+
+ " Time 00:00 assumed",
|
|
461
|
+
metavar="YYMMDDHH",
|
|
462
|
+
)
|
|
463
|
+
rasterize_grp.add_argument(
|
|
464
|
+
"--end",
|
|
465
|
+
help="when hourly rasters are desired, specify end date"
|
|
466
|
+
+ " Time 00:00 assumed",
|
|
467
|
+
metavar="YYMMDDHH",
|
|
468
|
+
)
|
|
469
|
+
rasterize_grp.add_argument(
|
|
470
|
+
"--point-ids",
|
|
471
|
+
nargs="+",
|
|
472
|
+
help="List of pointsource id's to include in rasterizer. "
|
|
473
|
+
+ "All sources will be rasterized for other sourcetypes if not limited "
|
|
474
|
+
+ "using argument sourcetypes or filtering specified for ids for "
|
|
475
|
+
+ " other sourcetypes too.",
|
|
476
|
+
)
|
|
477
|
+
rasterize_grp.add_argument(
|
|
478
|
+
"--area-ids",
|
|
479
|
+
nargs="+",
|
|
480
|
+
help="List of areasource id's to include in rasterizer",
|
|
481
|
+
)
|
|
482
|
+
rasterize_grp.add_argument(
|
|
483
|
+
"--road-ids",
|
|
484
|
+
nargs="+",
|
|
485
|
+
help="List of roadsource id's to include in rasterizer",
|
|
486
|
+
)
|
|
487
|
+
rasterize_grp.add_argument(
|
|
488
|
+
"--grid-ids",
|
|
489
|
+
nargs="+",
|
|
490
|
+
help="List of gridsource id's to include in rasterizer",
|
|
491
|
+
)
|
|
492
|
+
# TODO add argument begin/end for rasterize
|
|
493
|
+
# TODO add argument to aggregate emissions within polygon
|
|
494
|
+
|
|
495
|
+
args = sub_parser.parse_args(sub_args)
|
|
496
|
+
try:
|
|
497
|
+
if args.substances is not None:
|
|
498
|
+
substances = []
|
|
499
|
+
for s in args.substances:
|
|
500
|
+
substances.append(Substance.objects.get(slug=s))
|
|
501
|
+
else:
|
|
502
|
+
substances = None
|
|
503
|
+
except Substance.DoesNotExist:
|
|
504
|
+
sys.stderr.write(f"Substance {s} does not exist.\n")
|
|
505
|
+
if not Path(db_path).exists():
|
|
506
|
+
sys.stderr.write("Database does not exist.\n")
|
|
507
|
+
sys.exit(1)
|
|
508
|
+
if args.update:
|
|
509
|
+
editor.update_emission_tables(
|
|
510
|
+
sourcetypes=args.sourcetypes, unit=args.unit, substances=args.substances
|
|
511
|
+
)
|
|
512
|
+
sys.stdout.write("Successfully updated tables\n")
|
|
513
|
+
sys.exit(0)
|
|
514
|
+
if args.aggregate is not None:
|
|
515
|
+
editor.aggregate_emissions(
|
|
516
|
+
args.aggregate,
|
|
517
|
+
substances=substances,
|
|
518
|
+
sourcetypes=args.sourcetypes,
|
|
519
|
+
unit=args.unit,
|
|
520
|
+
codeset=args.codeset,
|
|
521
|
+
)
|
|
522
|
+
sys.stdout.write("Successfully aggregated emissions\n")
|
|
523
|
+
sys.exit(0)
|
|
524
|
+
if args.rasterize is not None:
|
|
525
|
+
if args.begin is not None:
|
|
526
|
+
args.begin = datetime.datetime.strptime(args.begin, "%y%m%d%H").replace(
|
|
527
|
+
tzinfo=datetime.timezone.utc
|
|
528
|
+
)
|
|
529
|
+
if args.end is not None:
|
|
530
|
+
args.end = datetime.datetime.strptime(args.end, "%y%m%d%H").replace(
|
|
531
|
+
tzinfo=datetime.timezone.utc
|
|
532
|
+
)
|
|
533
|
+
else:
|
|
534
|
+
sys.stderr.write(
|
|
535
|
+
"If begin is specified," + " end has to be specified too.\n"
|
|
536
|
+
)
|
|
537
|
+
sys.exit(1)
|
|
538
|
+
editor.rasterize_emissions(
|
|
539
|
+
args.rasterize,
|
|
540
|
+
args.cellsize,
|
|
541
|
+
substances=substances,
|
|
542
|
+
sourcetypes=args.sourcetypes,
|
|
543
|
+
point_ids=args.point_ids,
|
|
544
|
+
area_ids=args.area_ids,
|
|
545
|
+
grid_ids=args.grid_ids,
|
|
546
|
+
road_ids=args.road_ids,
|
|
547
|
+
unit=args.unit,
|
|
548
|
+
extent=args.extent,
|
|
549
|
+
srid=args.srid,
|
|
550
|
+
begin=args.begin,
|
|
551
|
+
end=args.end,
|
|
552
|
+
) # TODO add arguments for codeset, substances, begin/end, timezone!
|
|
553
|
+
sys.stdout.write("Successfully rasterized emissions\n")
|
|
554
|
+
sys.exit(0)
|
|
555
|
+
|
|
556
|
+
elif main_args.command == "export":
|
|
557
|
+
sub_parser = argparse.ArgumentParser(
|
|
558
|
+
description="Export data to xlsx-file",
|
|
559
|
+
usage="cetk export <filename> [options]",
|
|
560
|
+
)
|
|
561
|
+
sub_parser.add_argument("filename", help="Path to xslx-file")
|
|
562
|
+
if not Path(db_path).exists():
|
|
563
|
+
sys.stderr.write(
|
|
564
|
+
"Database " + db_path + " does not exist, first run "
|
|
565
|
+
"'cetk create' or 'cetk migrate'\n"
|
|
566
|
+
)
|
|
567
|
+
sys.exit(1)
|
|
568
|
+
|
|
569
|
+
args = sub_parser.parse_args(sub_args)
|
|
570
|
+
try:
|
|
571
|
+
# Check if the file can be created at the given path
|
|
572
|
+
os.access(args.filename, os.W_OK | os.X_OK)
|
|
573
|
+
except Exception as e:
|
|
574
|
+
sys.stderr.write(f"File {args.filename} cannot be created: \n {e} ")
|
|
575
|
+
sys.exit(1)
|
|
576
|
+
status = editor.export_data(args.filename)
|
|
577
|
+
if status:
|
|
578
|
+
sys.stdout.write(f"Exported data from {db_path} to {args.filename}.\n")
|
|
579
|
+
sys.exit(0)
|
|
580
|
+
else:
|
|
581
|
+
sys.stderr.write("Did not export data, something went wrong.")
|
|
582
|
+
sys.exit(1)
|
|
583
|
+
|
|
584
|
+
elif main_args.command == "settings":
|
|
585
|
+
sub_parser = argparse.ArgumentParser(
|
|
586
|
+
description="Update settings",
|
|
587
|
+
usage="cetk settings [--srid EPSG] [--codeset1 SLUG] [--codeset2 SLUG] [--codeset3 SLUG]", # noqa: E501
|
|
588
|
+
)
|
|
589
|
+
sub_parser.add_argument("--srid", type=int, help="Update srid in settings")
|
|
590
|
+
sub_parser.add_argument("--codeset1", help="Update code-set1")
|
|
591
|
+
sub_parser.add_argument("--codeset2", help="Update code-set2")
|
|
592
|
+
sub_parser.add_argument("--codeset3", help="Update code-set3")
|
|
593
|
+
args = sub_parser.parse_args(sub_args)
|
|
594
|
+
editor.update_settings(
|
|
595
|
+
srid=args.srid,
|
|
596
|
+
codeset1=args.codeset1,
|
|
597
|
+
codeset2=args.codeset2,
|
|
598
|
+
codeset3=args.codeset3,
|
|
599
|
+
)
|
cetk/tools/manage.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Djangos management command for cetk."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main():
|
|
8
|
+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cetk.settings")
|
|
9
|
+
try:
|
|
10
|
+
from django.core.management import execute_from_command_line
|
|
11
|
+
except ImportError:
|
|
12
|
+
# The above import may fail for some other reason. Ensure that the
|
|
13
|
+
# issue is really that Django is missing to avoid masking other
|
|
14
|
+
# exceptions on Python 2.
|
|
15
|
+
try:
|
|
16
|
+
import django # noqa
|
|
17
|
+
except ImportError:
|
|
18
|
+
raise ImportError(
|
|
19
|
+
"Couldn't import Django. Are you sure it's installed and "
|
|
20
|
+
"available on your PYTHONPATH environment variable? Did you "
|
|
21
|
+
"forget to activate a virtual environment?"
|
|
22
|
+
)
|
|
23
|
+
raise
|
|
24
|
+
execute_from_command_line(sys.argv)
|