Commit bc18b96cedce9e4dcbc8ed20a08c7888d6da2b74
1 parent
3f0d5ea2
Exists in
master
and in
2 other branches
Implement first (working!) draft of NetCDF generation.
Still more to do, like the time and the orbits.
Showing
1 changed file
with
125 additions
and
18 deletions
Show diff stats
web/run.py
1 | -import random | |
2 | -import datetime | |
3 | 1 | import StringIO |
4 | -from math import sqrt | |
5 | - | |
6 | -from os import listdir, environ, remove as removefile | |
7 | -from os.path import isfile, join, abspath, dirname, isdir | |
8 | - | |
9 | -import csv | |
10 | -import json | |
2 | +import datetime | |
3 | +import time | |
11 | 4 | import gzip |
5 | +import json | |
6 | +import logging | |
7 | +import random | |
12 | 8 | import tarfile |
13 | 9 | import urllib |
14 | -import logging | |
15 | -from pprint import pprint | |
16 | 10 | from csv import writer as csv_writer |
17 | -from yaml import load as yaml_load | |
11 | +from math import sqrt | |
12 | +from os import environ, remove as removefile | |
13 | +from os.path import isfile, join, abspath, dirname | |
14 | + | |
18 | 15 | from flask import Flask |
19 | -from flask import redirect, url_for, send_from_directory, abort as abort_flask | |
20 | 16 | from flask import request |
17 | +from flask import url_for, send_from_directory, abort as abort_flask | |
21 | 18 | from jinja2 import Environment, FileSystemLoader |
22 | 19 | from netCDF4 import Dataset |
23 | - | |
20 | +from yaml import load as yaml_load | |
24 | 21 | |
25 | 22 | # PATH RELATIVITY ############################################################# |
26 | 23 | |
... | ... | @@ -162,6 +159,13 @@ def render_view(view, context=None): |
162 | 159 | # ) |
163 | 160 | |
164 | 161 | |
162 | +def is_list_in_list(needle, haystack): | |
163 | + for n in needle: | |
164 | + if n not in haystack: | |
165 | + return False | |
166 | + return True | |
167 | + | |
168 | + | |
165 | 169 | def datetime_from_list(time_list): |
166 | 170 | """ |
167 | 171 | Datetimes in retrieved CDFs are stored as lists of numbers, |
... | ... | @@ -276,9 +280,26 @@ def retrieve_amda_netcdf(orbiter, what, started_at, stopped_at): |
276 | 280 | # The order matters. If you change this you also need to change the |
277 | 281 | # innermost loop of `get_data_for_target`. |
278 | 282 | # The javascript knows the targets' properties under these names. |
283 | +# DEPRECATED, use PARAMETERS instead | |
279 | 284 | PROPERTIES = ('time', 'vrad', 'vtan', 'vlen', 'magn', 'temp', 'pdyn', 'dens', |
280 | 285 | 'angl', 'xhci', 'yhci') |
281 | 286 | |
287 | +PARAMETERS = { | |
288 | + 'vlen': { | |
289 | + 'slug': 'vlen', | |
290 | + 'name': 'Velocity', | |
291 | + 'unit': 'km/s', | |
292 | + 'show': True, | |
293 | + }, | |
294 | + 'magn': { | |
295 | + 'slug': 'magn', | |
296 | + 'name': 'Magnetism', | |
297 | + 'unit': 'nT', | |
298 | + 'show': True, | |
299 | + }, | |
300 | + # fixme: add more | |
301 | +} | |
302 | + | |
282 | 303 | |
283 | 304 | def get_data_for_target(target_config, started_at, stopped_at): |
284 | 305 | """ |
... | ... | @@ -509,12 +530,11 @@ def download_target_csv(target, started_at, stopped_at): |
509 | 530 | @app.route("/<targets>_<started_at>_<stopped_at>.tar.gz") |
510 | 531 | def download_targets_tarball(targets, started_at, stopped_at): |
511 | 532 | """ |
512 | - Grab data and orbit data for the specified `target`, | |
513 | - rearrange it and return it as a CSV file. | |
514 | - `started_at` and `stopped_at` should be UTC. | |
533 | + Grab data and orbit data for each of the specified `targets`, | |
534 | + in their own CSV file, and make a tarball of them. | |
535 | + `started_at` and `stopped_at` should be UTC strings. | |
515 | 536 | |
516 | 537 | targets: string list of targets' slugs, separated by `-`. |
517 | - This will fail hard if targets' slugs start having `-` in them. | |
518 | 538 | """ |
519 | 539 | separator = '-' |
520 | 540 | targets = targets.split(separator) |
... | ... | @@ -566,6 +586,93 @@ def download_targets_tarball(targets, started_at, stopped_at): |
566 | 586 | return send_from_directory(get_path("../cache/"), gzip_filename) |
567 | 587 | |
568 | 588 | |
589 | +@app.route("/<targets>_<params>_<started_at>_<stopped_at>.nc") | |
590 | +def download_targets_netcdf(targets, params, started_at, stopped_at): | |
591 | + """ | |
592 | + Grab data and orbit data for the specified `target`, | |
593 | + rearrange it and return it as a CSV file. | |
594 | + `started_at` and `stopped_at` should be UTC. | |
595 | + | |
596 | + targets: string list of targets' slugs, separated by `-`. | |
597 | + params: string list of targets' parameters, separated by `-`. | |
598 | + """ | |
599 | + separator = '-' | |
600 | + targets = targets.split(separator) | |
601 | + targets.sort() | |
602 | + targets_configs = [] | |
603 | + for target in targets: | |
604 | + if not target: | |
605 | + abort(400, "Invalid targets format : `%s`." % targets) | |
606 | + targets_configs.append(get_target_config(target)) | |
607 | + if 0 == len(targets_configs): | |
608 | + abort(400, "No valid targets specified. What are you doing?") | |
609 | + params = params.split(separator) | |
610 | + params.sort() | |
611 | + if 0 == len(params): | |
612 | + abort(400, "No valid parameters specified. What are you doing?") | |
613 | + if not is_list_in_list(params, PARAMETERS.keys()): | |
614 | + abort(400, "Some parameters are not recognized in '%s'." % str(params)) | |
615 | + | |
616 | + date_fmt = "%Y-%m-%dT%H:%M:%S" | |
617 | + try: | |
618 | + started_at = datetime.datetime.strptime(started_at, date_fmt) | |
619 | + except: | |
620 | + abort(400, "Invalid started_at parameter : '%s'." % started_at) | |
621 | + try: | |
622 | + stopped_at = datetime.datetime.strptime(stopped_at, date_fmt) | |
623 | + except: | |
624 | + abort(400, "Invalid stopped_at parameter : '%s'." % stopped_at) | |
625 | + sta = started_at.strftime(date_fmt) | |
626 | + sto = stopped_at.strftime(date_fmt) | |
627 | + | |
628 | + nc_filename = "%s_%s_%s_%s.nc" % \ | |
629 | + (separator.join(targets), separator.join(params), sta, sto) | |
630 | + nc_path = get_path("../cache/%s" % nc_filename) | |
631 | + | |
632 | + if not isfile(nc_path): | |
633 | + log.debug("Creating the NetCDF file '%s'..." % nc_filename) | |
634 | + nc_handle = Dataset(nc_path, "w", format="NETCDF4") | |
635 | + try: | |
636 | + nc_handle.description = "TODO" # todo | |
637 | + nc_handle.history = "Created " + time.ctime(time.time()) | |
638 | + nc_handle.source = "Transplanet (CDDP)" | |
639 | + available_params = list(PROPERTIES) | |
640 | + for target in targets_configs: | |
641 | + target_slug = target['slug'] | |
642 | + log.debug("Adding group '%s' to the NetCDF..." % target_slug) | |
643 | + nc_group = nc_handle.createGroup(target_slug) | |
644 | + data = get_data_for_target(target, started_at, stopped_at) | |
645 | + dkeys = sorted(data) | |
646 | + nc_handle.createDimension('dim_'+target_slug, len(dkeys)) | |
647 | + # todo: add time | |
648 | + nc_vars = [] | |
649 | + indices = [] | |
650 | + for param in params: | |
651 | + indices.append(available_params.index(param)) | |
652 | + nc_var = nc_group.createVariable( | |
653 | + param, 'f8', ('dim_'+target_slug,) | |
654 | + ) | |
655 | + nc_var.units = PARAMETERS[param]['unit'] | |
656 | + nc_vars.append(nc_var) | |
657 | + for i, nc_var in enumerate(nc_vars): | |
658 | + index = indices[i] | |
659 | + values = [] | |
660 | + for dkey in dkeys: | |
661 | + dval = data[dkey] | |
662 | + values.append(dval[index]) | |
663 | + nc_var[:] = values | |
664 | + # todo: add orbit x and y | |
665 | + except Exception as e: | |
666 | + raise e | |
667 | + finally: | |
668 | + nc_handle.close() | |
669 | + | |
670 | + if not isfile(nc_path): | |
671 | + abort(500, "No NetCDF to serve. Looked at '%s'." % nc_path) | |
672 | + | |
673 | + return send_from_directory(get_path("../cache/"), nc_filename) | |
674 | + | |
675 | + | |
569 | 676 | # API ######################################################################### |
570 | 677 | |
571 | 678 | @app.route("/cache/clear") | ... | ... |