/**
* @module meteoJS/thermodynamicDiagram/diagramSounding
*/
import addEventFunctions from '../Events.js';
import Unique from '../base/Unique.js';
import Collection from '../base/Collection.js';
import {
getNormalizedLineOptions,
updateLineOptions
} from '../thermodynamicDiagram/Functions.js';
import DiagramParcel from './DiagramParcel.js';
/**
* Change visibility event. Only triggered, if the visibility of the sounding
* changes, not if only a part's visibility (like hodograph) changes.
*
* @event module:meteoJS/thermodynamicDiagram/diagramSounding#change:visible
*/
/**
* Change options event.
*
* @event module:meteoJS/thermodynamicDiagram/diagramSounding#change:options
*/
/**
* Options for a line-segment of a sounding in the hodograph.
*
* @typedef {module:meteoJS/thermodynamicDiagram~lineOptions}
* module:meteoJS/thermodynamicDiagram/diagramSounding~hodographSegmentOptions
* @property {number|undefined}
* [minPressure] - Minimum pressure level of the segment. Unit: hPa.
* @property {number|undefined}
* [maxPressure] - Maximum pressure level of the segment. Unit: hPa.
*/
/**
* Options for a sounding in the hodograph.
*
* @typedef {module:meteoJS/thermodynamicDiagram~lineOptions}
* module:meteoJS/thermodynamicDiagram/diagramSounding~hodographOptions
* @property {number|undefined}
* [minPressure] - Minimum pressure level to plot in the hodograph. Unit: hPa.
* @property {number|undefined}
* [maxPressure] - Maximum pressure level to plot in the hodograph. Unit: hPa.
* @property {module:meteoJS/thermodynamicDiagram/diagramSounding~hodographSegmentOptions[]}
* [segments] - Array of segment definitions.
*/
/**
* Definition of the options for the constructor.
*
* @typedef {Object} module:meteoJS/thermodynamicDiagram/diagramSounding~options
* @param {boolean} [visible=true] - Visibility of the sounding.
* @param {module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions}
* [diagram] - Options for the thermodynamic diagram part.
* @param {module:meteoJS/thermodynamicDiagram/diagramSounding~windprofileOptions}
* [windprofile] - Options for the windprofile part.
* @param {module:meteoJS/thermodynamicDiagram/diagramSounding~hodographOptions}
* [hodograph] - Options for this sounding for the hodograph.
* @param {module:meteoJS/thermodynamicDiagram/diagramSounding~parcelsOptions}
* [parcels] - Options for this sounding for the parcels.
*/
/**
* Representation of a plotted sounding (data and display options)
*
* <pre><code>import DiagramSounding from 'meteojs/thermodynamicDiagram/DiagramSounding';</code></pre>
*
* @extends module:meteoJS/base/unique.Unique
* @fires module:meteoJS/thermodynamicDiagram/diagramSounding#change:visible
* @fires module:meteoJS/thermodynamicDiagram/diagramSounding#change:options
*/
export class DiagramSounding extends Unique {
/**
* @param {module:meteoJS/sounding.Sounding} sounding - Sounding data.
* @param {module:meteoJS/thermodynamicDiagram/diagramSounding~options} [options] - Options.
*/
constructor(sounding, {
visible = true,
diagram = {},
windprofile = {},
hodograph = {},
parcels = {}
} = {}) {
super();
/**
* @type module:meteoJS/sounding.Sounding
* @private
*/
this._sounding = sounding;
/**
* @type module:meteoJS/base/collection.Collection
* @private
*/
this._diagramParcelCollection = new Collection({
fireReplace: false,
fireAddRemoveOnReplace: true,
emptyObjectMaker: () => new DiagramParcel()
});
/**
* @type boolean
* @private
*/
this._visible = visible;
/**
* @type Object
* @private
*/
this._options = {
diagram: getNormalizedDiagramOptions(diagram),
windprofile: getNormalizedWindprofileOptions(windprofile),
hodograph: getNormalizedHodographOptions(hodograph),
parcels: getNormalizedParcelsOptions(parcels)
};
// Initialize soundig-object with its parcels.
if (this._sounding !== undefined) {
this._sounding.parcelCollection.on('add:item',
parcel => this.addParcel(parcel));
this._sounding.parcelCollection.on('remove:item', parcel => {
for (let diagramParcel of this._diagramParcelCollection)
if (diagramParcel.parcel === parcel)
this._diagramParcelCollection.remove(diagramParcel);
});
for (let parcel of this._sounding.parcelCollection)
this.addParcel(parcel);
}
}
/**
* Sounding data.
*
* @type module:meteoJS/sounding.Sounding
* @readonly
*/
get sounding() {
return this._sounding;
}
/**
* Visibility of the sounding.
*
* @type {boolean}
* @fires module:meteoJS/thermodynamicDiagram/diagramSounding#change:visible
*/
get visible() {
return this._visible;
}
set visible(visible) {
let oldVisible = this._visible;
this._visible = visible ? true : false;
if (oldVisible != this._visible)
this.trigger('change:visible');
}
get options() {
return this._options;
}
/**
* Collection of the DiagramParcel objects.
*
* @type module:meteoJS/base/collection.Collection
* @readonly
*/
get diagramParcelCollection() {
return this._diagramParcelCollection;
}
/**
* Add a parcel with styles to the sounding.
* (analogue to {@link module:meteoJS/thermodynamicDiagramPluggable.ThermodynamicDiagramPluggable#addSounding})
*
* @param {module:meteoJS/sounding/parcel.Parcel} parcel - Parcel object.
* @param {module:meteoJS/thermodynamicDiagram/diagramParcel~parcelOptions}
* [options] - Style options.
* @returns {module:meteoJS/thermodynamicDiagram/diagramParcel.diagramParcel}
* Parcel object for the diagram with style options.
*/
addParcel(parcel, options = undefined) {
options = (options === undefined) ? this.getParcelOptions(parcel) : options;
options.parcel = parcel;
const dp = new DiagramParcel(options);
this._diagramParcelCollection.append(dp);
return dp;
}
/**
* Updated the style options for this sounding.
*
* @param {module:meteoJS/thermodynamicDiagram/diagramSounding~options}
* [options] - Options.
* @fires module:meteoJS/thermodynamicDiagram/diagramSounding#change:visible
* @fires module:meteoJS/thermodynamicDiagram/diagramSounding#change:options
*/
update({
visible = undefined,
diagram = undefined,
windprofile = undefined,
hodograph = undefined,
parcels = undefined
} = {}) {
let willTrigger = false;
if (diagram === undefined)
diagram = {};
else
willTrigger = true;
if (windprofile === undefined)
windprofile = {};
else
willTrigger = true;
if (hodograph === undefined)
hodograph = {};
else
willTrigger = true;
this._options.diagram =
updateDiagramOptions(this._options.diagram, diagram);
this._options.windprofile =
updateWindprofileOptions(this._options.windprofile, windprofile);
this._options.hodograph =
updateHodographOptions(this._options.hodograph, hodograph);
if (willTrigger)
this.trigger('change:options');
if (parcels === undefined)
parcels = {};
this._options.parcels =
updateParcelsOptions(this._options.parcels, parcels);
for (let diagramParcel of this.diagramParcelCollection) {
if (diagramParcel.id in parcels)
diagramParcel.update(parcels[diagramParcel.id]);
}
if (visible !== undefined)
this.visible = visible;
}
/**
* Returns normalized visibility and style options for a parcel. This is a
* combination of the specific options for the passed parcel and the defaults.
*
* @param {module:meteoJS/sounding/parcel.Parcel} [parcel] - Parcel.
* @returns {module:meteoJS/thermodynamicDiagram/diagramSounding~parcelsOptions}
* Parcel options.
* @public
*/
getParcelOptions(parcel = undefined) {
let result = {
visible: this.options.parcels.default.visible,
temp: {
visible: this.options.parcels.default.temp.visible,
style: {}
},
dewp: {
visible: this.options.parcels.default.dewp.visible,
style: {}
}
};
['temp', 'dewp'].forEach(key => {
Object.keys(this.options.parcels.default[key].style).forEach(styleKey => {
result[key].style[styleKey] =
this.options.parcels.default[key].style[styleKey];
});
});
if (parcel !== undefined &&
parcel.id in this.options.parcels)
result = updateOptionsPart(result, this.options.parcels[parcel.id],
['temp', 'dewp']);
return result;
}
}
addEventFunctions(DiagramSounding.prototype);
export default DiagramSounding;
/**
* Style/visibility options for a sounding in the thermodynamic diagram.
*
* @typedef {Object} module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions
* @param {boolean} [visible=true]
* Visibility in the thermodynamic diagram.
* @param {module:meteoJS/thermodynamicDiagram~lineOptions}
* [temp] - Options for the temperature curve.
* @param {module:meteoJS/thermodynamicDiagram~lineOptions}
* [dewp] - Options for the dewpoint curve.
* @param {module:meteoJS/thermodynamicDiagram~lineOptions}
* [wetbulb] - Options for the wetbulb temperature curve.
*/
/**
* Returns normalized diagram options.
*
* @param {module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions}
* [options] - Options.
* @returns {module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions}
* Normalized options.
* @private
*/
function getNormalizedDiagramOptions({
visible = true,
temp = {},
dewp = {},
wetbulb = {}
} = {}) {
return {
visible,
temp: getNormalizedLineOptions(temp, {
style: {
color: 'red',
width: 3,
linecap: 'round'
}
}),
dewp: getNormalizedLineOptions(dewp, {
style: {
color: 'blue',
width: 3,
linecap: 'round'
}
}),
wetbulb: getNormalizedLineOptions(wetbulb, {
style: {
color: 'green',
width: 2,
linecap: 'round'
}
})
};
}
/**
* Updates diagram options.
*
* @param {module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions}
* options - Current options.
* @param {module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions}
* updateOptions - Some new options.
* @returns {module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions}
* New options object.
* @private
*/
function updateDiagramOptions(options, updateOptions) {
return updateOptionsPart(options, updateOptions, ['temp', 'dewp', 'wetbulb']);
}
/**
* Style/visibility options for a sounding in the windprofile.
*
* @typedef {Object} module:meteoJS/thermodynamicDiagram/diagramSounding~windprofileOptions
* @param {boolean} [visible=true] - Visibility in the windprofile part.
* @param {module:meteoJS/thermodynamicDiagram~lineOptions}
* [windbarbs] - Options for the windbarbs.
* @param {module:meteoJS/thermodynamicDiagram~lineOptions}
* [windspeed] - Options for the windspeed line.
*/
/**
* Returns normalized windprofile options.
*
* @param {module:meteoJS/thermodynamicDiagram/diagramSounding~windprofileOptions}
* [options] - Options.
* @returns {module:meteoJS/thermodynamicDiagram/diagramSounding~windprofileOptions}
* Normalized options.
* @private
*/
function getNormalizedWindprofileOptions({
visible = true,
windbarbs = {},
windspeed = {}
} = {}) {
return {
visible,
windbarbs: getNormalizedLineOptions(windbarbs),
windspeed: getNormalizedLineOptions(windspeed)
};
}
/**
* Updates windprofile options.
*
* @param {module:meteoJS/thermodynamicDiagram/diagramSounding~windprofileOptions}
* options - Current options.
* @param {module:meteoJS/thermodynamicDiagram/diagramSounding~windprofileOptions}
* updateOptions - Some new options.
* @returns {module:meteoJS/thermodynamicDiagram/diagramSounding~windprofileOptions}
* New options object.
* @private
*/
function updateWindprofileOptions(options, updateOptions) {
return updateOptionsPart(options, updateOptions, ['windbarbs', 'windspeed']);
}
/**
* Returns normalized hodograph options.
*
* @param {module:meteoJS/thermodynamicDiagram/diagramSounding~hodographOptions}
* [options] - Options.
* @returns {module:meteoJS/thermodynamicDiagram/diagramSounding~hodographOptions}
* Normalized options.
* @private
*/
function getNormalizedHodographOptions({
minPressure = undefined,
maxPressure = undefined,
segments = [],
...result
} = {}) {
result = getNormalizedLineOptions(result, {
style: {
color: 'green',
width: 2
}
});
result.minPressure = minPressure;
result.maxPressure = maxPressure;
result.segments = segments.map(({
minPressure = undefined,
maxPressure = undefined,
...segment
}) => {
segment = getNormalizedLineOptions(segment);
segment.minPressure = minPressure;
segment.maxPressure = maxPressure;
return segment;
});
return result;
}
/**
* Updates hodograph options.
*
* @param {module:meteoJS/thermodynamicDiagram/diagramSounding~hodographOptions}
* options - Current options.
* @param {module:meteoJS/thermodynamicDiagram/diagramSounding~hodographOptions}
* updateOptions - Some new options.
* @returns {module:meteoJS/thermodynamicDiagram/diagramSounding~hodographOptions}
* New options object.
* @private
*/
function updateHodographOptions(options, updateOptions) {
options = updateLineOptions(options, updateOptions);
['minPressure', 'maxPressure'].forEach(styleKey => {
if (styleKey in updateOptions)
options[styleKey] = updateOptions[styleKey];
});
if ('segments' in updateOptions)
options.segments = updateOptions.segments.map(({
minPressure = undefined,
maxPressure = undefined,
...segment
}) => {
segment = getNormalizedLineOptions(segment);
segment.minPressure = minPressure;
segment.maxPressure = maxPressure;
return segment;
});
return options;
}
/**
* Visibility/style of the parcels. This object can contain further keys with
* values as {@link module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions}
* which applies to the parcel with the equivalent id.
*
* @typedef {Object} module:meteoJS/thermodynamicDiagram/diagramSounding~parcelsOptions
* @param {boolean} [visible=true] - Visibility of the parcels.
* @param {module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions}
* [default] - Default options for a parcel.
*/
/**
* Returns normalized parcels options.
*
* @param {module:meteoJS/thermodynamicDiagram/diagramSounding~parcelsOptions}
* [options] - Options.
* @returns {module:meteoJS/thermodynamicDiagram/diagramSounding~parcelsOptions}
* Normalized options.
* @private
*/
function getNormalizedParcelsOptions(options = {}) {
if (options.visible === undefined)
options.visible = true;
if (options.default === undefined)
options.default = {};
if (options.default.visible === undefined)
options.default.visible = false;
if (options.default.temp === undefined)
options.default.temp = {};
if (options.default.temp.visible === undefined)
options.default.temp.visible = true;
if (options.default.temp.style === undefined)
options.default.temp.style = {};
if (options.default.temp.style.color === undefined)
options.default.temp.style.color = 'rgb(255, 153, 0)';
if (options.default.temp.style.width === undefined)
options.default.temp.style.width = 3;
if (options.default.temp.style.linecap === undefined)
options.default.temp.style.linecap = 'round';
if (options.default.dewp === undefined)
options.default.dewp = {};
if (options.default.dewp.visible === undefined)
options.default.dewp.visible = true;
if (options.default.dewp.style === undefined)
options.default.dewp.style = {};
if (options.default.dewp.style.color === undefined)
options.default.dewp.style.color = 'rgb(255, 194, 102)';
if (options.default.dewp.style.width === undefined)
options.default.dewp.style.width = 3;
if (options.default.dewp.style.linecap === undefined)
options.default.dewp.style.linecap = 'round';
return options;
}
/**
* Updates Parcels options.
*
* @param {module:meteoJS/thermodynamicDiagram/diagramSounding~parcelsOptions}
* options - Current options.
* @param {module:meteoJS/thermodynamicDiagram/diagramSounding~parcelsOptions}
* updateOptions - Some new options.
* @returns {module:meteoJS/thermodynamicDiagram/diagramSounding~parcelsOptions}
* New options object.
* @private
*/
function updateParcelsOptions(options, updateOptions) {
if ('visible' in updateOptions)
options.visible = updateOptions.visible;
if ('default' in updateOptions)
options.default =
updateOptionsPart(options.default, updateOptions.default,
['temp', 'dewp']);
Object.keys(updateOptions)
.filter(key => key != 'visible' && key != 'default')
.forEach(key =>
options[key] =
updateDiagramOptions(
(key in options) ? options[key] : {},
updateOptions[key]));
return options;
}
/**
* Updates diagram/windprofile options.
*
* @param {module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions|module:meteoJS/thermodynamicDiagram/diagramSounding~windprofileOptions}
* options - Current options.
* @param {module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions|module:meteoJS/thermodynamicDiagram/diagramSounding~windprofileOptions}
* updateOptions - Some new options.
* @param {Array.<string>} [lineKeys] - Keys to update.
* @returns {module:meteoJS/thermodynamicDiagram/diagramSounding~diagramOptions|module:meteoJS/thermodynamicDiagram/diagramSounding~windprofileOptions}
* New options object.
* @private
*/
function updateOptionsPart(options, updateOptions, lineKeys = []) {
if ('visible' in updateOptions)
options.visible = updateOptions.visible;
lineKeys.forEach(key => {
if (key in updateOptions)
options[key] = updateLineOptions(options[key] ? options[key] : { style: {} }, updateOptions[key]);
});
return options;
}