@opengeoweb/soundings 12.6.0 → 12.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.esm.js +272 -93
- package/package.json +7 -7
- package/src/lib/api/edr-api/hooks.d.ts +5 -1
- package/src/lib/api/edr-api/types.d.ts +2 -1
- package/src/lib/components/Providers/Providers.d.ts +1 -0
- package/src/lib/components/Soundings/DiagramTypeButton.d.ts +8 -0
- package/src/lib/components/Soundings/Soundings.stories.d.ts +4 -2
- package/src/lib/components/Soundings/SoundingsConnect.d.ts +3 -1
- package/src/lib/components/Soundings/SoundingsConnect.stories.d.ts +5 -0
- package/src/lib/components/Soundings/drawUtils.d.ts +2 -0
- package/src/lib/components/StaticLines/drawDryAdiabats.d.ts +3 -1
- package/src/lib/components/StaticLines/drawIsohumes.d.ts +3 -1
- package/src/lib/components/StaticLines/drawIsotherms.d.ts +3 -1
- package/src/lib/components/StaticLines/drawMoistAdiabats.d.ts +3 -1
- package/src/lib/utils/soundingsLookUp.d.ts +2 -0
package/index.esm.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
import React, { useRef, useEffect, useMemo, useState, Fragment as Fragment$1 } from 'react';
|
|
2
3
|
import { useSelector, useDispatch } from 'react-redux';
|
|
3
|
-
import {
|
|
4
|
+
import { Box, Stack, Menu, ListSubheader, Typography, MenuItem, LinearProgress, Card, List, ListItem, ListItemIcon, Checkbox, ListItemText, Grid, ListItemButton } from '@mui/material';
|
|
4
5
|
import { safelyInitI18nForTestsAndStories, sharedTranslations, SHARED_NAMESPACE, dateUtils, CustomSlider, CustomToggleButton, AlertBanner, ToolContainerDraggable, StyledSelect, SearchField, CustomAccordion } from '@opengeoweb/shared';
|
|
5
|
-
import React, { useRef, useEffect, useMemo, useState, Fragment as Fragment$1 } from 'react';
|
|
6
6
|
import * as d3 from 'd3';
|
|
7
7
|
import { useQueries } from '@tanstack/react-query';
|
|
8
8
|
import { getHeaders, handleResponse } from '@opengeoweb/api';
|
|
9
9
|
import { ParseISOTimeRangeDuration, parseISO8601IntervalToDateInterval } from '@opengeoweb/webmap';
|
|
10
|
-
import { range } from 'lodash';
|
|
10
|
+
import { range, minBy } from 'lodash';
|
|
11
11
|
import { timesliderTranslations, TIMESLIDER_NAMESPACE, BackwardForwardStepButton } from '@opengeoweb/timeslider';
|
|
12
12
|
import i18n from 'i18next';
|
|
13
13
|
export { default as i18n } from 'i18next';
|
|
@@ -23,7 +23,11 @@ var en = {
|
|
|
23
23
|
noInstancesAvailable: "No instances found",
|
|
24
24
|
noServicesFound: "No source available",
|
|
25
25
|
locations: "Locations",
|
|
26
|
-
"locations-menu-warning": "You have to choose your sources in the sources menu"
|
|
26
|
+
"locations-menu-warning": "You have to choose your sources in the sources menu",
|
|
27
|
+
options: "Options",
|
|
28
|
+
"diagram-SKEWT": "Skew-T",
|
|
29
|
+
"diagram-BIJVOET": "Bijvoet",
|
|
30
|
+
"diagram-EMAGRAM": "Emagram"
|
|
27
31
|
};
|
|
28
32
|
var no = {
|
|
29
33
|
sources: "Kilder",
|
|
@@ -31,7 +35,11 @@ var no = {
|
|
|
31
35
|
noInstancesAvailable: "Ingen instanser funnet",
|
|
32
36
|
noServicesFound: "Ingen tjenester funnet",
|
|
33
37
|
locations: "Lokasjoner",
|
|
34
|
-
"locations-menu-warning": "Du må velge kilder i kilde-menyen"
|
|
38
|
+
"locations-menu-warning": "Du må velge kilder i kilde-menyen",
|
|
39
|
+
options: "Valg",
|
|
40
|
+
"diagram-SKEWT": "Skew-T",
|
|
41
|
+
"diagram-BIJVOET": "Bijvoet",
|
|
42
|
+
"diagram-EMAGRAM": "Emagram"
|
|
35
43
|
};
|
|
36
44
|
var nl = {
|
|
37
45
|
sources: "Bronnen",
|
|
@@ -39,7 +47,11 @@ var nl = {
|
|
|
39
47
|
noInstancesAvailable: "Geen exemplaren gevonden",
|
|
40
48
|
noServicesFound: "Geen bron beschikbaar",
|
|
41
49
|
locations: "Locaties",
|
|
42
|
-
"locations-menu-warning": "Selecteer eerst een bron in het bron menu"
|
|
50
|
+
"locations-menu-warning": "Selecteer eerst een bron in het bron menu",
|
|
51
|
+
options: "Opties",
|
|
52
|
+
"diagram-SKEWT": "Skew-T",
|
|
53
|
+
"diagram-BIJVOET": "Bijvoet",
|
|
54
|
+
"diagram-EMAGRAM": "Emagram"
|
|
43
55
|
};
|
|
44
56
|
var fi = {
|
|
45
57
|
sources: "Lähteet",
|
|
@@ -47,7 +59,11 @@ var fi = {
|
|
|
47
59
|
noInstancesAvailable: "Tapauksia ei löytynyt",
|
|
48
60
|
noServicesFound: "Lähdettä ei ole saatavilla",
|
|
49
61
|
locations: "Locations",
|
|
50
|
-
"locations-menu-warning": "Valitse ensin lähde lähdevalikosta"
|
|
62
|
+
"locations-menu-warning": "Valitse ensin lähde lähdevalikosta",
|
|
63
|
+
options: "Valinnat",
|
|
64
|
+
"diagram-SKEWT": "Skew-T",
|
|
65
|
+
"diagram-BIJVOET": "Bijvoet",
|
|
66
|
+
"diagram-EMAGRAM": "Emagram"
|
|
51
67
|
};
|
|
52
68
|
var soundingsTranslations = {
|
|
53
69
|
en: en,
|
|
@@ -158,7 +174,7 @@ const calculateThetaFromPressTemp = (pressure, temperature, basePressure) => {
|
|
|
158
174
|
const pstep = (base - pressure) / numberOfSteps;
|
|
159
175
|
let tempN = temperature;
|
|
160
176
|
let pressureN = pressure;
|
|
161
|
-
for (let
|
|
177
|
+
for (let step = 0; step < numberOfSteps; step += 1) {
|
|
162
178
|
const k1 = pstep * dTdp(pressureN, tempN);
|
|
163
179
|
const k2 = pstep * dTdp(pressureN + 0.5 * pstep, tempN + 0.5 * k1);
|
|
164
180
|
const k3 = pstep * dTdp(pressureN + 0.5 * pstep, tempN + 0.5 * k2);
|
|
@@ -229,6 +245,22 @@ const drawIsobars = ({
|
|
|
229
245
|
* Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
|
|
230
246
|
* */
|
|
231
247
|
const SOUNDING_COLORS = ['#4487f3', '#c3213c', '#509a00'];
|
|
248
|
+
const getDiagramName = (t, diagramType) => {
|
|
249
|
+
switch (diagramType) {
|
|
250
|
+
case 'BIJVOET':
|
|
251
|
+
{
|
|
252
|
+
return t('diagram-BIJVOET');
|
|
253
|
+
}
|
|
254
|
+
case 'EMAGRAM':
|
|
255
|
+
{
|
|
256
|
+
return t('diagram-EMAGRAM');
|
|
257
|
+
}
|
|
258
|
+
default:
|
|
259
|
+
{
|
|
260
|
+
return t('diagram-SKEWT');
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
};
|
|
232
264
|
// vfx code
|
|
233
265
|
const initializeSvg = ({
|
|
234
266
|
svgRef,
|
|
@@ -237,10 +269,9 @@ const initializeSvg = ({
|
|
|
237
269
|
margin,
|
|
238
270
|
diagramType
|
|
239
271
|
}) => {
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
svg.append('
|
|
243
|
-
svg.append('rect').attr('height', height).attr('width', width).attr('fill', '#ffffff00').attr('opacity', 1).attr('id', `${diagram}d3`).attr('clip-path', `url(#clip-${diagram}d3)`);
|
|
272
|
+
const svg = d3.select(svgRef.current).attr('width', width + (margin.left + margin.right)).attr('height', height + (margin.top + margin.bottom)).attr('id', diagramType).attr('data-testid', diagramType).append('g').attr('transform', `translate(${margin.left + margin.right},${margin.top})`);
|
|
273
|
+
svg.append('clipPath').attr('id', `clip-${diagramType}d3`).append('rect').attr('width', width).attr('height', height).attr('x', 0).attr('y', 0);
|
|
274
|
+
svg.append('rect').attr('height', height).attr('width', width).attr('fill', '#ffffff00').attr('opacity', 1).attr('id', `${diagramType}d3`).attr('clip-path', `url(#clip-${diagramType}d3)`);
|
|
244
275
|
return svg;
|
|
245
276
|
};
|
|
246
277
|
const createScales = (diagramType, width, height, minPressure, maxPressure, minTemperature, maxTemperature) => {
|
|
@@ -271,9 +302,9 @@ const drawAxes = ({
|
|
|
271
302
|
svg.append('g').attr('type', 'x-axis').attr('transform', `translate(0,${height})`).call(d3.axisBottom(xScale).tickValues(xTicks).tickSizeInner(0).tickSizeOuter(0));
|
|
272
303
|
// Reformat in the case of the middle tick label
|
|
273
304
|
// to add the unit part
|
|
274
|
-
svg.selectAll('g[type="x-axis"] .tick').filter(function () {
|
|
305
|
+
svg.selectAll('g[type="x-axis"] .tick').filter(function isMiddle() {
|
|
275
306
|
return parseInt(d3.select(this).select('text').text(), 10) === xMiddleValue;
|
|
276
|
-
}).each(function () {
|
|
307
|
+
}).each(function txt() {
|
|
277
308
|
const unitPart = '°C';
|
|
278
309
|
d3.select(this).select('text')
|
|
279
310
|
// tspan necessary to use a different font-size
|
|
@@ -283,7 +314,7 @@ const drawAxes = ({
|
|
|
283
314
|
// Create y-axis with intervals
|
|
284
315
|
// Make sure all zeroes are printed (1000 vs 1k)
|
|
285
316
|
const formatTicks = d3.format('.0f');
|
|
286
|
-
svg.append('g').attr('type', 'y-axis').call(d3.axisLeft(yScale).tickValues(isobarValues).tickFormat(formatTicks).tickSizeInner(0).tickSizeOuter(0)).call(
|
|
317
|
+
svg.append('g').attr('type', 'y-axis').call(d3.axisLeft(yScale).tickValues(isobarValues).tickFormat(formatTicks).tickSizeInner(0).tickSizeOuter(0)).call(gee => gee.select('.domain').remove()).append('text').attr('fill', 'currentColor').attr('x', '0').attr('y', yScale(400)).attr('font-size', '11px').attr('dy', '-0.9em').attr('text-anchor', 'left').text('hPa');
|
|
287
318
|
};
|
|
288
319
|
const addLegends = ({
|
|
289
320
|
svg,
|
|
@@ -300,6 +331,9 @@ const mapToPlot = (diagramType, filteredPressureValues, filteredTemperatureValue
|
|
|
300
331
|
return d3.zip(filteredPressureValues, thetaValues).map(([pressure, theta]) => [xScale(theta), yScale(pressure)]);
|
|
301
332
|
}
|
|
302
333
|
const Px = d3.zip(filteredPressureValues, filteredTemperatureValues).map(([pressure, temperature]) => [xScale(temperature), yScale(pressure)]);
|
|
334
|
+
if (diagramType === 'EMAGRAM') {
|
|
335
|
+
return Px.map(([x, y]) => [x, y]);
|
|
336
|
+
}
|
|
303
337
|
// Skew/shear transformation for skew-t
|
|
304
338
|
return Px.map(([x, y]) => [x + height - y, y]);
|
|
305
339
|
};
|
|
@@ -314,17 +348,16 @@ const plotSounding = ({
|
|
|
314
348
|
svg,
|
|
315
349
|
diagramType
|
|
316
350
|
}) => {
|
|
317
|
-
const diagram = diagramType === 'BIJVOET' ? 'bijvoet' : 'skewT';
|
|
318
351
|
const [filteredPressureValues, filteredTemperatureValues] = filterDomainRangeForParameter(pressureValues, temperatureValues);
|
|
319
352
|
// Map temp and pressure to pixels
|
|
320
353
|
const Px = mapToPlot(diagramType, filteredPressureValues, filteredTemperatureValues, xScale, yScale, height);
|
|
321
354
|
const lineGenerator = d3.line();
|
|
322
355
|
const pathString = lineGenerator(Px);
|
|
323
|
-
svg.append('path').attr('d', pathString).attr('id', 'dataline').attr('x', 0).attr('y', 0).style('fill', 'none').style('stroke-width', 2).style('stroke', color).style('stroke-opacity', 1).style('stroke-dasharray', lineStyle).attr('clip-path', `url(#clip-${
|
|
356
|
+
svg.append('path').attr('d', pathString).attr('id', 'dataline').attr('x', 0).attr('y', 0).style('fill', 'none').style('stroke-width', 2).style('stroke', color).style('stroke-opacity', 1).style('stroke-dasharray', lineStyle).attr('clip-path', `url(#clip-${diagramType}d3)`);
|
|
324
357
|
};
|
|
325
358
|
// Use this function to filter the pressure and parameter array so no NaN or null values are included
|
|
326
359
|
const filterDomainRangeForParameter = (pressureDomain, parameterDomain) => {
|
|
327
|
-
const filteredPressureDomain = pressureDomain.filter((
|
|
360
|
+
const filteredPressureDomain = pressureDomain.filter((_value, index) => !isNaN(parameterDomain[index]) && parameterDomain[index] !== null);
|
|
328
361
|
const filteredParameterDomain = parameterDomain.filter(value => !isNaN(value) && value !== null);
|
|
329
362
|
return [filteredPressureDomain, filteredParameterDomain];
|
|
330
363
|
};
|
|
@@ -830,16 +863,16 @@ const drawWindBarbs = ({
|
|
|
830
863
|
return;
|
|
831
864
|
}
|
|
832
865
|
// Add extra width to all isobars needed for the windbarbs
|
|
833
|
-
d3.selectAll('path[type="isobar"]').each(
|
|
834
|
-
const path = d3.select(
|
|
866
|
+
d3.selectAll('path[type="isobar"]').each((_d, i, nodes) => {
|
|
867
|
+
const path = d3.select(nodes[i]).attr('d');
|
|
835
868
|
const newD = extendIsoBar(path, horizontalBarbMargin);
|
|
836
|
-
d3.select(
|
|
869
|
+
d3.select(nodes[i]).attr('d', newD).attr('clip-path', null);
|
|
837
870
|
});
|
|
838
871
|
// Add extra width to x-axis needed for the windbarbs
|
|
839
|
-
d3.select('g[type="x-axis"] path.domain').each(
|
|
840
|
-
const path = d3.select(
|
|
872
|
+
d3.select('g[type="x-axis"] path.domain').each((_d, i, nodes) => {
|
|
873
|
+
const path = d3.select(nodes[i]).attr('d');
|
|
841
874
|
const newD = extendXAxis(path, horizontalBarbMargin);
|
|
842
|
-
d3.select(
|
|
875
|
+
d3.select(nodes[i]).attr('d', newD);
|
|
843
876
|
});
|
|
844
877
|
svg.attr('width', currentWidth + margin.left + horizontalBarbMargin);
|
|
845
878
|
const barbGroup = svg.append('g').attr('class', 'wind-group').attr('transform',
|
|
@@ -857,8 +890,8 @@ const drawWindBarbs = ({
|
|
|
857
890
|
innerGroup.selectAll('*').attr('stroke', barbColor);
|
|
858
891
|
// Only update the fill for the paths that already used a fill
|
|
859
892
|
// in their original svg form.
|
|
860
|
-
innerGroup.selectAll('*').each(
|
|
861
|
-
const el = d3.select(
|
|
893
|
+
innerGroup.selectAll('*').each((_d, i, nodes) => {
|
|
894
|
+
const el = d3.select(nodes[i]);
|
|
862
895
|
const currentFill = el.attr('fill');
|
|
863
896
|
if (currentFill && currentFill !== 'none') {
|
|
864
897
|
el.attr('fill', barbColor);
|
|
@@ -938,7 +971,8 @@ const drawDryAdiabats = ({
|
|
|
938
971
|
xScale,
|
|
939
972
|
yScale,
|
|
940
973
|
svg,
|
|
941
|
-
dp
|
|
974
|
+
dp,
|
|
975
|
+
diagramType
|
|
942
976
|
}) => {
|
|
943
977
|
for (let Tbase = -40; Tbase < 240; Tbase += 10) {
|
|
944
978
|
let Pnew = staticPressureArray[0];
|
|
@@ -950,13 +984,15 @@ const drawDryAdiabats = ({
|
|
|
950
984
|
Pnew += DPDT[0];
|
|
951
985
|
Px.push([xScale(Tnew), yScale(pressure)]);
|
|
952
986
|
}
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
987
|
+
if (diagramType === 'SKEWT') {
|
|
988
|
+
// Skew/shear transformation for skew-t
|
|
989
|
+
for (const PxElement of Px) {
|
|
990
|
+
PxElement[0] = PxElement[0] + height - PxElement[1];
|
|
991
|
+
}
|
|
956
992
|
}
|
|
957
993
|
const lineGenerator = d3.line();
|
|
958
994
|
const daPathString = lineGenerator(Px);
|
|
959
|
-
svg.append('path').attr('d', daPathString).style('fill', 'none').style('stroke-width', 1).style('stroke', 'green').style('opacity', 0.3).attr('clip-path',
|
|
995
|
+
svg.append('path').attr('d', daPathString).style('fill', 'none').style('stroke-width', 1).style('stroke', 'green').style('opacity', 0.3).attr('clip-path', `url(#clip-${diagramType}d3)`);
|
|
960
996
|
}
|
|
961
997
|
};
|
|
962
998
|
const tempPresDryAiabat = (pressure, pres0, temp0) => temp0 * (pressure / pres0) ** (2.0 / 7.0);
|
|
@@ -981,7 +1017,7 @@ const drawDryAdiabatsBijvoet = ({
|
|
|
981
1017
|
const lineGenerator = d3.line();
|
|
982
1018
|
const daPathString = lineGenerator(Px);
|
|
983
1019
|
const strID = `${Tbase}-dryadiabat`;
|
|
984
|
-
svg.append('path').attr('id', strID).attr('d', daPathString).style('fill', 'none').style('stroke-width', 1).style('stroke', 'green').style('opacity', 0.3).attr('clip-path', 'url(#clip-
|
|
1020
|
+
svg.append('path').attr('id', strID).attr('d', daPathString).style('fill', 'none').style('stroke-width', 1).style('stroke', 'green').style('opacity', 0.3).attr('clip-path', 'url(#clip-BIJVOETd3)');
|
|
985
1021
|
}
|
|
986
1022
|
};
|
|
987
1023
|
|
|
@@ -1008,7 +1044,8 @@ const drawIsohumes = ({
|
|
|
1008
1044
|
height,
|
|
1009
1045
|
xScale,
|
|
1010
1046
|
yScale,
|
|
1011
|
-
svg
|
|
1047
|
+
svg,
|
|
1048
|
+
diagramType
|
|
1012
1049
|
}) => {
|
|
1013
1050
|
for (const isohumeVal of isohumeValues) {
|
|
1014
1051
|
const Px = [];
|
|
@@ -1018,14 +1055,16 @@ const drawIsohumes = ({
|
|
|
1018
1055
|
const temp = 237.3 * logthing / (17.269 - logthing);
|
|
1019
1056
|
Px.push([xScale(temp), yScale(pressureVal)]);
|
|
1020
1057
|
}
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1058
|
+
if (diagramType === 'SKEWT') {
|
|
1059
|
+
// Skew/shear transformation for skew-t
|
|
1060
|
+
for (const PxElement of Px) {
|
|
1061
|
+
PxElement[0] = PxElement[0] + height - PxElement[1];
|
|
1062
|
+
}
|
|
1024
1063
|
}
|
|
1025
1064
|
const lineGenerator = d3.line();
|
|
1026
1065
|
const pathStringQ = lineGenerator(Px);
|
|
1027
1066
|
const strID = `${isohumeVal}isohume`;
|
|
1028
|
-
svg.append('path').attr('id', strID).attr('d', pathStringQ).style('fill', 'none').style('stroke-width', 1).style('stroke-dasharray', '15, 8').style('stroke', '#5267A9').style('stroke-opacity', 0.5).attr('clip-path',
|
|
1067
|
+
svg.append('path').attr('id', strID).attr('d', pathStringQ).style('fill', 'none').style('stroke-width', 1).style('stroke-dasharray', '15, 8').style('stroke', '#5267A9').style('stroke-opacity', 0.5).attr('clip-path', `url(#clip-${diagramType}d3)`);
|
|
1029
1068
|
svg.append('text').text(isohumeVal).attr('text-anchor', 'middle').attr('x', `${Px[0][0] + 20}px`).attr('y', `${height - 10}px`).attr('fill', '#5267A9').attr('font-size', '10px');
|
|
1030
1069
|
}
|
|
1031
1070
|
};
|
|
@@ -1054,7 +1093,7 @@ const drawIsohumesBijvoet = ({
|
|
|
1054
1093
|
const lineGenerator = d3.line();
|
|
1055
1094
|
const pathStringQ = lineGenerator(Px);
|
|
1056
1095
|
const strID = `${isohumeVal}isohume`;
|
|
1057
|
-
svg.append('path').attr('id', strID).attr('d', pathStringQ).style('fill', 'none').style('stroke-width', 1).style('stroke-dasharray', '15, 8').style('stroke', '#5267A9').style('stroke-opacity', 0.5).attr('clip-path', 'url(#clip-
|
|
1096
|
+
svg.append('path').attr('id', strID).attr('d', pathStringQ).style('fill', 'none').style('stroke-width', 1).style('stroke-dasharray', '15, 8').style('stroke', '#5267A9').style('stroke-opacity', 0.5).attr('clip-path', 'url(#clip-BIJVOETd3)');
|
|
1058
1097
|
svg.append('text').text(isohumeVal).attr('text-anchor', 'middle').attr('x', `${Px[0][0] + 20}px`).attr('y', `${height - 10}px`).attr('fill', '#5267A9').attr('font-size', '10px');
|
|
1059
1098
|
}
|
|
1060
1099
|
};
|
|
@@ -1081,7 +1120,8 @@ const drawIsotherms = ({
|
|
|
1081
1120
|
height,
|
|
1082
1121
|
xScale,
|
|
1083
1122
|
yScale,
|
|
1084
|
-
svg
|
|
1123
|
+
svg,
|
|
1124
|
+
diagramType
|
|
1085
1125
|
}) => {
|
|
1086
1126
|
// lines of constant temperature (10 degree interval from -80...+40C)
|
|
1087
1127
|
for (let temp = -80; temp <= 40; temp += 10) {
|
|
@@ -1089,12 +1129,11 @@ const drawIsotherms = ({
|
|
|
1089
1129
|
for (const pressure of staticPressureArray) {
|
|
1090
1130
|
const Ppx = yScale(pressure);
|
|
1091
1131
|
const Tpx = xScale(temp);
|
|
1092
|
-
|
|
1093
|
-
points.push([skewedX, Ppx]);
|
|
1132
|
+
points.push([diagramType === 'SKEWT' ? Tpx + (height - Ppx) : Tpx, Ppx]);
|
|
1094
1133
|
}
|
|
1095
1134
|
const lineGenerator = d3.line();
|
|
1096
1135
|
const pathString = lineGenerator(points);
|
|
1097
|
-
svg.append('path').attr('d', pathString).style('fill', 'none').style('stroke-width', 1).style('stroke', 'orange').style('stroke-opacity', 0.5).attr('clip-path',
|
|
1136
|
+
svg.append('path').attr('d', pathString).style('fill', 'none').style('stroke-width', 1).style('stroke', 'orange').style('stroke-opacity', 0.5).attr('clip-path', `url(#clip-${diagramType}d3)`);
|
|
1098
1137
|
}
|
|
1099
1138
|
};
|
|
1100
1139
|
const drawIsothermsBijvoet = ({
|
|
@@ -1115,8 +1154,8 @@ const drawIsothermsBijvoet = ({
|
|
|
1115
1154
|
}
|
|
1116
1155
|
const lineGenerator = d3.line();
|
|
1117
1156
|
const pathString = lineGenerator(points);
|
|
1118
|
-
const strID = `${temp}-isotherm-
|
|
1119
|
-
svg.append('path').attr('id', strID).attr('d', pathString).style('fill', 'none').style('stroke-width', temp % 10 === 0 ? 2 : 1).style('stroke', temp === 0 ? '#D27D2D' : 'orange').style('stroke-opacity', 0.5).attr('clip-path', 'url(#clip-
|
|
1157
|
+
const strID = `${temp}-isotherm-BIJVOET`;
|
|
1158
|
+
svg.append('path').attr('id', strID).attr('d', pathString).style('fill', 'none').style('stroke-width', temp % 10 === 0 ? 2 : 1).style('stroke', temp === 0 ? '#D27D2D' : 'orange').style('stroke-opacity', 0.5).attr('clip-path', 'url(#clip-BIJVOETd3)');
|
|
1120
1159
|
// only show value every 10 degrees and if isotherm reaches bottom of plot
|
|
1121
1160
|
if (temp % 10 === 0 && points[0] && points[0][1] === height) {
|
|
1122
1161
|
svg.append('text').text(temp).attr('text-anchor', 'middle').attr('x', `${points[0][0]}px`).attr('y', `${height + 12}px`).attr('fill', temp === 0 ? '#D27D2D' : 'orange').attr('font-size', '10px');
|
|
@@ -1145,7 +1184,7 @@ const CP = 1.03e3;
|
|
|
1145
1184
|
const L_CONST = 2.5e6;
|
|
1146
1185
|
const RD = 287.0;
|
|
1147
1186
|
const RV = 461.0;
|
|
1148
|
-
const moistAdiabaticLapserate = (
|
|
1187
|
+
const moistAdiabaticLapserate = (pressure, temperature, dp) => {
|
|
1149
1188
|
const kelvin = temperature + KELVIN;
|
|
1150
1189
|
const lsbc = L_CONST / RV * (1.0 / KELVIN - 1.0 / kelvin);
|
|
1151
1190
|
const rw = 6.11 * Math.exp(lsbc) * (0.622 / pressure);
|
|
@@ -1167,7 +1206,8 @@ const drawMoistAdiabats = ({
|
|
|
1167
1206
|
xScale,
|
|
1168
1207
|
yScale,
|
|
1169
1208
|
svg,
|
|
1170
|
-
dp
|
|
1209
|
+
dp,
|
|
1210
|
+
diagramType
|
|
1171
1211
|
}) => {
|
|
1172
1212
|
/* We need the moist adiabat to cross the isotherm at 1000hPa
|
|
1173
1213
|
Therefore we start the calculations from 1000hPa and work upwards (towards 0)
|
|
@@ -1184,19 +1224,21 @@ const drawMoistAdiabats = ({
|
|
|
1184
1224
|
// Then calculate the rest of the moist adiabat for pressures <1000hPa
|
|
1185
1225
|
for (const pressure of staticPressureArray) {
|
|
1186
1226
|
if (pressure < 1000) {
|
|
1187
|
-
const DPDT = moistAdiabaticLapserate(
|
|
1227
|
+
const DPDT = moistAdiabaticLapserate(Pnew, Tnew, dp);
|
|
1188
1228
|
Tnew += DPDT[1];
|
|
1189
1229
|
Pnew += DPDT[0];
|
|
1190
1230
|
Px.push([xScale(Tnew), yScale(pressure)]);
|
|
1191
1231
|
}
|
|
1192
1232
|
}
|
|
1193
|
-
|
|
1194
|
-
|
|
1233
|
+
if (diagramType === 'SKEWT') {
|
|
1234
|
+
for (const PxElement of Px) {
|
|
1235
|
+
PxElement[0] = PxElement[0] + height - PxElement[1];
|
|
1236
|
+
}
|
|
1195
1237
|
}
|
|
1196
1238
|
const lineGenerator = d3.line();
|
|
1197
1239
|
const maPathString = lineGenerator(Px);
|
|
1198
1240
|
// Draw this first section of the line
|
|
1199
|
-
svg.append('path').attr('d', maPathString).style('fill', 'none').style('stroke-width', 1).style('stroke', 'purple').style('stroke-opacity', 0.5).attr('clip-path',
|
|
1241
|
+
svg.append('path').attr('d', maPathString).style('fill', 'none').style('stroke-width', 1).style('stroke', 'purple').style('stroke-opacity', 0.5).attr('clip-path', `url(#clip-${diagramType}d3)`);
|
|
1200
1242
|
// Now work backwards from 1000hPa to calculate most adiabat until the bottom of the plot
|
|
1201
1243
|
const PxBottom = [];
|
|
1202
1244
|
PxBottom.push([Tpx1000, Ppx1000]);
|
|
@@ -1204,17 +1246,19 @@ const drawMoistAdiabats = ({
|
|
|
1204
1246
|
let TnewBottom = Tbase;
|
|
1205
1247
|
const startIndex = staticPressureArray.findIndex(value => value === 1000);
|
|
1206
1248
|
for (let i = startIndex - 1; i >= 0; i -= 1) {
|
|
1207
|
-
const DPDT = moistAdiabaticLapserate(
|
|
1249
|
+
const DPDT = moistAdiabaticLapserate(PnewBottom, TnewBottom, 5);
|
|
1208
1250
|
TnewBottom += DPDT[1];
|
|
1209
1251
|
PnewBottom += DPDT[0];
|
|
1210
1252
|
PxBottom.push([xScale(TnewBottom), yScale(staticPressureArray[i])]);
|
|
1211
1253
|
}
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1254
|
+
if (diagramType === 'SKEWT') {
|
|
1255
|
+
// Skew/shear transformation for skew-t
|
|
1256
|
+
for (const PxElement of PxBottom) {
|
|
1257
|
+
PxElement[0] = PxElement[0] + height - PxElement[1];
|
|
1258
|
+
}
|
|
1215
1259
|
}
|
|
1216
1260
|
const maPathStringBottom = lineGenerator(PxBottom);
|
|
1217
|
-
svg.append('path').attr('d', maPathStringBottom).style('fill', 'none').style('stroke-width', 1).style('stroke', 'purple').style('stroke-opacity', 0.5).attr('clip-path',
|
|
1261
|
+
svg.append('path').attr('d', maPathStringBottom).style('fill', 'none').style('stroke-width', 1).style('stroke', 'purple').style('stroke-opacity', 0.5).attr('clip-path', `url(#clip-${diagramType}d3)`);
|
|
1218
1262
|
}
|
|
1219
1263
|
};
|
|
1220
1264
|
const drawMoistAdiabatsBijvoet = ({
|
|
@@ -1234,7 +1278,7 @@ const drawMoistAdiabatsBijvoet = ({
|
|
|
1234
1278
|
const lineGenerator = d3.line();
|
|
1235
1279
|
const isoPathString = lineGenerator(Px);
|
|
1236
1280
|
const strID = `${Tbase}-moistadiabat`;
|
|
1237
|
-
svg.append('path').attr('id', strID).attr('d', isoPathString).style('fill', 'none').style('stroke-width', 1).style('stroke', 'purple').style('stroke-opacity', 0.5).attr('clip-path', 'url(#clip-
|
|
1281
|
+
svg.append('path').attr('id', strID).attr('d', isoPathString).style('fill', 'none').style('stroke-width', 1).style('stroke', 'purple').style('stroke-opacity', 0.5).attr('clip-path', 'url(#clip-BIJVOETd3)');
|
|
1238
1282
|
}
|
|
1239
1283
|
};
|
|
1240
1284
|
|
|
@@ -1307,6 +1351,7 @@ const drawStaticLines = ({
|
|
|
1307
1351
|
});
|
|
1308
1352
|
} else {
|
|
1309
1353
|
drawIsotherms({
|
|
1354
|
+
diagramType,
|
|
1310
1355
|
staticPressureArray,
|
|
1311
1356
|
height,
|
|
1312
1357
|
xScale,
|
|
@@ -1314,6 +1359,7 @@ const drawStaticLines = ({
|
|
|
1314
1359
|
svg
|
|
1315
1360
|
});
|
|
1316
1361
|
drawMoistAdiabats({
|
|
1362
|
+
diagramType,
|
|
1317
1363
|
staticPressureArray,
|
|
1318
1364
|
minTemperature,
|
|
1319
1365
|
maxTemperature,
|
|
@@ -1327,6 +1373,7 @@ const drawStaticLines = ({
|
|
|
1327
1373
|
dp
|
|
1328
1374
|
});
|
|
1329
1375
|
drawDryAdiabats({
|
|
1376
|
+
diagramType,
|
|
1330
1377
|
staticPressureArray,
|
|
1331
1378
|
height,
|
|
1332
1379
|
xScale,
|
|
@@ -1335,6 +1382,7 @@ const drawStaticLines = ({
|
|
|
1335
1382
|
dp
|
|
1336
1383
|
});
|
|
1337
1384
|
drawIsohumes({
|
|
1385
|
+
diagramType,
|
|
1338
1386
|
staticPressureArray,
|
|
1339
1387
|
height,
|
|
1340
1388
|
xScale,
|
|
@@ -1389,7 +1437,7 @@ const Soundings = ({
|
|
|
1389
1437
|
width: svgDimensions?.width || 812,
|
|
1390
1438
|
height: svgDimensions?.height || 802,
|
|
1391
1439
|
margin: {
|
|
1392
|
-
top: svgDimensions?.margin?.top ||
|
|
1440
|
+
top: svgDimensions?.margin?.top || 4,
|
|
1393
1441
|
right: svgDimensions?.margin?.right || 20,
|
|
1394
1442
|
bottom: svgDimensions?.margin?.bottom || 25,
|
|
1395
1443
|
left: svgDimensions?.margin?.left || 20
|
|
@@ -1518,7 +1566,7 @@ const Soundings = ({
|
|
|
1518
1566
|
svg.on('mousemove', null);
|
|
1519
1567
|
};
|
|
1520
1568
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1521
|
-
}, [showTooltip, data]);
|
|
1569
|
+
}, [showTooltip, data, diagramType]);
|
|
1522
1570
|
useEffect(() => {
|
|
1523
1571
|
const handleMouseLeave = () => {
|
|
1524
1572
|
if (pressureRef.current) {
|
|
@@ -1559,7 +1607,7 @@ const Soundings = ({
|
|
|
1559
1607
|
windRef.current.style.display = 'none';
|
|
1560
1608
|
}
|
|
1561
1609
|
}, [data]);
|
|
1562
|
-
return jsxs(
|
|
1610
|
+
return jsxs(Box, {
|
|
1563
1611
|
ref: canvasRef,
|
|
1564
1612
|
sx: {
|
|
1565
1613
|
position: 'relative',
|
|
@@ -1902,8 +1950,8 @@ const constructQueryParams = (collectionId, baseUrl, instanceId, location, datet
|
|
|
1902
1950
|
return {
|
|
1903
1951
|
queryKey: ['edr-soundings-for-location', collectionId, instanceId || '', location, parameters.join(), datetime],
|
|
1904
1952
|
queryFn: async () => {
|
|
1905
|
-
// No collection or
|
|
1906
|
-
if (collectionId === undefined || location === '') {
|
|
1953
|
+
// No collection, location or time selected, don't do EDR call but return empty array
|
|
1954
|
+
if (collectionId === undefined || location === '' || datetime === '') {
|
|
1907
1955
|
return [];
|
|
1908
1956
|
}
|
|
1909
1957
|
try {
|
|
@@ -1920,8 +1968,8 @@ const constructQueryParams = (collectionId, baseUrl, instanceId, location, datet
|
|
|
1920
1968
|
return {
|
|
1921
1969
|
queryKey: ['edr-soundings-for-position', collectionId, location.join(), parameters.join(), datetime],
|
|
1922
1970
|
queryFn: async () => {
|
|
1923
|
-
// No collection or
|
|
1924
|
-
if (collectionId === undefined || location === undefined) {
|
|
1971
|
+
// No collection, position or time selected, don't do EDR call but return empty array
|
|
1972
|
+
if (collectionId === undefined || location === undefined || datetime === '') {
|
|
1925
1973
|
return [];
|
|
1926
1974
|
}
|
|
1927
1975
|
try {
|
|
@@ -1950,23 +1998,34 @@ const getParameterMappings = parameterMappings => {
|
|
|
1950
1998
|
return Object.keys(parameterMappings).map(key => parameterMappings[key]);
|
|
1951
1999
|
};
|
|
1952
2000
|
const useMultipleSources = (sources, locations, datetime) => {
|
|
1953
|
-
const
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
2001
|
+
const {
|
|
2002
|
+
data: timesteps,
|
|
2003
|
+
error: timestepsError,
|
|
2004
|
+
isPending: timestepsPending
|
|
2005
|
+
} = useTimesteps(sources);
|
|
2006
|
+
const queries = useMemo(() => {
|
|
2007
|
+
return sources.map(({
|
|
2008
|
+
collectionId,
|
|
2009
|
+
url,
|
|
2010
|
+
instanceId,
|
|
2011
|
+
parameterMappings
|
|
2012
|
+
}, index) => {
|
|
2013
|
+
// If acceptance time logic is needed, it could come here.
|
|
2014
|
+
const dateToUse = datetime &&
|
|
2015
|
+
// Only use selected time if exact match if found
|
|
2016
|
+
timesteps[index].includes(new Date(datetime).toISOString()) ? datetime : '';
|
|
2017
|
+
const queryParams = constructQueryParams(collectionId, url, instanceId, locations[collectionId] || '', dateToUse || '', parameterMappings || defaultParameterMappings);
|
|
2018
|
+
return queryParams;
|
|
2019
|
+
});
|
|
2020
|
+
}, [datetime, locations, sources, timesteps]);
|
|
1962
2021
|
return useQueries({
|
|
1963
2022
|
queries,
|
|
1964
2023
|
combine: queryResults => {
|
|
1965
2024
|
const data = queryResults.map(result => {
|
|
1966
2025
|
return result.data || [];
|
|
1967
2026
|
});
|
|
1968
|
-
const isPending = queryResults.some(result => result.isPending);
|
|
1969
|
-
const error = queryResults.find(result => result?.error)?.error;
|
|
2027
|
+
const isPending = timestepsPending || queryResults.some(result => result.isPending);
|
|
2028
|
+
const error = timestepsError || queryResults.find(result => result?.error)?.error;
|
|
1970
2029
|
return {
|
|
1971
2030
|
data,
|
|
1972
2031
|
isPending,
|
|
@@ -1994,8 +2053,18 @@ const useTimesteps = sourceSelections => {
|
|
|
1994
2053
|
const results = useQueries({
|
|
1995
2054
|
queries,
|
|
1996
2055
|
combine: queryResults => {
|
|
1997
|
-
const
|
|
1998
|
-
|
|
2056
|
+
const data = queryResults.map(result => result.data || []);
|
|
2057
|
+
// The underlying query is generic, so we rewrap the error to be more specific
|
|
2058
|
+
const foundError = queryResults.find(result => result?.error)?.error;
|
|
2059
|
+
const error = foundError ? new Error('Error fetching timesteps by collection', {
|
|
2060
|
+
cause: foundError
|
|
2061
|
+
}) : null;
|
|
2062
|
+
const isPending = queryResults.some(result => result.isPending);
|
|
2063
|
+
return {
|
|
2064
|
+
data,
|
|
2065
|
+
error,
|
|
2066
|
+
isPending
|
|
2067
|
+
};
|
|
1999
2068
|
}
|
|
2000
2069
|
});
|
|
2001
2070
|
return results;
|
|
@@ -2312,6 +2381,16 @@ const getSelectedSourcesIds = createSelector(getSelectedSources, activeSources =
|
|
|
2312
2381
|
return activeSources.map(source => source.collectionId);
|
|
2313
2382
|
}, selectorMemoizationOptions);
|
|
2314
2383
|
|
|
2384
|
+
const roundToClosestTimestep = (timeToRound, availableSteps) => {
|
|
2385
|
+
if (availableSteps.length > 0 && !availableSteps.includes(timeToRound)) {
|
|
2386
|
+
const selectionValue = dateUtils.unix(new Date(timeToRound));
|
|
2387
|
+
const closestTimestep = minBy(availableSteps, timeValue =>
|
|
2388
|
+
// distance to selected time
|
|
2389
|
+
Math.abs(selectionValue - dateUtils.unix(new Date(timeValue))));
|
|
2390
|
+
return closestTimestep || availableSteps[0];
|
|
2391
|
+
}
|
|
2392
|
+
return timeToRound;
|
|
2393
|
+
};
|
|
2315
2394
|
const SoundingsTimeSliderConnect = () => {
|
|
2316
2395
|
const selectedTime = useSelector(getSelectedTime);
|
|
2317
2396
|
const sourceSelections = useSelector(getSelectedSources);
|
|
@@ -2321,13 +2400,22 @@ const SoundingsTimeSliderConnect = () => {
|
|
|
2321
2400
|
selectedTime
|
|
2322
2401
|
}));
|
|
2323
2402
|
}, [dispatch]);
|
|
2324
|
-
const
|
|
2403
|
+
const {
|
|
2404
|
+
data: timesteps
|
|
2405
|
+
} = useTimesteps(sourceSelections);
|
|
2325
2406
|
const mergedTimesteps = Array.from(new Set(timesteps.flat())).sort();
|
|
2326
2407
|
// TODO: next line should work in Storybook but does not: https://gitlab.com/opengeoweb/geoweb-assets/-/issues/4025
|
|
2327
2408
|
// const mergedTimesteps = [...new Set(timestepArrays.flat())].sort();
|
|
2409
|
+
// Ensure that the selected time is always one of the available timesteps
|
|
2410
|
+
const validSelectedTime = roundToClosestTimestep(selectedTime, mergedTimesteps);
|
|
2411
|
+
useEffect(() => {
|
|
2412
|
+
if (selectedTime !== validSelectedTime) {
|
|
2413
|
+
updateSelectedTime(validSelectedTime);
|
|
2414
|
+
}
|
|
2415
|
+
}, [selectedTime, updateSelectedTime, validSelectedTime]);
|
|
2328
2416
|
return jsx(SoundingsTimeSlider, {
|
|
2329
2417
|
timesteps: mergedTimesteps,
|
|
2330
|
-
selectedTime:
|
|
2418
|
+
selectedTime: validSelectedTime,
|
|
2331
2419
|
updateSelectedTime: updateSelectedTime
|
|
2332
2420
|
});
|
|
2333
2421
|
};
|
|
@@ -2416,9 +2504,84 @@ const MenuBarConnect = ({
|
|
|
2416
2504
|
});
|
|
2417
2505
|
};
|
|
2418
2506
|
|
|
2507
|
+
const soundingDiagrams = ['SKEWT', 'BIJVOET', 'EMAGRAM'];
|
|
2508
|
+
|
|
2509
|
+
const DiagramTypeButton = ({
|
|
2510
|
+
diagramType,
|
|
2511
|
+
setDiagramType
|
|
2512
|
+
}) => {
|
|
2513
|
+
const {
|
|
2514
|
+
t
|
|
2515
|
+
} = useSoundingsTranslation();
|
|
2516
|
+
const [anchorEl, setAnchorEl] = React.useState(null);
|
|
2517
|
+
const isOpen = Boolean(anchorEl);
|
|
2518
|
+
const handleClick = event => {
|
|
2519
|
+
setAnchorEl(anchorEl ? null : event.currentTarget);
|
|
2520
|
+
};
|
|
2521
|
+
return jsxs(CustomToggleButton, {
|
|
2522
|
+
variant: "tool",
|
|
2523
|
+
selected: isOpen,
|
|
2524
|
+
onClick: handleClick,
|
|
2525
|
+
"data-testid": "soundings-diagramtype-options-toolbutton",
|
|
2526
|
+
disableRipple: true,
|
|
2527
|
+
size: "small",
|
|
2528
|
+
sx: {
|
|
2529
|
+
height: '24px !important',
|
|
2530
|
+
width: '75px'
|
|
2531
|
+
},
|
|
2532
|
+
role: "button",
|
|
2533
|
+
children: [getDiagramName(t, diagramType), jsxs(Menu, {
|
|
2534
|
+
autoFocus: false,
|
|
2535
|
+
slotProps: {
|
|
2536
|
+
backdrop: {
|
|
2537
|
+
style: {
|
|
2538
|
+
opacity: 0,
|
|
2539
|
+
transition: 'none'
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
},
|
|
2543
|
+
open: isOpen,
|
|
2544
|
+
anchorEl: anchorEl,
|
|
2545
|
+
anchorOrigin: {
|
|
2546
|
+
vertical: 21,
|
|
2547
|
+
horizontal: 6
|
|
2548
|
+
},
|
|
2549
|
+
sx: {
|
|
2550
|
+
'& .MuiMenu-list': {
|
|
2551
|
+
padding: 0
|
|
2552
|
+
},
|
|
2553
|
+
'& .MuiMenuItem-root': {
|
|
2554
|
+
fontSize: 16
|
|
2555
|
+
}
|
|
2556
|
+
},
|
|
2557
|
+
children: [jsx(ListSubheader, {
|
|
2558
|
+
sx: {
|
|
2559
|
+
paddingTop: '8px'
|
|
2560
|
+
},
|
|
2561
|
+
children: jsx(Typography, {
|
|
2562
|
+
sx: {
|
|
2563
|
+
letterSpacing: '0.4px',
|
|
2564
|
+
fontSize: 12
|
|
2565
|
+
},
|
|
2566
|
+
children: t('options')
|
|
2567
|
+
})
|
|
2568
|
+
}), soundingDiagrams.map(id => jsx(MenuItem, {
|
|
2569
|
+
onClick: () => {
|
|
2570
|
+
setDiagramType(id);
|
|
2571
|
+
},
|
|
2572
|
+
selected: diagramType === id,
|
|
2573
|
+
children: getDiagramName(t, id)
|
|
2574
|
+
}, id))]
|
|
2575
|
+
})]
|
|
2576
|
+
});
|
|
2577
|
+
};
|
|
2578
|
+
|
|
2579
|
+
const size = 600;
|
|
2419
2580
|
const SoundingsConnect = ({
|
|
2420
|
-
viewId
|
|
2581
|
+
viewId,
|
|
2582
|
+
diagramType = 'SKEWT'
|
|
2421
2583
|
}) => {
|
|
2584
|
+
const [chosenDiagramType, setChosenDiagramType] = React.useState(diagramType);
|
|
2422
2585
|
const selectedTime = useSelector(getSelectedTime);
|
|
2423
2586
|
const sourceSelections = useSelector(getSelectedSources);
|
|
2424
2587
|
const locationSelections = useSelector(getLocationSelections);
|
|
@@ -2448,18 +2611,33 @@ const SoundingsConnect = ({
|
|
|
2448
2611
|
children: jsx(MenuBarConnect, {
|
|
2449
2612
|
viewId: viewId
|
|
2450
2613
|
})
|
|
2451
|
-
}),
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2614
|
+
}), jsxs(Card, {
|
|
2615
|
+
children: [jsx(Box, {
|
|
2616
|
+
sx: {
|
|
2617
|
+
display: 'flex',
|
|
2618
|
+
justifyContent: 'flex-end',
|
|
2619
|
+
paddingTop: '2px',
|
|
2620
|
+
paddingRight: '2px',
|
|
2621
|
+
width: size
|
|
2622
|
+
},
|
|
2623
|
+
children: jsx(DiagramTypeButton, {
|
|
2624
|
+
diagramType: chosenDiagramType,
|
|
2625
|
+
setDiagramType: setChosenDiagramType
|
|
2626
|
+
})
|
|
2627
|
+
}), jsx(Soundings, {
|
|
2628
|
+
data: data,
|
|
2629
|
+
svgDimensions: {
|
|
2630
|
+
width: size,
|
|
2631
|
+
height: size,
|
|
2632
|
+
margin: {
|
|
2633
|
+
bottom: 0,
|
|
2634
|
+
left: 0,
|
|
2635
|
+
right: 0,
|
|
2636
|
+
top: 0
|
|
2637
|
+
}
|
|
2638
|
+
},
|
|
2639
|
+
diagramType: chosenDiagramType
|
|
2640
|
+
})]
|
|
2463
2641
|
}), jsx(SoundingsTimeSliderConnect, {})]
|
|
2464
2642
|
})]
|
|
2465
2643
|
});
|
|
@@ -2820,7 +2998,8 @@ const soundingsComponentType = 'Soundings';
|
|
|
2820
2998
|
const soundingsLookUp = payload => {
|
|
2821
2999
|
if (payload.componentType === soundingsComponentType) {
|
|
2822
3000
|
return jsx(SoundingsConnect, {
|
|
2823
|
-
viewId: payload.id
|
|
3001
|
+
viewId: payload.id,
|
|
3002
|
+
diagramType: payload.diagramType ? payload.diagramType : 'SKEWT'
|
|
2824
3003
|
});
|
|
2825
3004
|
}
|
|
2826
3005
|
return null;
|
|
@@ -2854,4 +3033,4 @@ const createMockStore = mockState => configureStore({
|
|
|
2854
3033
|
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(storeMiddlewares)
|
|
2855
3034
|
});
|
|
2856
3035
|
|
|
2857
|
-
export { LocationsMenuConnect, SOUNDINGS_NAMESPACE, SoundingsConnect, SoundingsSourceDialogConnect, createMockStore, initSoundingsI18n, soundingsComponentType, soundingsLookUp, soundingsReducersMap, soundingsTranslations, translateKeyOutsideComponents, useSoundingsTranslation };
|
|
3036
|
+
export { LocationsMenuConnect, SOUNDINGS_NAMESPACE, SoundingsConnect, SoundingsSourceDialogConnect, createMockStore, initSoundingsI18n, soundingDiagrams, soundingsComponentType, soundingsLookUp, soundingsReducersMap, soundingsTranslations, translateKeyOutsideComponents, useSoundingsTranslation };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeoweb/soundings",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.7.0",
|
|
4
4
|
"description": "GeoWeb soundings library for the opengeoweb project",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -8,21 +8,21 @@
|
|
|
8
8
|
"url": "git@gitlab.com:opengeoweb/opengeoweb.git"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@opengeoweb/api": "12.
|
|
11
|
+
"@opengeoweb/api": "12.7.0",
|
|
12
12
|
"@tanstack/react-query": "^5.69.2",
|
|
13
13
|
"react": "^18.3.1",
|
|
14
14
|
"d3": "^7.9.0",
|
|
15
15
|
"react-i18next": "^15.1.1",
|
|
16
16
|
"i18next": "^25.0.1",
|
|
17
|
-
"@opengeoweb/shared": "12.
|
|
17
|
+
"@opengeoweb/shared": "12.7.0",
|
|
18
18
|
"@mui/material": "^7.0.1",
|
|
19
|
-
"@opengeoweb/store": "12.
|
|
19
|
+
"@opengeoweb/store": "12.7.0",
|
|
20
20
|
"react-redux": "^9.2.0",
|
|
21
|
-
"@opengeoweb/theme": "12.
|
|
21
|
+
"@opengeoweb/theme": "12.7.0",
|
|
22
22
|
"@reduxjs/toolkit": "^2.6.1",
|
|
23
|
-
"@opengeoweb/timeslider": "12.
|
|
23
|
+
"@opengeoweb/timeslider": "12.7.0",
|
|
24
24
|
"react-window": "^1.8.10",
|
|
25
|
-
"@opengeoweb/webmap": "12.
|
|
25
|
+
"@opengeoweb/webmap": "12.7.0",
|
|
26
26
|
"lodash": "^4.17.21"
|
|
27
27
|
},
|
|
28
28
|
"module": "./index.esm.js",
|
|
@@ -22,7 +22,11 @@ export declare const useMultipleSources: (sources: SourceType[], locations: Reco
|
|
|
22
22
|
isPending: boolean;
|
|
23
23
|
error: Error | null | undefined;
|
|
24
24
|
};
|
|
25
|
-
export declare const useTimesteps: (sourceSelections: SourceType[]) =>
|
|
25
|
+
export declare const useTimesteps: (sourceSelections: SourceType[]) => {
|
|
26
|
+
data: string[][];
|
|
27
|
+
error: Error | null;
|
|
28
|
+
isPending: boolean;
|
|
29
|
+
};
|
|
26
30
|
export declare const useGetCollectionsInstances: (collections?: SoundingsCollection[], isDialogOpen?: boolean) => {
|
|
27
31
|
data: SoundingsCollectionsWithInstances[] | undefined;
|
|
28
32
|
isPending: boolean;
|
|
@@ -31,4 +31,5 @@ export type SoundingsInstance = EDRInstance;
|
|
|
31
31
|
export interface SoundingsInstances {
|
|
32
32
|
instances: SoundingsInstance[];
|
|
33
33
|
}
|
|
34
|
-
export
|
|
34
|
+
export declare const soundingDiagrams: readonly ["SKEWT", "BIJVOET", "EMAGRAM"];
|
|
35
|
+
export type SoundingDiagramType = (typeof soundingDiagrams)[number];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { SoundingDiagramType } from '../../api/edr-api/types';
|
|
3
|
+
interface DiagramTypeButtonProps {
|
|
4
|
+
diagramType: SoundingDiagramType;
|
|
5
|
+
setDiagramType: (diagramType: SoundingDiagramType) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare const DiagramTypeButton: ({ diagramType, setDiagramType, }: DiagramTypeButtonProps) => React.ReactElement;
|
|
8
|
+
export {};
|
|
@@ -3,7 +3,9 @@ import { Soundings } from './Soundings';
|
|
|
3
3
|
declare const meta: Meta<typeof Soundings>;
|
|
4
4
|
export default meta;
|
|
5
5
|
type Story = StoryObj<typeof Soundings>;
|
|
6
|
-
export declare const
|
|
7
|
-
export declare const
|
|
6
|
+
export declare const SoundingsSkewTExample: Story;
|
|
7
|
+
export declare const SoundingsSkewTExampleDark: Story;
|
|
8
|
+
export declare const SoundingsEmagramExample: Story;
|
|
9
|
+
export declare const SoundingsEmagramExampleDark: Story;
|
|
8
10
|
export declare const SoundingsBijvoetExample: Story;
|
|
9
11
|
export declare const SoundingsBijvoetExampleDark: Story;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { SoundingDiagramType } from '../../api/edr-api/types';
|
|
2
3
|
interface SoundingsConnectProps {
|
|
3
4
|
viewId: string;
|
|
5
|
+
diagramType?: SoundingDiagramType;
|
|
4
6
|
}
|
|
5
|
-
export declare const SoundingsConnect: ({ viewId, }: SoundingsConnectProps) => React.ReactElement;
|
|
7
|
+
export declare const SoundingsConnect: ({ viewId, diagramType, }: SoundingsConnectProps) => React.ReactElement;
|
|
6
8
|
export {};
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import { StoryObj } from '@storybook/react/*';
|
|
2
|
+
import { SoundingsConnect } from '..';
|
|
1
3
|
declare const _default: {
|
|
2
4
|
title: string;
|
|
3
5
|
tags: string[];
|
|
4
6
|
};
|
|
5
7
|
export default _default;
|
|
8
|
+
type Story = StoryObj<typeof SoundingsConnect>;
|
|
9
|
+
export declare const SoundingsConnectStory: Story;
|
|
10
|
+
export declare const SoundingsConnectStoryDark: Story;
|
|
6
11
|
export declare const SoundingsConnectLoading: {
|
|
7
12
|
(): React.ReactElement;
|
|
8
13
|
parameters: {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import * as d3 from 'd3';
|
|
3
|
+
import { i18n, TFunction } from 'i18next';
|
|
3
4
|
import { SoundingDiagramType } from '../../api/edr-api/types';
|
|
4
5
|
import { ScalesType } from './types';
|
|
5
6
|
export declare const SOUNDING_COLORS: string[];
|
|
@@ -38,6 +39,7 @@ interface PlotSounding {
|
|
|
38
39
|
svg: d3.Selection<SVGElement, unknown, HTMLElement, undefined>;
|
|
39
40
|
diagramType: SoundingDiagramType;
|
|
40
41
|
}
|
|
42
|
+
export declare const getDiagramName: (t: TFunction<'soundings', i18n>, diagramType: SoundingDiagramType) => string;
|
|
41
43
|
export declare const initializeSvg: ({ svgRef, width, height, margin, diagramType, }: InitializeSvg) => d3.Selection<SVGGElement, unknown, null, undefined>;
|
|
42
44
|
export declare const createScales: (diagramType: SoundingDiagramType, width: number, height: number, minPressure: number, maxPressure: number, minTemperature: number, maxTemperature: number) => [ScalesType, ScalesType];
|
|
43
45
|
export declare const drawAxes: ({ svg, xScale, yScale, height, diagramType, }: DrawAxis) => void;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as d3 from 'd3';
|
|
2
2
|
import { ScalesType } from '../Soundings/types';
|
|
3
|
+
import { SoundingDiagramType } from '../../api/edr-api/types';
|
|
3
4
|
interface DrawDryAdiabatsProps {
|
|
4
5
|
staticPressureArray: number[];
|
|
5
6
|
height: number;
|
|
@@ -7,8 +8,9 @@ interface DrawDryAdiabatsProps {
|
|
|
7
8
|
yScale: ScalesType;
|
|
8
9
|
dp: number;
|
|
9
10
|
svg: d3.Selection<SVGElement, unknown, HTMLElement, unknown>;
|
|
11
|
+
diagramType: SoundingDiagramType;
|
|
10
12
|
}
|
|
11
|
-
export declare const drawDryAdiabats: ({ staticPressureArray, height, xScale, yScale, svg, dp, }: DrawDryAdiabatsProps) => void;
|
|
13
|
+
export declare const drawDryAdiabats: ({ staticPressureArray, height, xScale, yScale, svg, dp, diagramType, }: DrawDryAdiabatsProps) => void;
|
|
12
14
|
interface DrawDryAdiabatsBijvoetProps {
|
|
13
15
|
staticPressureArray: number[];
|
|
14
16
|
xScale: ScalesType;
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import * as d3 from 'd3';
|
|
2
2
|
import { ScalesType } from '../Soundings/types';
|
|
3
|
+
import { SoundingDiagramType } from '../../api/edr-api/types';
|
|
3
4
|
interface DrawIsohumesProps {
|
|
4
5
|
staticPressureArray: number[];
|
|
5
6
|
height: number;
|
|
6
7
|
xScale: ScalesType;
|
|
7
8
|
yScale: ScalesType;
|
|
8
9
|
svg: d3.Selection<SVGElement, unknown, HTMLElement, unknown>;
|
|
10
|
+
diagramType: SoundingDiagramType;
|
|
9
11
|
}
|
|
10
|
-
export declare const drawIsohumes: ({ staticPressureArray, height, xScale, yScale, svg, }: DrawIsohumesProps) => void;
|
|
12
|
+
export declare const drawIsohumes: ({ staticPressureArray, height, xScale, yScale, svg, diagramType, }: DrawIsohumesProps) => void;
|
|
11
13
|
interface DrawIsohumesBijvoetProps {
|
|
12
14
|
staticPressureArray: number[];
|
|
13
15
|
height: number;
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import * as d3 from 'd3';
|
|
2
2
|
import { ScalesType } from '../Soundings/types';
|
|
3
|
+
import { SoundingDiagramType } from '../../api/edr-api/types';
|
|
3
4
|
interface DrawIsothermsProps {
|
|
4
5
|
staticPressureArray: number[];
|
|
5
6
|
height: number;
|
|
6
7
|
xScale: ScalesType;
|
|
7
8
|
yScale: ScalesType;
|
|
8
9
|
svg: d3.Selection<SVGElement, unknown, HTMLElement, unknown>;
|
|
10
|
+
diagramType: SoundingDiagramType;
|
|
9
11
|
}
|
|
10
|
-
export declare const drawIsotherms: ({ staticPressureArray, height, xScale, yScale, svg, }: DrawIsothermsProps) => void;
|
|
12
|
+
export declare const drawIsotherms: ({ staticPressureArray, height, xScale, yScale, svg, diagramType, }: DrawIsothermsProps) => void;
|
|
11
13
|
interface DrawIsothermsBijvoetProps {
|
|
12
14
|
staticPressureArray: number[];
|
|
13
15
|
xScale: ScalesType;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as d3 from 'd3';
|
|
2
2
|
import { ScalesType } from '../Soundings/types';
|
|
3
|
+
import { SoundingDiagramType } from '../../api/edr-api/types';
|
|
3
4
|
interface DrawMoistAdiabatsProps {
|
|
4
5
|
staticPressureArray: number[];
|
|
5
6
|
minTemperature: number;
|
|
@@ -12,8 +13,9 @@ interface DrawMoistAdiabatsProps {
|
|
|
12
13
|
xScale: ScalesType;
|
|
13
14
|
yScale: ScalesType;
|
|
14
15
|
svg: d3.Selection<SVGElement, unknown, HTMLElement, unknown>;
|
|
16
|
+
diagramType: SoundingDiagramType;
|
|
15
17
|
}
|
|
16
|
-
export declare const drawMoistAdiabats: ({ staticPressureArray, minTemperature, maxTemperature, minPressure, maxPressure, width, height, xScale, yScale, svg, dp, }: DrawMoistAdiabatsProps) => void;
|
|
18
|
+
export declare const drawMoistAdiabats: ({ staticPressureArray, minTemperature, maxTemperature, minPressure, maxPressure, width, height, xScale, yScale, svg, dp, diagramType, }: DrawMoistAdiabatsProps) => void;
|
|
17
19
|
interface DrawMoistAdiabatsBijvoetProps {
|
|
18
20
|
minPressure: number;
|
|
19
21
|
maxPressure: number;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { SoundingDiagramType } from '../api/edr-api/types';
|
|
1
2
|
export declare const soundingsComponentType = "Soundings";
|
|
2
3
|
export interface SoundingsLookUpPayload {
|
|
3
4
|
componentType: typeof soundingsComponentType;
|
|
4
5
|
id: string;
|
|
5
6
|
title?: string;
|
|
7
|
+
diagramType?: SoundingDiagramType;
|
|
6
8
|
initialProps: Record<string, unknown>;
|
|
7
9
|
}
|
|
8
10
|
export declare const soundingsLookUp: (payload: SoundingsLookUpPayload) => React.JSX.Element | null;
|