Commit bde97e4d3c1fc75b05aa98d61d6e87e4459504ce
1 parent
17c52617
Exists in
master
and in
2 other branches
Add more changes suggested by Vincent. Still hunting why the recent data is not …
…shown, but from as far as I can see, it's not in the CDF files provided by AMDA.
Showing
4 changed files
with
84 additions
and
61 deletions
Show diff stats
CHANGELOG.md
1 | ## Misc | 1 | ## Misc |
2 | 2 | ||
3 | -- [ ] Support multiple models for each target | ||
4 | - [ ] Optimize CSV generation (numpy vectorization ?) | 3 | - [ ] Optimize CSV generation (numpy vectorization ?) |
5 | - [ ] Credit the author of the pixel art planets | 4 | - [ ] Credit the author of the pixel art planets |
6 | - [ ] Add a README to the download tarball | 5 | - [ ] Add a README to the download tarball |
@@ -23,9 +22,13 @@ | @@ -23,9 +22,13 @@ | ||
23 | - [ ] CRON statement to call it | 22 | - [ ] CRON statement to call it |
24 | - [ ] Cache warmup | 23 | - [ ] Cache warmup |
25 | - [x] Download raw data (tarball of CSV) for current time interval and targets | 24 | - [x] Download raw data (tarball of CSV) for current time interval and targets |
26 | -- [ ] Download raw data as NetCDF for current everything, via SAMP | 25 | +- [x] Download raw data as NetCDF for current everything, via SAMP |
27 | - [x] Add a warning for users with javascript disabled | 26 | - [x] Add a warning for users with javascript disabled |
28 | - [ ] Set the log level to _error_ in production (see `web/run.py`) | 27 | - [ ] Set the log level to _error_ in production (see `web/run.py`) |
28 | +- [ ] Make the targets dynamic in the orbit plot, allowing zoom | ||
29 | +- [ ] Add units to the generated CSV files | ||
30 | +- [x] Support multiple models for each target | ||
31 | + | ||
29 | 32 | ||
30 | 33 | ||
31 | ## 0.0.0 | 34 | ## 0.0.0 |
web/run.py
1 | +# coding=utf-8 | ||
2 | + | ||
1 | import StringIO | 3 | import StringIO |
2 | import datetime | 4 | import datetime |
3 | -import time | ||
4 | import gzip | 5 | import gzip |
5 | import json | 6 | import json |
6 | import logging | 7 | import logging |
7 | import random | 8 | import random |
8 | import tarfile | 9 | import tarfile |
10 | +import time | ||
9 | import urllib | 11 | import urllib |
10 | from csv import writer as csv_writer | 12 | from csv import writer as csv_writer |
11 | from math import sqrt | 13 | from math import sqrt |
@@ -15,7 +17,7 @@ from os.path import isfile, join, abspath, dirname | @@ -15,7 +17,7 @@ from os.path import isfile, join, abspath, dirname | ||
15 | from flask import Flask | 17 | from flask import Flask |
16 | from flask import request | 18 | from flask import request |
17 | from flask import url_for, send_from_directory, abort as abort_flask | 19 | from flask import url_for, send_from_directory, abort as abort_flask |
18 | -from jinja2 import Environment, FileSystemLoader | 20 | +from jinja2 import Environment, FileSystemLoader, Markup |
19 | from netCDF4 import Dataset, date2num | 21 | from netCDF4 import Dataset, date2num |
20 | from yaml import load as yaml_load | 22 | from yaml import load as yaml_load |
21 | 23 | ||
@@ -101,6 +103,33 @@ def markdown_filter(value, nl2br=False, p=True): | @@ -101,6 +103,33 @@ def markdown_filter(value, nl2br=False, p=True): | ||
101 | return markdowned | 103 | return markdowned |
102 | 104 | ||
103 | 105 | ||
106 | +_js_escapes = { | ||
107 | + '\\': '\\u005C', | ||
108 | + '\'': '\\u0027', | ||
109 | + '"': '\\u0022', | ||
110 | + '>': '\\u003E', | ||
111 | + '<': '\\u003C', | ||
112 | + '&': '\\u0026', | ||
113 | + '=': '\\u003D', | ||
114 | + '-': '\\u002D', | ||
115 | + ';': '\\u003B', | ||
116 | + u'\u2028': '\\u2028', | ||
117 | + u'\u2029': '\\u2029' | ||
118 | +} | ||
119 | +# Escape every ASCII character with a value less than 32. | ||
120 | +_js_escapes.update(('%c' % z, '\\u%04X' % z) for z in xrange(32)) | ||
121 | + | ||
122 | + | ||
123 | +def escapejs_filter(value): | ||
124 | + escaped = [] | ||
125 | + for letter in value: | ||
126 | + if letter in _js_escapes: | ||
127 | + escaped.append(_js_escapes[letter]) | ||
128 | + else: | ||
129 | + escaped.append(letter) | ||
130 | + | ||
131 | + return Markup("".join(escaped)) | ||
132 | + | ||
104 | tpl_engine = Environment(loader=FileSystemLoader([get_path('view')]), | 133 | tpl_engine = Environment(loader=FileSystemLoader([get_path('view')]), |
105 | trim_blocks=True, | 134 | trim_blocks=True, |
106 | lstrip_blocks=True) | 135 | lstrip_blocks=True) |
@@ -113,6 +142,7 @@ tpl_engine.globals.update( | @@ -113,6 +142,7 @@ tpl_engine.globals.update( | ||
113 | tpl_engine.filters['markdown'] = markdown_filter | 142 | tpl_engine.filters['markdown'] = markdown_filter |
114 | tpl_engine.filters['md'] = markdown_filter | 143 | tpl_engine.filters['md'] = markdown_filter |
115 | tpl_engine.filters['shuffle'] = shuffle_filter | 144 | tpl_engine.filters['shuffle'] = shuffle_filter |
145 | +tpl_engine.filters['escapejs'] = escapejs_filter | ||
116 | 146 | ||
117 | tpl_global_vars = { | 147 | tpl_global_vars = { |
118 | 'request': request, | 148 | 'request': request, |
@@ -278,11 +308,14 @@ def retrieve_amda_netcdf(orbiter, what, started_at, stopped_at): | @@ -278,11 +308,14 @@ def retrieve_amda_netcdf(orbiter, what, started_at, stopped_at): | ||
278 | return local_netc_files | 308 | return local_netc_files |
279 | 309 | ||
280 | 310 | ||
311 | +# These two configs are not in the YAML config because adding a new parameter | ||
312 | +# will not work as-is, you'll have to edit the netcdf-related code | ||
313 | + | ||
281 | # The available parameters in the generated CSV files. | 314 | # The available parameters in the generated CSV files. |
282 | # The order matters. If you change this you also need to change the | 315 | # The order matters. If you change this you also need to change the |
283 | # innermost loop of `get_data_for_target`. | 316 | # innermost loop of `get_data_for_target`. |
284 | # The javascript knows the targets' properties under these names. | 317 | # The javascript knows the targets' properties under these names. |
285 | -PROPERTIES = ('time', 'vrad', 'vtan', 'vlen', 'magn', 'temp', 'pdyn', 'dens', | 318 | +PROPERTIES = ('time', 'vrad', 'vtan', 'vtot', 'btan', 'temp', 'pdyn', 'dens', |
286 | 'angl', 'xhee', 'yhee') | 319 | 'angl', 'xhee', 'yhee') |
287 | 320 | ||
288 | # The parameters that the users can handle. | 321 | # The parameters that the users can handle. |
@@ -293,36 +326,48 @@ PARAMETERS = { | @@ -293,36 +326,48 @@ PARAMETERS = { | ||
293 | 'name': 'Dyn. Pressure', | 326 | 'name': 'Dyn. Pressure', |
294 | 'title': 'The dynamic pressure.', | 327 | 'title': 'The dynamic pressure.', |
295 | 'units': 'nPa', | 328 | 'units': 'nPa', |
329 | + 'active': True, | ||
330 | + 'position': 10, | ||
296 | }, | 331 | }, |
297 | - 'vlen': { | ||
298 | - 'slug': 'vlen', | 332 | + 'vtot': { |
333 | + 'slug': 'vtot', | ||
299 | 'name': 'Velocity', | 334 | 'name': 'Velocity', |
300 | 'title': 'The velocity of the particles.', | 335 | 'title': 'The velocity of the particles.', |
301 | 'units': 'km/s', | 336 | 'units': 'km/s', |
337 | + 'active': False, | ||
338 | + 'position': 20, | ||
302 | }, | 339 | }, |
303 | - 'magn': { | ||
304 | - 'slug': 'magn', | 340 | + 'btan': { |
341 | + 'slug': 'btan', | ||
305 | 'name': 'B Tangential', | 342 | 'name': 'B Tangential', |
306 | 'title': 'B Tangential.', | 343 | 'title': 'B Tangential.', |
307 | 'units': 'nT', | 344 | 'units': 'nT', |
345 | + 'active': False, | ||
346 | + 'position': 30, | ||
308 | }, | 347 | }, |
309 | 'temp': { | 348 | 'temp': { |
310 | 'slug': 'temp', | 349 | 'slug': 'temp', |
311 | 'name': 'Temperature', | 350 | 'name': 'Temperature', |
312 | 'title': 'The absolute temperature.', | 351 | 'title': 'The absolute temperature.', |
313 | 'units': 'K', | 352 | 'units': 'K', |
353 | + 'active': False, | ||
354 | + 'position': 40, | ||
314 | }, | 355 | }, |
315 | 'dens': { | 356 | 'dens': { |
316 | 'slug': 'dens', | 357 | 'slug': 'dens', |
317 | 'name': 'Density', | 358 | 'name': 'Density', |
318 | 'title': 'The density N.', | 359 | 'title': 'The density N.', |
319 | - 'units': 'cm^-3', | 360 | + 'units': u'cm⁻³', |
361 | + 'active': False, | ||
362 | + 'position': 50, | ||
320 | }, | 363 | }, |
321 | 'angl': { | 364 | 'angl': { |
322 | 'slug': 'angl', | 365 | 'slug': 'angl', |
323 | 'name': 'Angle T-S-E', | 366 | 'name': 'Angle T-S-E', |
324 | 'title': 'Angle Target-Sun-Earth.', | 367 | 'title': 'Angle Target-Sun-Earth.', |
325 | 'units': 'deg', | 368 | 'units': 'deg', |
369 | + 'active': False, | ||
370 | + 'position': 60, | ||
326 | }, | 371 | }, |
327 | } | 372 | } |
328 | 373 | ||
@@ -528,7 +573,7 @@ tpl_global_vars['visits'] = get_hit_counter() | @@ -528,7 +573,7 @@ tpl_global_vars['visits'] = get_hit_counter() | ||
528 | # ROUTING ##################################################################### | 573 | # ROUTING ##################################################################### |
529 | 574 | ||
530 | @app.route('/favicon.ico') | 575 | @app.route('/favicon.ico') |
531 | -def favicon(): | 576 | +def favicon(): # we want it served from the root, not from static/ |
532 | return send_from_directory( | 577 | return send_from_directory( |
533 | join(app.root_path, 'static', 'img'), | 578 | join(app.root_path, 'static', 'img'), |
534 | 'favicon.ico', mimetype='image/vnd.microsoft.icon' | 579 | 'favicon.ico', mimetype='image/vnd.microsoft.icon' |
@@ -540,8 +585,11 @@ def favicon(): | @@ -540,8 +585,11 @@ def favicon(): | ||
540 | @app.route("/index.html") | 585 | @app.route("/index.html") |
541 | def home(): | 586 | def home(): |
542 | increment_hit_counter() | 587 | increment_hit_counter() |
588 | + parameters = PARAMETERS.values() | ||
589 | + parameters.sort(key=lambda x: x['position']) | ||
543 | return render_view('home.html.jinja2', { | 590 | return render_view('home.html.jinja2', { |
544 | 'targets': config['targets'], | 591 | 'targets': config['targets'], |
592 | + 'parameters': parameters, | ||
545 | 'planets': [s for s in config['targets'] if s['type'] == 'planet'], | 593 | 'planets': [s for s in config['targets'] if s['type'] == 'planet'], |
546 | 'probes': [s for s in config['targets'] if s['type'] == 'probe'], | 594 | 'probes': [s for s in config['targets'] if s['type'] == 'probe'], |
547 | 'comets': [s for s in config['targets'] if s['type'] == 'comet'], | 595 | 'comets': [s for s in config['targets'] if s['type'] == 'comet'], |
@@ -751,7 +799,7 @@ def download_targets_netcdf(targets, params, started_at, stopped_at): | @@ -751,7 +799,7 @@ def download_targets_netcdf(targets, params, started_at, stopped_at): | ||
751 | if not isfile(nc_path): | 799 | if not isfile(nc_path): |
752 | abort(500, "No NetCDF to serve. Looked at '%s'." % nc_path) | 800 | abort(500, "No NetCDF to serve. Looked at '%s'." % nc_path) |
753 | 801 | ||
754 | - return send_from_directory(get_path("../cache/"), nc_filename) | 802 | + return send_from_directory(get_path("../cache"), nc_filename) |
755 | 803 | ||
756 | 804 | ||
757 | # API ######################################################################### | 805 | # API ######################################################################### |
@@ -773,12 +821,18 @@ def cache_warmup(): | @@ -773,12 +821,18 @@ def cache_warmup(): | ||
773 | Warms up the cache for the current day. | 821 | Warms up the cache for the current day. |
774 | Linked to SpaceWeather#edit in swapp.ls | 822 | Linked to SpaceWeather#edit in swapp.ls |
775 | """ | 823 | """ |
776 | - from dateutil.relativedelta import relativedelta | ||
777 | # relativedelta(years=3) | 824 | # relativedelta(years=3) |
778 | # startted_at = datetime.datetime.now() - relativedelta(years=3) | 825 | # startted_at = datetime.datetime.now() - relativedelta(years=3) |
779 | return "To Do" | 826 | return "To Do" |
780 | 827 | ||
781 | 828 | ||
829 | +@app.route("/run.log") | ||
830 | +def run_log(): | ||
831 | + with open(get_path('run.log'), 'r') as f: | ||
832 | + contents = f.read() | ||
833 | + return contents | ||
834 | + | ||
835 | + | ||
782 | # DEV TOOLS ################################################################### | 836 | # DEV TOOLS ################################################################### |
783 | 837 | ||
784 | # @app.route("/inspect") | 838 | # @app.route("/inspect") |
web/static/js/swapp.ls
@@ -337,7 +337,7 @@ export class TimeSeries | @@ -337,7 +337,7 @@ export class TimeSeries | ||
337 | # Data in y-axis | 337 | # Data in y-axis |
338 | 338 | ||
339 | (@parameter, @title, @target, data, @visible, @container, @options = {}) -> | 339 | (@parameter, @title, @target, data, @visible, @container, @options = {}) -> |
340 | - # parameter : slug of the parameter to observe, like magn or pdyn | 340 | + # parameter : slug of the parameter to observe, like btan or pdyn |
341 | # title : string, more descriptive, shown on the left of the Y axis | 341 | # title : string, more descriptive, shown on the left of the Y axis |
342 | # target : target object, like described in configuration | 342 | # target : target object, like described in configuration |
343 | # data : list of {x: <datetime>, y: <float>} | 343 | # data : list of {x: <datetime>, y: <float>} |
web/view/home.html.jinja2
@@ -99,13 +99,9 @@ | @@ -99,13 +99,9 @@ | ||
99 | <span class="mdl-layout-title">{{ icon('flask') }} Parameters</span> | 99 | <span class="mdl-layout-title">{{ icon('flask') }} Parameters</span> |
100 | 100 | ||
101 | <nav id="parameters" class="mdl-navigation"> | 101 | <nav id="parameters" class="mdl-navigation"> |
102 | - {# todo: generate this from config #} | ||
103 | - <a class="mdl-navigation__link parameter active" data-ts-slug="pdyn" href="#">Dynamic Pressure</a> | ||
104 | - <a class="mdl-navigation__link parameter" data-ts-slug="magn" href="#">B Tangential</a> | ||
105 | - <a class="mdl-navigation__link parameter" data-ts-slug="vlen" href="#">Velocity</a> | ||
106 | - <a class="mdl-navigation__link parameter" data-ts-slug="temp" href="#">Temperature</a> | ||
107 | - <a class="mdl-navigation__link parameter" data-ts-slug="dens" href="#">Density</a> | ||
108 | - <a class="mdl-navigation__link parameter" data-ts-slug="angl" href="#">Angle Target-Sun-Earth</a> | 102 | + {% for p in parameters %} |
103 | + <a class="mdl-navigation__link parameter{{ ' active' if p.active }}" data-ts-slug="{{ p.slug }}" href="#">{{ p.name }}</a> | ||
104 | + {% endfor %} | ||
109 | </nav> | 105 | </nav> |
110 | 106 | ||
111 | <div class="mdl-layout-spacer"></div> | 107 | <div class="mdl-layout-spacer"></div> |
@@ -537,7 +533,7 @@ | @@ -537,7 +533,7 @@ | ||
537 | var configuration = { | 533 | var configuration = { |
538 | time_series_container: '#time_series', | 534 | time_series_container: '#time_series', |
539 | orbits_container: '#orbits', | 535 | orbits_container: '#orbits', |
540 | - api : { | 536 | + api: { |
541 | 'data_for_interval': "{{ request.url_root }}<target>_<started_at>_<stopped_at>.csv", | 537 | 'data_for_interval': "{{ request.url_root }}<target>_<started_at>_<stopped_at>.csv", |
542 | 'download': "{{ request.url_root }}<targets>_<started_at>_<stopped_at>.tar.gz", | 538 | 'download': "{{ request.url_root }}<targets>_<started_at>_<stopped_at>.tar.gz", |
543 | 'samp': "{{ request.url_root }}<targets>_<params>_<started_at>_<stopped_at>.nc" | 539 | 'samp': "{{ request.url_root }}<targets>_<params>_<started_at>_<stopped_at>.nc" |
@@ -545,7 +541,7 @@ var configuration = { | @@ -545,7 +541,7 @@ var configuration = { | ||
545 | sun: { | 541 | sun: { |
546 | img: '{{ static('img/sun_128.png') }}' | 542 | img: '{{ static('img/sun_128.png') }}' |
547 | }, | 543 | }, |
548 | - targets : { | 544 | + targets: { |
549 | {% for target in targets if not target.locked %} | 545 | {% for target in targets if not target.locked %} |
550 | {% if not loop.first %},{% endif %} | 546 | {% if not loop.first %},{% endif %} |
551 | '{{ target.slug }}': { | 547 | '{{ target.slug }}': { |
@@ -557,45 +553,15 @@ var configuration = { | @@ -557,45 +553,15 @@ var configuration = { | ||
557 | } | 553 | } |
558 | {% endfor %} | 554 | {% endfor %} |
559 | }, | 555 | }, |
560 | -{# todo @Nicolas Define -somehow- error margins of each parameter ? #} | ||
561 | -{# todo: generate this from config PARAMETERS #} | ||
562 | - parameters : [ | 556 | + parameters: [ |
557 | +{% for p in parameters %} | ||
563 | { | 558 | { |
564 | - id: 'pdyn', | ||
565 | - title: "Dyn. Pressure (nPa)", | ||
566 | - active: true, | ||
567 | - unit: "nPa" | ||
568 | - }, | ||
569 | - { | ||
570 | - id: 'magn', | ||
571 | - title: "B Tangential (nT)", | ||
572 | - active: false, | ||
573 | - unit: "nT" | ||
574 | - }, | ||
575 | - { | ||
576 | - id: 'vlen', | ||
577 | - title: "Velocity (km/s)", | ||
578 | - active: false, | ||
579 | - unit: "km/s" | ||
580 | - }, | ||
581 | - { | ||
582 | - id: 'temp', | ||
583 | - title: "Temperature (K)", | ||
584 | - active: false, | ||
585 | - unit: "K" | ||
586 | - }, | ||
587 | - { | ||
588 | - id: 'dens', | ||
589 | - title: "Density N (cm⁻³)", | ||
590 | - active: false, | ||
591 | - unit: "cm⁻³" | ||
592 | - }, | ||
593 | - { | ||
594 | - id: 'angl', | ||
595 | - title: "Angle T-S-E (deg)", | ||
596 | - active: false, | ||
597 | - unit: "deg" | ||
598 | - } | 559 | + id: "{{ p.slug | escapejs }}", |
560 | + title: "{{ p.name | escapejs }} ({{ p.units | escapejs }})", | ||
561 | + active: {{ 'true' if p.active else 'false' }}, | ||
562 | + unit: "{{ p.units | escapejs }}" | ||
563 | + }{{ ',' if not loop.last }} | ||
564 | +{% endfor %} | ||
599 | ] | 565 | ] |
600 | }; | 566 | }; |
601 | 567 |