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 | import StringIO | 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 | import gzip | 4 | import gzip |
5 | +import json | ||
6 | +import logging | ||
7 | +import random | ||
12 | import tarfile | 8 | import tarfile |
13 | import urllib | 9 | import urllib |
14 | -import logging | ||
15 | -from pprint import pprint | ||
16 | from csv import writer as csv_writer | 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 | from flask import Flask | 15 | from flask import Flask |
19 | -from flask import redirect, url_for, send_from_directory, abort as abort_flask | ||
20 | from flask import request | 16 | from flask import request |
17 | +from flask import url_for, send_from_directory, abort as abort_flask | ||
21 | from jinja2 import Environment, FileSystemLoader | 18 | from jinja2 import Environment, FileSystemLoader |
22 | from netCDF4 import Dataset | 19 | from netCDF4 import Dataset |
23 | - | 20 | +from yaml import load as yaml_load |
24 | 21 | ||
25 | # PATH RELATIVITY ############################################################# | 22 | # PATH RELATIVITY ############################################################# |
26 | 23 | ||
@@ -162,6 +159,13 @@ def render_view(view, context=None): | @@ -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 | def datetime_from_list(time_list): | 169 | def datetime_from_list(time_list): |
166 | """ | 170 | """ |
167 | Datetimes in retrieved CDFs are stored as lists of numbers, | 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,9 +280,26 @@ def retrieve_amda_netcdf(orbiter, what, started_at, stopped_at): | ||
276 | # The order matters. If you change this you also need to change the | 280 | # The order matters. If you change this you also need to change the |
277 | # innermost loop of `get_data_for_target`. | 281 | # innermost loop of `get_data_for_target`. |
278 | # The javascript knows the targets' properties under these names. | 282 | # The javascript knows the targets' properties under these names. |
283 | +# DEPRECATED, use PARAMETERS instead | ||
279 | PROPERTIES = ('time', 'vrad', 'vtan', 'vlen', 'magn', 'temp', 'pdyn', 'dens', | 284 | PROPERTIES = ('time', 'vrad', 'vtan', 'vlen', 'magn', 'temp', 'pdyn', 'dens', |
280 | 'angl', 'xhci', 'yhci') | 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 | def get_data_for_target(target_config, started_at, stopped_at): | 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,12 +530,11 @@ def download_target_csv(target, started_at, stopped_at): | ||
509 | @app.route("/<targets>_<started_at>_<stopped_at>.tar.gz") | 530 | @app.route("/<targets>_<started_at>_<stopped_at>.tar.gz") |
510 | def download_targets_tarball(targets, started_at, stopped_at): | 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 | targets: string list of targets' slugs, separated by `-`. | 537 | targets: string list of targets' slugs, separated by `-`. |
517 | - This will fail hard if targets' slugs start having `-` in them. | ||
518 | """ | 538 | """ |
519 | separator = '-' | 539 | separator = '-' |
520 | targets = targets.split(separator) | 540 | targets = targets.split(separator) |
@@ -566,6 +586,93 @@ def download_targets_tarball(targets, started_at, stopped_at): | @@ -566,6 +586,93 @@ def download_targets_tarball(targets, started_at, stopped_at): | ||
566 | return send_from_directory(get_path("../cache/"), gzip_filename) | 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 | # API ######################################################################### | 676 | # API ######################################################################### |
570 | 677 | ||
571 | @app.route("/cache/clear") | 678 | @app.route("/cache/clear") |