Commit d1c44c51ee5a6d366acecb225cb0d8752aeeace8
1 parent
952e3d8f
Exists in
master
and in
2 other branches
Enable Earth
Enable more precise configuration of each models' parameters
Showing
7 changed files
with
138 additions
and
59 deletions
Show diff stats
CHANGELOG.md
... | ... | @@ -5,13 +5,22 @@ |
5 | 5 | - [ ] Add a README to the download tarball |
6 | 6 | - [ ] Set the log level to _error_ in production (see `web/run.py`) |
7 | 7 | - [ ] CRON statements to call the cache cleanup and warmup |
8 | -- [ ] Cache warmup (generate for today's default interval) `/cache/warmup` | |
9 | 8 | - [ ] Give the future data another color |
10 | 9 | |
11 | 10 | An heliospheric propagation 1D MHD model for solar wind prediction at planets, probes and comets. |
12 | 11 | |
13 | 12 | |
14 | 13 | |
14 | +## 1.0.0-rc5 | |
15 | + | |
16 | +- [ ] Rework the images of Rosetta and Juno | |
17 | +- [ ] Cache warmup (generate for today's default interval) `/cache/warmup` | |
18 | +- [ ] Improve the generated CDF for SAMP | |
19 | +- [ ] Enable p67 | |
20 | +- [x] Enable Earth | |
21 | +- [x] Enable more precise configuration of each models' parameters | |
22 | + | |
23 | + | |
15 | 24 | ## 1.0.0-rc4 |
16 | 25 | |
17 | 26 | - [x] Sort times series by closeness to the sun | ... | ... |
README.md
... | ... | @@ -24,6 +24,7 @@ It also gathers NetCDF data from AMDA, and serves it as CSV to the plotter. |
24 | 24 | - `config.yml` : the main configuration file. |
25 | 25 | - `web/run.py` : the front controller, holding most of the code. |
26 | 26 | - `web/view/home.html.jinja2` : the HTML template. |
27 | +- `web/static/js/swapp.ls` : most of the javascript client-side. | |
27 | 28 | |
28 | 29 | |
29 | 30 | ## Install | ... | ... |
config.yml
... | ... | @@ -98,15 +98,17 @@ targets: |
98 | 98 | - type: 'planet' |
99 | 99 | slug: 'earth' |
100 | 100 | name: 'Earth' |
101 | - title: 'Earth (coming soon)' | |
101 | + title: 'Earth' | |
102 | 102 | orbit: |
103 | - models: | |
104 | - - slug: 'earth_orb_all' | |
105 | - semimajor: 0 | |
106 | - semiminor: 0 | |
103 | + models: [] # Earth has no orbit models, we hard-coded it to (1, 0) | |
107 | 104 | models: |
108 | - - slug: 'tao_earth_sw' | |
109 | - locked: true | |
105 | + - slug: 'omni_hour_all' | |
106 | + - slug: 'ace_swepam_real' | |
107 | + parameters: | |
108 | + dens: 'Dens' | |
109 | + vtot: 'Vel' | |
110 | + temp: 'Temp' | |
111 | + locked: false | |
110 | 112 | default: true |
111 | 113 | - type: 'planet' |
112 | 114 | slug: 'mars' |
... | ... | @@ -159,6 +161,8 @@ targets: |
159 | 161 | stopped_at: '2014-08-02T00:00:00' |
160 | 162 | - slug: 'p67_orb_all' |
161 | 163 | started_at: '2014-08-02T00:00:00' |
164 | + parameters: | |
165 | + hee: 'XYZ_HEE' | |
162 | 166 | models: |
163 | 167 | - slug: 'tao_ros_sw' |
164 | 168 | locked: false |
... | ... | @@ -184,6 +188,8 @@ targets: |
184 | 188 | orbit: |
185 | 189 | models: |
186 | 190 | - slug: 'p67_orb_all' |
191 | + parameters: | |
192 | + hee: 'XYZ_HEE' | |
187 | 193 | locked: true |
188 | 194 | default: false |
189 | 195 | ... | ... |
web/run.py
... | ... | @@ -74,7 +74,7 @@ CACHE_DIR = get_path('../cache') |
74 | 74 | # innermost loop of `get_data_for_target`. |
75 | 75 | # The javascript knows the targets' properties under these names. |
76 | 76 | PROPERTIES = ('time', 'vrad', 'vtan', 'vtot', 'btan', 'temp', 'pdyn', 'dens', |
77 | - 'angl', 'xhee', 'yhee') | |
77 | + 'atse', 'xhee', 'yhee') | |
78 | 78 | |
79 | 79 | # The parameters that the users can handle. |
80 | 80 | # The slug MUST be one of the properties above. |
... | ... | @@ -119,8 +119,8 @@ PARAMETERS = { |
119 | 119 | 'active': False, |
120 | 120 | 'position': 50, |
121 | 121 | }, |
122 | - 'angl': { | |
123 | - 'slug': 'angl', | |
122 | + 'atse': { | |
123 | + 'slug': 'atse', | |
124 | 124 | 'name': 'Angle T-S-E', |
125 | 125 | 'title': 'Angle Target-Sun-Earth.', |
126 | 126 | 'units': 'deg', |
... | ... | @@ -426,9 +426,10 @@ def get_data_for_target(target_config, started_at, stopped_at): |
426 | 426 | % (target_config['slug'], str(e))) |
427 | 427 | try: |
428 | 428 | orbits = target_config['orbit']['models'] |
429 | - except Exception as e: | |
430 | - abort(500, "Invalid orbit configuration for '%s' : %s" | |
431 | - % (target_config['slug'], str(e))) | |
429 | + except KeyError as e: | |
430 | + orbits = [] | |
431 | + # abort(500, "Invalid orbit configuration for '%s' : %s" | |
432 | + # % (target_config['slug'], str(e))) | |
432 | 433 | |
433 | 434 | def _sta_sto(_cnf, _sta, _sto): |
434 | 435 | if 'started_at' in _cnf: |
... | ... | @@ -443,12 +444,36 @@ def get_data_for_target(target_config, started_at, stopped_at): |
443 | 444 | _s1 = _sto |
444 | 445 | return _s0, _s1 |
445 | 446 | |
447 | + def _read_var(_nc, _keys, _key, mandatory=False): | |
448 | + try: | |
449 | + return _nc.variables[_keys[_key]] | |
450 | + except KeyError: | |
451 | + pass | |
452 | + if mandatory: | |
453 | + raise Exception("No variable '%s' found in NetCDF." % _keys[_key]) | |
454 | + return [None] * len(_nc.variables['Time']) # slow -- use numpy! | |
455 | + | |
456 | + # Override them using the configuration, maybe also put these in config ? | |
457 | + default_nc_keys = { | |
458 | + 'hee': 'HEE', | |
459 | + 'vtot': 'V', | |
460 | + 'magn': 'B', | |
461 | + 'temp': 'T', | |
462 | + 'dens': 'N', | |
463 | + 'pdyn': 'P_dyn', | |
464 | + 'atse': 'Delta_angle', | |
465 | + } | |
466 | + | |
446 | 467 | precision = "%Y-%m-%dT%H" # model and orbits times are only equal-ish |
447 | 468 | orbit_data = {} # keys are datetime as str, values arrays of XY |
448 | 469 | |
449 | 470 | for orbit in orbits: |
450 | 471 | s0, s1 = _sta_sto(orbit, started_at, stopped_at) |
451 | 472 | |
473 | + nc_keys = default_nc_keys.copy() | |
474 | + if 'parameters' in orbit: | |
475 | + nc_keys.update(orbit['parameters']) | |
476 | + | |
452 | 477 | orbit_files = retrieve_amda_netcdf( |
453 | 478 | target_config['slug'], orbit['slug'], s0, s1 |
454 | 479 | ) |
... | ... | @@ -457,19 +482,16 @@ def get_data_for_target(target_config, started_at, stopped_at): |
457 | 482 | (target_config['name'], orbit_file)) |
458 | 483 | cdf_handle = Dataset(orbit_file, "r", format="NETCDF4") |
459 | 484 | times = cdf_handle.variables['Time'] # YYYY DOY HH MM SS .ms |
460 | - try: | |
461 | - data_hee = cdf_handle.variables['HEE'] | |
462 | - except KeyError: | |
463 | - data_hee = cdf_handle.variables['XYZ_HEE'] # p67 uses this | |
485 | + data_hee = _read_var(cdf_handle, nc_keys, 'hee', mandatory=True) | |
464 | 486 | |
465 | 487 | log.debug("%s: aggregating data from '%s'..." % |
466 | 488 | (target_config['name'], orbit_file)) |
467 | - for time, datum_hee in zip(times, data_hee): | |
489 | + for ltime, datum_hee in zip(times, data_hee): | |
468 | 490 | try: |
469 | - dtime = datetime_from_list(time) | |
470 | - except Exception as e: | |
471 | - log.error("Failed to parse time from %s." % time) | |
472 | - raise e | |
491 | + dtime = datetime_from_list(ltime) | |
492 | + except Exception: | |
493 | + log.error("Failed to parse time from %s." % ltime) | |
494 | + raise | |
473 | 495 | if s0 <= dtime <= s1: |
474 | 496 | dkey = round_time(dtime, 60*60).strftime(precision) |
475 | 497 | orbit_data[dkey] = datum_hee |
... | ... | @@ -481,40 +503,66 @@ def get_data_for_target(target_config, started_at, stopped_at): |
481 | 503 | model_files = retrieve_amda_netcdf( |
482 | 504 | target_config['slug'], model['slug'], s0, s1 |
483 | 505 | ) |
506 | + nc_keys = default_nc_keys.copy() | |
507 | + if 'parameters' in model: | |
508 | + nc_keys.update(model['parameters']) | |
509 | + | |
484 | 510 | for model_file in model_files: |
485 | - # Time, StartTime, StopTime, V, B, N, T, Delta_angle, P_dyn | |
486 | 511 | log.debug("%s: opening model NETCDF4 '%s'..." % |
487 | 512 | (target_config['name'], model_file)) |
488 | 513 | cdf_handle = Dataset(model_file, "r", format="NETCDF4") |
514 | + # log.debug(cdf_handle.variables.keys()) | |
489 | 515 | times = cdf_handle.variables['Time'] # YYYY DOY HH MM SS .ms |
490 | - data_v = cdf_handle.variables['V'] | |
491 | - data_b = cdf_handle.variables['B'] | |
492 | - data_t = cdf_handle.variables['T'] | |
493 | - data_n = cdf_handle.variables['N'] | |
494 | - data_p = cdf_handle.variables['P_dyn'] | |
495 | - data_d = cdf_handle.variables['Delta_angle'] | |
516 | + data_v = _read_var(cdf_handle, nc_keys, 'vtot') | |
517 | + data_b = _read_var(cdf_handle, nc_keys, 'magn') | |
518 | + data_t = _read_var(cdf_handle, nc_keys, 'temp') | |
519 | + data_n = _read_var(cdf_handle, nc_keys, 'dens') | |
520 | + data_p = _read_var(cdf_handle, nc_keys, 'pdyn') | |
521 | + data_a = _read_var(cdf_handle, nc_keys, 'atse') | |
522 | + | |
523 | + # Usually: | |
524 | + # Time, StartTime, StopTime, V, B, N, T, Delta_angle, P_dyn | |
525 | + # Earth: | |
526 | + # Time, BartelsNumber, ImfID, SwID, ImfPoints, | |
527 | + # SwPoints, B_M_av, B_Vec_av, B_Theta_av, | |
528 | + # B_Phi_av, B, T, N, V, Vlat, Vlon, | |
529 | + # Alpha, RamP, E, Beta, Ma, Kp, R, DST, | |
530 | + # AE, Flux, Flag, F10_Index, StartTime, StopTime | |
531 | + | |
496 | 532 | log.debug("%s: aggregating data from '%s'..." % |
497 | 533 | (target_config['name'], model_file)) |
498 | - for time, datum_v, datum_b, datum_t, datum_n, datum_p, datum_d \ | |
499 | - in zip(times, data_v, data_b, data_t, data_n, data_p, data_d): | |
500 | - vrad = datum_v[0] | |
501 | - vtan = datum_v[1] | |
534 | + for ltime, datum_v, datum_b, datum_t, datum_n, datum_p, datum_a \ | |
535 | + in zip(times, data_v, data_b, data_t, data_n, data_p, data_a): | |
536 | + | |
502 | 537 | try: |
503 | - dtime = datetime_from_list(time) | |
504 | - except Exception as e: | |
505 | - log.error("Failed to parse time from %s." % time) | |
506 | - raise e | |
538 | + dtime = datetime_from_list(ltime) | |
539 | + except Exception: | |
540 | + log.error("Failed to parse time from %s." % ltime) | |
541 | + raise | |
507 | 542 | if s0 <= dtime <= s1: |
508 | 543 | dkey = round_time(dtime, 60*60).strftime(precision) |
544 | + | |
545 | + if hasattr(datum_v, '__len__'): | |
546 | + vrad = datum_v[0] | |
547 | + vtan = datum_v[1] | |
548 | + vtot = sqrt(vrad * vrad + vtan * vtan) | |
549 | + else: # eg: Earth | |
550 | + vrad = None | |
551 | + vtan = None | |
552 | + vtot = datum_v | |
553 | + | |
509 | 554 | x_hee = None |
510 | 555 | y_hee = None |
556 | + if target_config['slug'] == 'earth': | |
557 | + x_hee = 1 | |
558 | + y_hee = 0 | |
511 | 559 | if dkey in orbit_data: |
512 | 560 | x_hee = orbit_data[dkey][0] |
513 | 561 | y_hee = orbit_data[dkey][1] |
514 | 562 | all_data[dkey] = ( |
515 | 563 | dtime.strftime("%Y-%m-%dT%H:%M:%S+00:00"), |
516 | - vrad, vtan, sqrt(vrad * vrad + vtan * vtan), | |
517 | - datum_b, datum_t, datum_n, datum_p, datum_d, | |
564 | + vrad, vtan, vtot, | |
565 | + datum_b, datum_t, datum_n, datum_p, datum_a, | |
518 | 566 | x_hee, y_hee |
519 | 567 | ) |
520 | 568 | cdf_handle.close() |
... | ... | @@ -565,7 +613,12 @@ def generate_csv_file_if_needed(target_slug, started_at, stopped_at): |
565 | 613 | stopped_at=stopped_at)) |
566 | 614 | log.info("Generation of '%s' done." % filename) |
567 | 615 | except Exception as e: |
616 | + from sys import exc_info | |
617 | + from traceback import extract_tb | |
618 | + exc_type, exc_value, exc_traceback = exc_info() | |
568 | 619 | log.error(e) |
620 | + for trace in extract_tb(exc_traceback): | |
621 | + log.error(trace) | |
569 | 622 | if isfile(local_csv_file): |
570 | 623 | log.warn("Removing failed CSV '%s'..." % local_csv_file) |
571 | 624 | removefile(local_csv_file) |
... | ... | @@ -875,9 +928,9 @@ def download_targets_netcdf(targets, params, started_at, stopped_at): |
875 | 928 | nc_y[:] = values_y |
876 | 929 | log.debug("Writing NetCDF '%s'..." % nc_filename) |
877 | 930 | |
878 | - except Exception as e: | |
931 | + except Exception: | |
879 | 932 | log.error("Failed to generate NetCDF '%s'." % nc_filename) |
880 | - raise e | |
933 | + raise | |
881 | 934 | finally: |
882 | 935 | nc_handle.close() |
883 | 936 | ... | ... |
web/static/js/swapp.js
... | ... | @@ -199,12 +199,15 @@ |
199 | 199 | var dtime; |
200 | 200 | dtime = timeFormat(d['time']); |
201 | 201 | configuration['parameters'].forEach(function(parameter){ |
202 | - var id; | |
202 | + var id, val; | |
203 | 203 | id = parameter['id']; |
204 | - return data[id].push({ | |
205 | - x: dtime, | |
206 | - y: parseFloat(d[id]) | |
207 | - }); | |
204 | + val = parseFloat(d[id]); | |
205 | + if (!isNaN(val)) { | |
206 | + return data[id].push({ | |
207 | + x: dtime, | |
208 | + y: val | |
209 | + }); | |
210 | + } | |
208 | 211 | }); |
209 | 212 | if (d['xhee'] && d['yhee']) { |
210 | 213 | return data['hee'].push({ |
... | ... | @@ -299,10 +302,13 @@ |
299 | 302 | if (!(id in data)) { |
300 | 303 | console.error("No data for id '" + id + "'.", data); |
301 | 304 | } |
302 | - return this$.time_series.push(new TimeSeries(id, title, target, data[id], this$.parameters[id].active, container, { | |
303 | - 'started_at': this$.started_at, | |
304 | - 'stopped_at': this$.stopped_at | |
305 | - })); | |
305 | + console.log(target['name'], id, data[id]); | |
306 | + if (data[id].length) { | |
307 | + return this$.time_series.push(new TimeSeries(id, title, target, data[id], this$.parameters[id].active, container, { | |
308 | + 'started_at': this$.started_at, | |
309 | + 'stopped_at': this$.stopped_at | |
310 | + })); | |
311 | + } | |
306 | 312 | }); |
307 | 313 | this.time_series.forEach(function(ts){ |
308 | 314 | ts.options['onMouseOver'] = function(){ | ... | ... |
web/static/js/swapp.ls
... | ... | @@ -169,7 +169,9 @@ https://gitlab.irap.omp.eu/CDPP/SPACEWEATHERONLINE |
169 | 169 | dtime = timeFormat(d['time']) |
170 | 170 | configuration['parameters'].forEach((parameter) -> |
171 | 171 | id = parameter['id'] |
172 | - data[id].push({x: dtime, y: parseFloat(d[id])}) | |
172 | + val = parseFloat(d[id]) | |
173 | + if not isNaN(val) | |
174 | + data[id].push({x: dtime, y: val}) | |
173 | 175 | ) |
174 | 176 | if d['xhee'] and d['yhee'] |
175 | 177 | data['hee'].push({ |
... | ... | @@ -250,12 +252,14 @@ https://gitlab.irap.omp.eu/CDPP/SPACEWEATHERONLINE |
250 | 252 | container = @configuration['time_series_container'] |
251 | 253 | id = parameter['id'] ; title = parameter['title'] |
252 | 254 | if id not of data then console.error("No data for id '#{id}'.", data) |
253 | - @time_series.push(new TimeSeries( | |
254 | - id, title, target, data[id], @parameters[id].active, container, { | |
255 | - 'started_at': @started_at, | |
256 | - 'stopped_at': @stopped_at, | |
257 | - } | |
258 | - )) | |
255 | + console.log(target['name'], id, data[id]) | |
256 | + if data[id].length | |
257 | + @time_series.push(new TimeSeries( | |
258 | + id, title, target, data[id], @parameters[id].active, container, { | |
259 | + 'started_at': @started_at, | |
260 | + 'stopped_at': @stopped_at, | |
261 | + } | |
262 | + )) | |
259 | 263 | ) |
260 | 264 | @time_series.forEach((ts) ~> # returning true may be faster |
261 | 265 | ts.options['onMouseOver'] = ~> | ... | ... |
web/view/home.html.jinja2
... | ... | @@ -11,7 +11,7 @@ |
11 | 11 | <div class="target {{ target.slug }} {{ 'locked' if target.locked else 'active' }}" |
12 | 12 | data-target-slug="{{ target.slug }}"> |
13 | 13 | <img width="64px" height="64px" |
14 | - src="{{ static('img/target/'~target.slug~'_128.png') }}" | |
14 | + src="{{ static('img/target/'~target.slug~'_256.png') }}" | |
15 | 15 | title="{{ target.title }}" |
16 | 16 | alt="{{ target.name }}"> |
17 | 17 | <img width="64px" height="64px" | ... | ... |