Commit bc18b96cedce9e4dcbc8ed20a08c7888d6da2b74

Authored by Goutte
1 parent 3f0d5ea2

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