1. /**
  2. * @module meteoJS/thermodynamicDiagram/diagramSounding
  3. */
  4. import addEventFunctions from '../Events.js';
  5. import Unique from '../base/Unique.js';
  6. import Collection from '../base/Collection.js';
  7. import {
  8. getNormalizedLineOptions,
  9. updateLineOptions
  10. } from '../thermodynamicDiagram/Functions.js';
  11. import DiagramParcel from './DiagramParcel.js';
  12. /**
  13. * Change visibility event. Only triggered, if the visibility of the sounding
  14. * changes, not if only a part's visibility (like hodograph) changes.
  15. *
  16. * @event module:meteoJS/thermodynamicDiagram/diagramSounding#change:visible
  17. */
  18. /**
  19. * Change options event.
  20. *
  21. * @event module:meteoJS/thermodynamicDiagram/diagramSounding#change:options
  22. */
  23. /**
  24. * Options for a line-segment of a sounding in the hodograph.
  25. *
  26. * @typedef {module:meteoJS/thermodynamicDiagram~lineOptions}
  27. * module:meteoJS/thermodynamicDiagram/diagramSounding~hodographSegmentOptions
  28. * @property {number|undefined}
  29. * [minPressure] - Minimum pressure level of the segment. Unit: hPa.
  30. * @property {number|undefined}
  31. * [maxPressure] - Maximum pressure level of the segment. Unit: hPa.
  32. */
  33. /**
  34. * Options for a sounding in the hodograph.
  35. *
  36. * @typedef {module:meteoJS/thermodynamicDiagram~lineOptions}
  37. * module:meteoJS/thermodynamicDiagram/diagramSounding~hodographOptions
  38. * @property {number|undefined}
  39. * [minPressure] - Minimum pressure level to plot in the hodograph. Unit: hPa.
  40. * @property {number|undefined}
  41. * [maxPressure] - Maximum pressure level to plot in the hodograph. Unit: hPa.
  42. * @property {module:meteoJS/thermodynamicDiagram/diagramSounding~hodographSegmentOptions[]}
  43. * [segments] - Array of segment definitions.
  44. */
  45. /**
  46. * Definition of the options for the constructor.
  47. *
  48. * @typedef {Object} module:meteoJS/thermodynamicDiagram/diagramSounding~options
  49. * @param {boolean} [visible=true] - Visibility of the sounding.
  50. * @param {module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions}
  51. * [diagram] - Options for the thermodynamic diagram part.
  52. * @param {module:meteoJS/thermodynamicDiagram/diagramSounding~windprofileOptions}
  53. * [windprofile] - Options for the windprofile part.
  54. * @param {module:meteoJS/thermodynamicDiagram/diagramSounding~hodographOptions}
  55. * [hodograph] - Options for this sounding for the hodograph.
  56. * @param {module:meteoJS/thermodynamicDiagram/diagramSounding~parcelsOptions}
  57. * [parcels] - Options for this sounding for the parcels.
  58. */
  59. /**
  60. * Representation of a plotted sounding (data and display options)
  61. *
  62. * <pre><code>import DiagramSounding from 'meteojs/thermodynamicDiagram/DiagramSounding';</code></pre>
  63. *
  64. * @extends module:meteoJS/base/unique.Unique
  65. * @fires module:meteoJS/thermodynamicDiagram/diagramSounding#change:visible
  66. * @fires module:meteoJS/thermodynamicDiagram/diagramSounding#change:options
  67. */
  68. export class DiagramSounding extends Unique {
  69. /**
  70. * @param {module:meteoJS/sounding.Sounding} sounding - Sounding data.
  71. * @param {module:meteoJS/thermodynamicDiagram/diagramSounding~options} [options] - Options.
  72. */
  73. constructor(sounding, {
  74. visible = true,
  75. diagram = {},
  76. windprofile = {},
  77. hodograph = {},
  78. parcels = {}
  79. } = {}) {
  80. super();
  81. /**
  82. * @type module:meteoJS/sounding.Sounding
  83. * @private
  84. */
  85. this._sounding = sounding;
  86. /**
  87. * @type module:meteoJS/base/collection.Collection
  88. * @private
  89. */
  90. this._diagramParcelCollection = new Collection({
  91. fireReplace: false,
  92. fireAddRemoveOnReplace: true,
  93. emptyObjectMaker: () => new DiagramParcel()
  94. });
  95. /**
  96. * @type boolean
  97. * @private
  98. */
  99. this._visible = visible;
  100. /**
  101. * @type Object
  102. * @private
  103. */
  104. this._options = {
  105. diagram: getNormalizedDiagramOptions(diagram),
  106. windprofile: getNormalizedWindprofileOptions(windprofile),
  107. hodograph: getNormalizedHodographOptions(hodograph),
  108. parcels: getNormalizedParcelsOptions(parcels)
  109. };
  110. // Initialize soundig-object with its parcels.
  111. if (this._sounding !== undefined) {
  112. this._sounding.parcelCollection.on('add:item',
  113. parcel => this.addParcel(parcel));
  114. this._sounding.parcelCollection.on('remove:item', parcel => {
  115. for (let diagramParcel of this._diagramParcelCollection)
  116. if (diagramParcel.parcel === parcel)
  117. this._diagramParcelCollection.remove(diagramParcel);
  118. });
  119. for (let parcel of this._sounding.parcelCollection)
  120. this.addParcel(parcel);
  121. }
  122. }
  123. /**
  124. * Sounding data.
  125. *
  126. * @type module:meteoJS/sounding.Sounding
  127. * @readonly
  128. */
  129. get sounding() {
  130. return this._sounding;
  131. }
  132. /**
  133. * Visibility of the sounding.
  134. *
  135. * @type {boolean}
  136. * @fires module:meteoJS/thermodynamicDiagram/diagramSounding#change:visible
  137. */
  138. get visible() {
  139. return this._visible;
  140. }
  141. set visible(visible) {
  142. let oldVisible = this._visible;
  143. this._visible = visible ? true : false;
  144. if (oldVisible != this._visible)
  145. this.trigger('change:visible');
  146. }
  147. get options() {
  148. return this._options;
  149. }
  150. /**
  151. * Collection of the DiagramParcel objects.
  152. *
  153. * @type module:meteoJS/base/collection.Collection
  154. * @readonly
  155. */
  156. get diagramParcelCollection() {
  157. return this._diagramParcelCollection;
  158. }
  159. /**
  160. * Add a parcel with styles to the sounding.
  161. * (analogue to {@link module:meteoJS/thermodynamicDiagramPluggable.ThermodynamicDiagramPluggable#addSounding})
  162. *
  163. * @param {module:meteoJS/sounding/parcel.Parcel} parcel - Parcel object.
  164. * @param {module:meteoJS/thermodynamicDiagram/diagramParcel~parcelOptions}
  165. * [options] - Style options.
  166. * @returns {module:meteoJS/thermodynamicDiagram/diagramParcel.diagramParcel}
  167. * Parcel object for the diagram with style options.
  168. */
  169. addParcel(parcel, options = undefined) {
  170. options = (options === undefined) ? this.getParcelOptions(parcel) : options;
  171. options.parcel = parcel;
  172. const dp = new DiagramParcel(options);
  173. this._diagramParcelCollection.append(dp);
  174. return dp;
  175. }
  176. /**
  177. * Updated the style options for this sounding.
  178. *
  179. * @param {module:meteoJS/thermodynamicDiagram/diagramSounding~options}
  180. * [options] - Options.
  181. * @fires module:meteoJS/thermodynamicDiagram/diagramSounding#change:visible
  182. * @fires module:meteoJS/thermodynamicDiagram/diagramSounding#change:options
  183. */
  184. update({
  185. visible = undefined,
  186. diagram = undefined,
  187. windprofile = undefined,
  188. hodograph = undefined,
  189. parcels = undefined
  190. } = {}) {
  191. let willTrigger = false;
  192. if (diagram === undefined)
  193. diagram = {};
  194. else
  195. willTrigger = true;
  196. if (windprofile === undefined)
  197. windprofile = {};
  198. else
  199. willTrigger = true;
  200. if (hodograph === undefined)
  201. hodograph = {};
  202. else
  203. willTrigger = true;
  204. this._options.diagram =
  205. updateDiagramOptions(this._options.diagram, diagram);
  206. this._options.windprofile =
  207. updateWindprofileOptions(this._options.windprofile, windprofile);
  208. this._options.hodograph =
  209. updateHodographOptions(this._options.hodograph, hodograph);
  210. if (willTrigger)
  211. this.trigger('change:options');
  212. if (parcels === undefined)
  213. parcels = {};
  214. this._options.parcels =
  215. updateParcelsOptions(this._options.parcels, parcels);
  216. for (let diagramParcel of this.diagramParcelCollection) {
  217. if (diagramParcel.id in parcels)
  218. diagramParcel.update(parcels[diagramParcel.id]);
  219. }
  220. if (visible !== undefined)
  221. this.visible = visible;
  222. }
  223. /**
  224. * Returns normalized visibility and style options for a parcel. This is a
  225. * combination of the specific options for the passed parcel and the defaults.
  226. *
  227. * @param {module:meteoJS/sounding/parcel.Parcel} [parcel] - Parcel.
  228. * @returns {module:meteoJS/thermodynamicDiagram/diagramSounding~parcelsOptions}
  229. * Parcel options.
  230. * @public
  231. */
  232. getParcelOptions(parcel = undefined) {
  233. let result = {
  234. visible: this.options.parcels.default.visible,
  235. temp: {
  236. visible: this.options.parcels.default.temp.visible,
  237. style: {}
  238. },
  239. dewp: {
  240. visible: this.options.parcels.default.dewp.visible,
  241. style: {}
  242. }
  243. };
  244. ['temp', 'dewp'].forEach(key => {
  245. Object.keys(this.options.parcels.default[key].style).forEach(styleKey => {
  246. result[key].style[styleKey] =
  247. this.options.parcels.default[key].style[styleKey];
  248. });
  249. });
  250. if (parcel !== undefined &&
  251. parcel.id in this.options.parcels)
  252. result = updateOptionsPart(result, this.options.parcels[parcel.id],
  253. ['temp', 'dewp']);
  254. return result;
  255. }
  256. }
  257. addEventFunctions(DiagramSounding.prototype);
  258. export default DiagramSounding;
  259. /**
  260. * Style/visibility options for a sounding in the thermodynamic diagram.
  261. *
  262. * @typedef {Object} module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions
  263. * @param {boolean} [visible=true]
  264. * Visibility in the thermodynamic diagram.
  265. * @param {module:meteoJS/thermodynamicDiagram~lineOptions}
  266. * [temp] - Options for the temperature curve.
  267. * @param {module:meteoJS/thermodynamicDiagram~lineOptions}
  268. * [dewp] - Options for the dewpoint curve.
  269. * @param {module:meteoJS/thermodynamicDiagram~lineOptions}
  270. * [wetbulb] - Options for the wetbulb temperature curve.
  271. */
  272. /**
  273. * Returns normalized diagram options.
  274. *
  275. * @param {module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions}
  276. * [options] - Options.
  277. * @returns {module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions}
  278. * Normalized options.
  279. * @private
  280. */
  281. function getNormalizedDiagramOptions({
  282. visible = true,
  283. temp = {},
  284. dewp = {},
  285. wetbulb = {}
  286. } = {}) {
  287. return {
  288. visible,
  289. temp: getNormalizedLineOptions(temp, {
  290. style: {
  291. color: 'red',
  292. width: 3,
  293. linecap: 'round'
  294. }
  295. }),
  296. dewp: getNormalizedLineOptions(dewp, {
  297. style: {
  298. color: 'blue',
  299. width: 3,
  300. linecap: 'round'
  301. }
  302. }),
  303. wetbulb: getNormalizedLineOptions(wetbulb, {
  304. style: {
  305. color: 'green',
  306. width: 2,
  307. linecap: 'round'
  308. }
  309. })
  310. };
  311. }
  312. /**
  313. * Updates diagram options.
  314. *
  315. * @param {module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions}
  316. * options - Current options.
  317. * @param {module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions}
  318. * updateOptions - Some new options.
  319. * @returns {module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions}
  320. * New options object.
  321. * @private
  322. */
  323. function updateDiagramOptions(options, updateOptions) {
  324. return updateOptionsPart(options, updateOptions, ['temp', 'dewp', 'wetbulb']);
  325. }
  326. /**
  327. * Style/visibility options for a sounding in the windprofile.
  328. *
  329. * @typedef {Object} module:meteoJS/thermodynamicDiagram/diagramSounding~windprofileOptions
  330. * @param {boolean} [visible=true] - Visibility in the windprofile part.
  331. * @param {module:meteoJS/thermodynamicDiagram~lineOptions}
  332. * [windbarbs] - Options for the windbarbs.
  333. * @param {module:meteoJS/thermodynamicDiagram~lineOptions}
  334. * [windspeed] - Options for the windspeed line.
  335. */
  336. /**
  337. * Returns normalized windprofile options.
  338. *
  339. * @param {module:meteoJS/thermodynamicDiagram/diagramSounding~windprofileOptions}
  340. * [options] - Options.
  341. * @returns {module:meteoJS/thermodynamicDiagram/diagramSounding~windprofileOptions}
  342. * Normalized options.
  343. * @private
  344. */
  345. function getNormalizedWindprofileOptions({
  346. visible = true,
  347. windbarbs = {},
  348. windspeed = {}
  349. } = {}) {
  350. return {
  351. visible,
  352. windbarbs: getNormalizedLineOptions(windbarbs),
  353. windspeed: getNormalizedLineOptions(windspeed)
  354. };
  355. }
  356. /**
  357. * Updates windprofile options.
  358. *
  359. * @param {module:meteoJS/thermodynamicDiagram/diagramSounding~windprofileOptions}
  360. * options - Current options.
  361. * @param {module:meteoJS/thermodynamicDiagram/diagramSounding~windprofileOptions}
  362. * updateOptions - Some new options.
  363. * @returns {module:meteoJS/thermodynamicDiagram/diagramSounding~windprofileOptions}
  364. * New options object.
  365. * @private
  366. */
  367. function updateWindprofileOptions(options, updateOptions) {
  368. return updateOptionsPart(options, updateOptions, ['windbarbs', 'windspeed']);
  369. }
  370. /**
  371. * Returns normalized hodograph options.
  372. *
  373. * @param {module:meteoJS/thermodynamicDiagram/diagramSounding~hodographOptions}
  374. * [options] - Options.
  375. * @returns {module:meteoJS/thermodynamicDiagram/diagramSounding~hodographOptions}
  376. * Normalized options.
  377. * @private
  378. */
  379. function getNormalizedHodographOptions({
  380. minPressure = undefined,
  381. maxPressure = undefined,
  382. segments = [],
  383. ...result
  384. } = {}) {
  385. result = getNormalizedLineOptions(result, {
  386. style: {
  387. color: 'green',
  388. width: 2
  389. }
  390. });
  391. result.minPressure = minPressure;
  392. result.maxPressure = maxPressure;
  393. result.segments = segments.map(({
  394. minPressure = undefined,
  395. maxPressure = undefined,
  396. ...segment
  397. }) => {
  398. segment = getNormalizedLineOptions(segment);
  399. segment.minPressure = minPressure;
  400. segment.maxPressure = maxPressure;
  401. return segment;
  402. });
  403. return result;
  404. }
  405. /**
  406. * Updates hodograph options.
  407. *
  408. * @param {module:meteoJS/thermodynamicDiagram/diagramSounding~hodographOptions}
  409. * options - Current options.
  410. * @param {module:meteoJS/thermodynamicDiagram/diagramSounding~hodographOptions}
  411. * updateOptions - Some new options.
  412. * @returns {module:meteoJS/thermodynamicDiagram/diagramSounding~hodographOptions}
  413. * New options object.
  414. * @private
  415. */
  416. function updateHodographOptions(options, updateOptions) {
  417. options = updateLineOptions(options, updateOptions);
  418. ['minPressure', 'maxPressure'].forEach(styleKey => {
  419. if (styleKey in updateOptions)
  420. options[styleKey] = updateOptions[styleKey];
  421. });
  422. if ('segments' in updateOptions)
  423. options.segments = updateOptions.segments.map(({
  424. minPressure = undefined,
  425. maxPressure = undefined,
  426. ...segment
  427. }) => {
  428. segment = getNormalizedLineOptions(segment);
  429. segment.minPressure = minPressure;
  430. segment.maxPressure = maxPressure;
  431. return segment;
  432. });
  433. return options;
  434. }
  435. /**
  436. * Visibility/style of the parcels. This object can contain further keys with
  437. * values as {@link module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions}
  438. * which applies to the parcel with the equivalent id.
  439. *
  440. * @typedef {Object} module:meteoJS/thermodynamicDiagram/diagramSounding~parcelsOptions
  441. * @param {boolean} [visible=true] - Visibility of the parcels.
  442. * @param {module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions}
  443. * [default] - Default options for a parcel.
  444. */
  445. /**
  446. * Returns normalized parcels options.
  447. *
  448. * @param {module:meteoJS/thermodynamicDiagram/diagramSounding~parcelsOptions}
  449. * [options] - Options.
  450. * @returns {module:meteoJS/thermodynamicDiagram/diagramSounding~parcelsOptions}
  451. * Normalized options.
  452. * @private
  453. */
  454. function getNormalizedParcelsOptions(options = {}) {
  455. if (options.visible === undefined)
  456. options.visible = true;
  457. if (options.default === undefined)
  458. options.default = {};
  459. if (options.default.visible === undefined)
  460. options.default.visible = false;
  461. if (options.default.temp === undefined)
  462. options.default.temp = {};
  463. if (options.default.temp.visible === undefined)
  464. options.default.temp.visible = true;
  465. if (options.default.temp.style === undefined)
  466. options.default.temp.style = {};
  467. if (options.default.temp.style.color === undefined)
  468. options.default.temp.style.color = 'rgb(255, 153, 0)';
  469. if (options.default.temp.style.width === undefined)
  470. options.default.temp.style.width = 3;
  471. if (options.default.temp.style.linecap === undefined)
  472. options.default.temp.style.linecap = 'round';
  473. if (options.default.dewp === undefined)
  474. options.default.dewp = {};
  475. if (options.default.dewp.visible === undefined)
  476. options.default.dewp.visible = true;
  477. if (options.default.dewp.style === undefined)
  478. options.default.dewp.style = {};
  479. if (options.default.dewp.style.color === undefined)
  480. options.default.dewp.style.color = 'rgb(255, 194, 102)';
  481. if (options.default.dewp.style.width === undefined)
  482. options.default.dewp.style.width = 3;
  483. if (options.default.dewp.style.linecap === undefined)
  484. options.default.dewp.style.linecap = 'round';
  485. return options;
  486. }
  487. /**
  488. * Updates Parcels options.
  489. *
  490. * @param {module:meteoJS/thermodynamicDiagram/diagramSounding~parcelsOptions}
  491. * options - Current options.
  492. * @param {module:meteoJS/thermodynamicDiagram/diagramSounding~parcelsOptions}
  493. * updateOptions - Some new options.
  494. * @returns {module:meteoJS/thermodynamicDiagram/diagramSounding~parcelsOptions}
  495. * New options object.
  496. * @private
  497. */
  498. function updateParcelsOptions(options, updateOptions) {
  499. if ('visible' in updateOptions)
  500. options.visible = updateOptions.visible;
  501. if ('default' in updateOptions)
  502. options.default =
  503. updateOptionsPart(options.default, updateOptions.default,
  504. ['temp', 'dewp']);
  505. Object.keys(updateOptions)
  506. .filter(key => key != 'visible' && key != 'default')
  507. .forEach(key =>
  508. options[key] =
  509. updateDiagramOptions(
  510. (key in options) ? options[key] : {},
  511. updateOptions[key]));
  512. return options;
  513. }
  514. /**
  515. * Updates diagram/windprofile options.
  516. *
  517. * @param {module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions|module:meteoJS/thermodynamicDiagram/diagramSounding~windprofileOptions}
  518. * options - Current options.
  519. * @param {module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions|module:meteoJS/thermodynamicDiagram/diagramSounding~windprofileOptions}
  520. * updateOptions - Some new options.
  521. * @param {Array.<string>} [lineKeys] - Keys to update.
  522. * @returns {module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions|module:meteoJS/thermodynamicDiagram/diagramSounding~windprofileOptions}
  523. * New options object.
  524. * @private
  525. */
  526. function updateOptionsPart(options, updateOptions, lineKeys = []) {
  527. if ('visible' in updateOptions)
  528. options.visible = updateOptions.visible;
  529. lineKeys.forEach(key => {
  530. if (key in updateOptions)
  531. options[key] = updateLineOptions(options[key] ? options[key] : { style: {} }, updateOptions[key]);
  532. });
  533. return options;
  534. }