@opengeoweb/soundings 12.5.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 +494 -177
- package/package.json +9 -7
- package/src/lib/api/edr-api/api.d.ts +3 -2
- package/src/lib/api/edr-api/hooks.d.ts +5 -0
- package/src/lib/api/edr-api/types.d.ts +4 -65
- package/src/lib/api/edr-api/utils.d.ts +9 -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/SoundingsTimeSlider/SoundingsTimeSlider.stories.d.ts +1 -0
- package/src/lib/components/SoundingsTimeSlider/SoundingsTimeSliderConnect.d.ts +1 -5
- 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/store/types.d.ts +1 -1
- package/src/lib/utils/soundingsLookUp.d.ts +2 -0
- package/src/lib/api/edr-api/crss-types.d.ts +0 -26
package/index.esm.js
CHANGED
|
@@ -1,11 +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
|
-
import { useQueries
|
|
7
|
+
import { useQueries } from '@tanstack/react-query';
|
|
8
8
|
import { getHeaders, handleResponse } from '@opengeoweb/api';
|
|
9
|
+
import { ParseISOTimeRangeDuration, parseISO8601IntervalToDateInterval } from '@opengeoweb/webmap';
|
|
10
|
+
import { range, minBy } from 'lodash';
|
|
9
11
|
import { timesliderTranslations, TIMESLIDER_NAMESPACE, BackwardForwardStepButton } from '@opengeoweb/timeslider';
|
|
10
12
|
import i18n from 'i18next';
|
|
11
13
|
export { default as i18n } from 'i18next';
|
|
@@ -21,7 +23,11 @@ var en = {
|
|
|
21
23
|
noInstancesAvailable: "No instances found",
|
|
22
24
|
noServicesFound: "No source available",
|
|
23
25
|
locations: "Locations",
|
|
24
|
-
"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"
|
|
25
31
|
};
|
|
26
32
|
var no = {
|
|
27
33
|
sources: "Kilder",
|
|
@@ -29,7 +35,11 @@ var no = {
|
|
|
29
35
|
noInstancesAvailable: "Ingen instanser funnet",
|
|
30
36
|
noServicesFound: "Ingen tjenester funnet",
|
|
31
37
|
locations: "Lokasjoner",
|
|
32
|
-
"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"
|
|
33
43
|
};
|
|
34
44
|
var nl = {
|
|
35
45
|
sources: "Bronnen",
|
|
@@ -37,7 +47,11 @@ var nl = {
|
|
|
37
47
|
noInstancesAvailable: "Geen exemplaren gevonden",
|
|
38
48
|
noServicesFound: "Geen bron beschikbaar",
|
|
39
49
|
locations: "Locaties",
|
|
40
|
-
"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"
|
|
41
55
|
};
|
|
42
56
|
var fi = {
|
|
43
57
|
sources: "Lähteet",
|
|
@@ -45,7 +59,11 @@ var fi = {
|
|
|
45
59
|
noInstancesAvailable: "Tapauksia ei löytynyt",
|
|
46
60
|
noServicesFound: "Lähdettä ei ole saatavilla",
|
|
47
61
|
locations: "Locations",
|
|
48
|
-
"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"
|
|
49
67
|
};
|
|
50
68
|
var soundingsTranslations = {
|
|
51
69
|
en: en,
|
|
@@ -156,7 +174,7 @@ const calculateThetaFromPressTemp = (pressure, temperature, basePressure) => {
|
|
|
156
174
|
const pstep = (base - pressure) / numberOfSteps;
|
|
157
175
|
let tempN = temperature;
|
|
158
176
|
let pressureN = pressure;
|
|
159
|
-
for (let
|
|
177
|
+
for (let step = 0; step < numberOfSteps; step += 1) {
|
|
160
178
|
const k1 = pstep * dTdp(pressureN, tempN);
|
|
161
179
|
const k2 = pstep * dTdp(pressureN + 0.5 * pstep, tempN + 0.5 * k1);
|
|
162
180
|
const k3 = pstep * dTdp(pressureN + 0.5 * pstep, tempN + 0.5 * k2);
|
|
@@ -227,6 +245,22 @@ const drawIsobars = ({
|
|
|
227
245
|
* Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
|
|
228
246
|
* */
|
|
229
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
|
+
};
|
|
230
264
|
// vfx code
|
|
231
265
|
const initializeSvg = ({
|
|
232
266
|
svgRef,
|
|
@@ -235,10 +269,9 @@ const initializeSvg = ({
|
|
|
235
269
|
margin,
|
|
236
270
|
diagramType
|
|
237
271
|
}) => {
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
svg.append('
|
|
241
|
-
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)`);
|
|
242
275
|
return svg;
|
|
243
276
|
};
|
|
244
277
|
const createScales = (diagramType, width, height, minPressure, maxPressure, minTemperature, maxTemperature) => {
|
|
@@ -269,9 +302,9 @@ const drawAxes = ({
|
|
|
269
302
|
svg.append('g').attr('type', 'x-axis').attr('transform', `translate(0,${height})`).call(d3.axisBottom(xScale).tickValues(xTicks).tickSizeInner(0).tickSizeOuter(0));
|
|
270
303
|
// Reformat in the case of the middle tick label
|
|
271
304
|
// to add the unit part
|
|
272
|
-
svg.selectAll('g[type="x-axis"] .tick').filter(function () {
|
|
305
|
+
svg.selectAll('g[type="x-axis"] .tick').filter(function isMiddle() {
|
|
273
306
|
return parseInt(d3.select(this).select('text').text(), 10) === xMiddleValue;
|
|
274
|
-
}).each(function () {
|
|
307
|
+
}).each(function txt() {
|
|
275
308
|
const unitPart = '°C';
|
|
276
309
|
d3.select(this).select('text')
|
|
277
310
|
// tspan necessary to use a different font-size
|
|
@@ -281,7 +314,7 @@ const drawAxes = ({
|
|
|
281
314
|
// Create y-axis with intervals
|
|
282
315
|
// Make sure all zeroes are printed (1000 vs 1k)
|
|
283
316
|
const formatTicks = d3.format('.0f');
|
|
284
|
-
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');
|
|
285
318
|
};
|
|
286
319
|
const addLegends = ({
|
|
287
320
|
svg,
|
|
@@ -298,6 +331,9 @@ const mapToPlot = (diagramType, filteredPressureValues, filteredTemperatureValue
|
|
|
298
331
|
return d3.zip(filteredPressureValues, thetaValues).map(([pressure, theta]) => [xScale(theta), yScale(pressure)]);
|
|
299
332
|
}
|
|
300
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
|
+
}
|
|
301
337
|
// Skew/shear transformation for skew-t
|
|
302
338
|
return Px.map(([x, y]) => [x + height - y, y]);
|
|
303
339
|
};
|
|
@@ -312,17 +348,16 @@ const plotSounding = ({
|
|
|
312
348
|
svg,
|
|
313
349
|
diagramType
|
|
314
350
|
}) => {
|
|
315
|
-
const diagram = diagramType === 'BIJVOET' ? 'bijvoet' : 'skewT';
|
|
316
351
|
const [filteredPressureValues, filteredTemperatureValues] = filterDomainRangeForParameter(pressureValues, temperatureValues);
|
|
317
352
|
// Map temp and pressure to pixels
|
|
318
353
|
const Px = mapToPlot(diagramType, filteredPressureValues, filteredTemperatureValues, xScale, yScale, height);
|
|
319
354
|
const lineGenerator = d3.line();
|
|
320
355
|
const pathString = lineGenerator(Px);
|
|
321
|
-
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)`);
|
|
322
357
|
};
|
|
323
358
|
// Use this function to filter the pressure and parameter array so no NaN or null values are included
|
|
324
359
|
const filterDomainRangeForParameter = (pressureDomain, parameterDomain) => {
|
|
325
|
-
const filteredPressureDomain = pressureDomain.filter((
|
|
360
|
+
const filteredPressureDomain = pressureDomain.filter((_value, index) => !isNaN(parameterDomain[index]) && parameterDomain[index] !== null);
|
|
326
361
|
const filteredParameterDomain = parameterDomain.filter(value => !isNaN(value) && value !== null);
|
|
327
362
|
return [filteredPressureDomain, filteredParameterDomain];
|
|
328
363
|
};
|
|
@@ -828,16 +863,16 @@ const drawWindBarbs = ({
|
|
|
828
863
|
return;
|
|
829
864
|
}
|
|
830
865
|
// Add extra width to all isobars needed for the windbarbs
|
|
831
|
-
d3.selectAll('path[type="isobar"]').each(
|
|
832
|
-
const path = d3.select(
|
|
866
|
+
d3.selectAll('path[type="isobar"]').each((_d, i, nodes) => {
|
|
867
|
+
const path = d3.select(nodes[i]).attr('d');
|
|
833
868
|
const newD = extendIsoBar(path, horizontalBarbMargin);
|
|
834
|
-
d3.select(
|
|
869
|
+
d3.select(nodes[i]).attr('d', newD).attr('clip-path', null);
|
|
835
870
|
});
|
|
836
871
|
// Add extra width to x-axis needed for the windbarbs
|
|
837
|
-
d3.select('g[type="x-axis"] path.domain').each(
|
|
838
|
-
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');
|
|
839
874
|
const newD = extendXAxis(path, horizontalBarbMargin);
|
|
840
|
-
d3.select(
|
|
875
|
+
d3.select(nodes[i]).attr('d', newD);
|
|
841
876
|
});
|
|
842
877
|
svg.attr('width', currentWidth + margin.left + horizontalBarbMargin);
|
|
843
878
|
const barbGroup = svg.append('g').attr('class', 'wind-group').attr('transform',
|
|
@@ -855,8 +890,8 @@ const drawWindBarbs = ({
|
|
|
855
890
|
innerGroup.selectAll('*').attr('stroke', barbColor);
|
|
856
891
|
// Only update the fill for the paths that already used a fill
|
|
857
892
|
// in their original svg form.
|
|
858
|
-
innerGroup.selectAll('*').each(
|
|
859
|
-
const el = d3.select(
|
|
893
|
+
innerGroup.selectAll('*').each((_d, i, nodes) => {
|
|
894
|
+
const el = d3.select(nodes[i]);
|
|
860
895
|
const currentFill = el.attr('fill');
|
|
861
896
|
if (currentFill && currentFill !== 'none') {
|
|
862
897
|
el.attr('fill', barbColor);
|
|
@@ -936,7 +971,8 @@ const drawDryAdiabats = ({
|
|
|
936
971
|
xScale,
|
|
937
972
|
yScale,
|
|
938
973
|
svg,
|
|
939
|
-
dp
|
|
974
|
+
dp,
|
|
975
|
+
diagramType
|
|
940
976
|
}) => {
|
|
941
977
|
for (let Tbase = -40; Tbase < 240; Tbase += 10) {
|
|
942
978
|
let Pnew = staticPressureArray[0];
|
|
@@ -948,13 +984,15 @@ const drawDryAdiabats = ({
|
|
|
948
984
|
Pnew += DPDT[0];
|
|
949
985
|
Px.push([xScale(Tnew), yScale(pressure)]);
|
|
950
986
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
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
|
+
}
|
|
954
992
|
}
|
|
955
993
|
const lineGenerator = d3.line();
|
|
956
994
|
const daPathString = lineGenerator(Px);
|
|
957
|
-
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)`);
|
|
958
996
|
}
|
|
959
997
|
};
|
|
960
998
|
const tempPresDryAiabat = (pressure, pres0, temp0) => temp0 * (pressure / pres0) ** (2.0 / 7.0);
|
|
@@ -979,7 +1017,7 @@ const drawDryAdiabatsBijvoet = ({
|
|
|
979
1017
|
const lineGenerator = d3.line();
|
|
980
1018
|
const daPathString = lineGenerator(Px);
|
|
981
1019
|
const strID = `${Tbase}-dryadiabat`;
|
|
982
|
-
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)');
|
|
983
1021
|
}
|
|
984
1022
|
};
|
|
985
1023
|
|
|
@@ -1006,7 +1044,8 @@ const drawIsohumes = ({
|
|
|
1006
1044
|
height,
|
|
1007
1045
|
xScale,
|
|
1008
1046
|
yScale,
|
|
1009
|
-
svg
|
|
1047
|
+
svg,
|
|
1048
|
+
diagramType
|
|
1010
1049
|
}) => {
|
|
1011
1050
|
for (const isohumeVal of isohumeValues) {
|
|
1012
1051
|
const Px = [];
|
|
@@ -1016,14 +1055,16 @@ const drawIsohumes = ({
|
|
|
1016
1055
|
const temp = 237.3 * logthing / (17.269 - logthing);
|
|
1017
1056
|
Px.push([xScale(temp), yScale(pressureVal)]);
|
|
1018
1057
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
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
|
+
}
|
|
1022
1063
|
}
|
|
1023
1064
|
const lineGenerator = d3.line();
|
|
1024
1065
|
const pathStringQ = lineGenerator(Px);
|
|
1025
1066
|
const strID = `${isohumeVal}isohume`;
|
|
1026
|
-
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)`);
|
|
1027
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');
|
|
1028
1069
|
}
|
|
1029
1070
|
};
|
|
@@ -1052,7 +1093,7 @@ const drawIsohumesBijvoet = ({
|
|
|
1052
1093
|
const lineGenerator = d3.line();
|
|
1053
1094
|
const pathStringQ = lineGenerator(Px);
|
|
1054
1095
|
const strID = `${isohumeVal}isohume`;
|
|
1055
|
-
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)');
|
|
1056
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');
|
|
1057
1098
|
}
|
|
1058
1099
|
};
|
|
@@ -1079,7 +1120,8 @@ const drawIsotherms = ({
|
|
|
1079
1120
|
height,
|
|
1080
1121
|
xScale,
|
|
1081
1122
|
yScale,
|
|
1082
|
-
svg
|
|
1123
|
+
svg,
|
|
1124
|
+
diagramType
|
|
1083
1125
|
}) => {
|
|
1084
1126
|
// lines of constant temperature (10 degree interval from -80...+40C)
|
|
1085
1127
|
for (let temp = -80; temp <= 40; temp += 10) {
|
|
@@ -1087,12 +1129,11 @@ const drawIsotherms = ({
|
|
|
1087
1129
|
for (const pressure of staticPressureArray) {
|
|
1088
1130
|
const Ppx = yScale(pressure);
|
|
1089
1131
|
const Tpx = xScale(temp);
|
|
1090
|
-
|
|
1091
|
-
points.push([skewedX, Ppx]);
|
|
1132
|
+
points.push([diagramType === 'SKEWT' ? Tpx + (height - Ppx) : Tpx, Ppx]);
|
|
1092
1133
|
}
|
|
1093
1134
|
const lineGenerator = d3.line();
|
|
1094
1135
|
const pathString = lineGenerator(points);
|
|
1095
|
-
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)`);
|
|
1096
1137
|
}
|
|
1097
1138
|
};
|
|
1098
1139
|
const drawIsothermsBijvoet = ({
|
|
@@ -1113,8 +1154,8 @@ const drawIsothermsBijvoet = ({
|
|
|
1113
1154
|
}
|
|
1114
1155
|
const lineGenerator = d3.line();
|
|
1115
1156
|
const pathString = lineGenerator(points);
|
|
1116
|
-
const strID = `${temp}-isotherm-
|
|
1117
|
-
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)');
|
|
1118
1159
|
// only show value every 10 degrees and if isotherm reaches bottom of plot
|
|
1119
1160
|
if (temp % 10 === 0 && points[0] && points[0][1] === height) {
|
|
1120
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');
|
|
@@ -1143,7 +1184,7 @@ const CP = 1.03e3;
|
|
|
1143
1184
|
const L_CONST = 2.5e6;
|
|
1144
1185
|
const RD = 287.0;
|
|
1145
1186
|
const RV = 461.0;
|
|
1146
|
-
const moistAdiabaticLapserate = (
|
|
1187
|
+
const moistAdiabaticLapserate = (pressure, temperature, dp) => {
|
|
1147
1188
|
const kelvin = temperature + KELVIN;
|
|
1148
1189
|
const lsbc = L_CONST / RV * (1.0 / KELVIN - 1.0 / kelvin);
|
|
1149
1190
|
const rw = 6.11 * Math.exp(lsbc) * (0.622 / pressure);
|
|
@@ -1165,7 +1206,8 @@ const drawMoistAdiabats = ({
|
|
|
1165
1206
|
xScale,
|
|
1166
1207
|
yScale,
|
|
1167
1208
|
svg,
|
|
1168
|
-
dp
|
|
1209
|
+
dp,
|
|
1210
|
+
diagramType
|
|
1169
1211
|
}) => {
|
|
1170
1212
|
/* We need the moist adiabat to cross the isotherm at 1000hPa
|
|
1171
1213
|
Therefore we start the calculations from 1000hPa and work upwards (towards 0)
|
|
@@ -1182,19 +1224,21 @@ const drawMoistAdiabats = ({
|
|
|
1182
1224
|
// Then calculate the rest of the moist adiabat for pressures <1000hPa
|
|
1183
1225
|
for (const pressure of staticPressureArray) {
|
|
1184
1226
|
if (pressure < 1000) {
|
|
1185
|
-
const DPDT = moistAdiabaticLapserate(
|
|
1227
|
+
const DPDT = moistAdiabaticLapserate(Pnew, Tnew, dp);
|
|
1186
1228
|
Tnew += DPDT[1];
|
|
1187
1229
|
Pnew += DPDT[0];
|
|
1188
1230
|
Px.push([xScale(Tnew), yScale(pressure)]);
|
|
1189
1231
|
}
|
|
1190
1232
|
}
|
|
1191
|
-
|
|
1192
|
-
|
|
1233
|
+
if (diagramType === 'SKEWT') {
|
|
1234
|
+
for (const PxElement of Px) {
|
|
1235
|
+
PxElement[0] = PxElement[0] + height - PxElement[1];
|
|
1236
|
+
}
|
|
1193
1237
|
}
|
|
1194
1238
|
const lineGenerator = d3.line();
|
|
1195
1239
|
const maPathString = lineGenerator(Px);
|
|
1196
1240
|
// Draw this first section of the line
|
|
1197
|
-
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)`);
|
|
1198
1242
|
// Now work backwards from 1000hPa to calculate most adiabat until the bottom of the plot
|
|
1199
1243
|
const PxBottom = [];
|
|
1200
1244
|
PxBottom.push([Tpx1000, Ppx1000]);
|
|
@@ -1202,17 +1246,19 @@ const drawMoistAdiabats = ({
|
|
|
1202
1246
|
let TnewBottom = Tbase;
|
|
1203
1247
|
const startIndex = staticPressureArray.findIndex(value => value === 1000);
|
|
1204
1248
|
for (let i = startIndex - 1; i >= 0; i -= 1) {
|
|
1205
|
-
const DPDT = moistAdiabaticLapserate(
|
|
1249
|
+
const DPDT = moistAdiabaticLapserate(PnewBottom, TnewBottom, 5);
|
|
1206
1250
|
TnewBottom += DPDT[1];
|
|
1207
1251
|
PnewBottom += DPDT[0];
|
|
1208
1252
|
PxBottom.push([xScale(TnewBottom), yScale(staticPressureArray[i])]);
|
|
1209
1253
|
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
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
|
+
}
|
|
1213
1259
|
}
|
|
1214
1260
|
const maPathStringBottom = lineGenerator(PxBottom);
|
|
1215
|
-
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)`);
|
|
1216
1262
|
}
|
|
1217
1263
|
};
|
|
1218
1264
|
const drawMoistAdiabatsBijvoet = ({
|
|
@@ -1232,7 +1278,7 @@ const drawMoistAdiabatsBijvoet = ({
|
|
|
1232
1278
|
const lineGenerator = d3.line();
|
|
1233
1279
|
const isoPathString = lineGenerator(Px);
|
|
1234
1280
|
const strID = `${Tbase}-moistadiabat`;
|
|
1235
|
-
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)');
|
|
1236
1282
|
}
|
|
1237
1283
|
};
|
|
1238
1284
|
|
|
@@ -1305,6 +1351,7 @@ const drawStaticLines = ({
|
|
|
1305
1351
|
});
|
|
1306
1352
|
} else {
|
|
1307
1353
|
drawIsotherms({
|
|
1354
|
+
diagramType,
|
|
1308
1355
|
staticPressureArray,
|
|
1309
1356
|
height,
|
|
1310
1357
|
xScale,
|
|
@@ -1312,6 +1359,7 @@ const drawStaticLines = ({
|
|
|
1312
1359
|
svg
|
|
1313
1360
|
});
|
|
1314
1361
|
drawMoistAdiabats({
|
|
1362
|
+
diagramType,
|
|
1315
1363
|
staticPressureArray,
|
|
1316
1364
|
minTemperature,
|
|
1317
1365
|
maxTemperature,
|
|
@@ -1325,6 +1373,7 @@ const drawStaticLines = ({
|
|
|
1325
1373
|
dp
|
|
1326
1374
|
});
|
|
1327
1375
|
drawDryAdiabats({
|
|
1376
|
+
diagramType,
|
|
1328
1377
|
staticPressureArray,
|
|
1329
1378
|
height,
|
|
1330
1379
|
xScale,
|
|
@@ -1333,6 +1382,7 @@ const drawStaticLines = ({
|
|
|
1333
1382
|
dp
|
|
1334
1383
|
});
|
|
1335
1384
|
drawIsohumes({
|
|
1385
|
+
diagramType,
|
|
1336
1386
|
staticPressureArray,
|
|
1337
1387
|
height,
|
|
1338
1388
|
xScale,
|
|
@@ -1387,7 +1437,7 @@ const Soundings = ({
|
|
|
1387
1437
|
width: svgDimensions?.width || 812,
|
|
1388
1438
|
height: svgDimensions?.height || 802,
|
|
1389
1439
|
margin: {
|
|
1390
|
-
top: svgDimensions?.margin?.top ||
|
|
1440
|
+
top: svgDimensions?.margin?.top || 4,
|
|
1391
1441
|
right: svgDimensions?.margin?.right || 20,
|
|
1392
1442
|
bottom: svgDimensions?.margin?.bottom || 25,
|
|
1393
1443
|
left: svgDimensions?.margin?.left || 20
|
|
@@ -1516,7 +1566,7 @@ const Soundings = ({
|
|
|
1516
1566
|
svg.on('mousemove', null);
|
|
1517
1567
|
};
|
|
1518
1568
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1519
|
-
}, [showTooltip, data]);
|
|
1569
|
+
}, [showTooltip, data, diagramType]);
|
|
1520
1570
|
useEffect(() => {
|
|
1521
1571
|
const handleMouseLeave = () => {
|
|
1522
1572
|
if (pressureRef.current) {
|
|
@@ -1557,7 +1607,7 @@ const Soundings = ({
|
|
|
1557
1607
|
windRef.current.style.display = 'none';
|
|
1558
1608
|
}
|
|
1559
1609
|
}, [data]);
|
|
1560
|
-
return jsxs(
|
|
1610
|
+
return jsxs(Box, {
|
|
1561
1611
|
ref: canvasRef,
|
|
1562
1612
|
sx: {
|
|
1563
1613
|
position: 'relative',
|
|
@@ -1617,7 +1667,7 @@ const Soundings = ({
|
|
|
1617
1667
|
const getCollectionLocations = (collectionId, baseUrl) => fetch(`${baseUrl}/collections/${collectionId}/locations`, {
|
|
1618
1668
|
headers: getHeaders()
|
|
1619
1669
|
}).then(handleResponse);
|
|
1620
|
-
const
|
|
1670
|
+
const getCollectionInstances = (collectionId, baseUrl) => fetch(`${baseUrl}/collections/${collectionId}/instances`, {
|
|
1621
1671
|
headers: getHeaders()
|
|
1622
1672
|
}).then(handleResponse);
|
|
1623
1673
|
const getEdrCollectionForLocation = (collectionId, baseUrl, instanceId, locationId, parameters, datetime) => {
|
|
@@ -1690,6 +1740,32 @@ relHumidity) {
|
|
|
1690
1740
|
// Parsing and conversion function
|
|
1691
1741
|
function parseCoverageCollection(response, parameterMappings) {
|
|
1692
1742
|
const features = [];
|
|
1743
|
+
const pressureArray = [];
|
|
1744
|
+
const tempArray = [];
|
|
1745
|
+
const dewpointArray = [];
|
|
1746
|
+
const windDirArray = [];
|
|
1747
|
+
const windSpeedArray = [];
|
|
1748
|
+
const windUArray = [];
|
|
1749
|
+
const windVArray = [];
|
|
1750
|
+
const specificHumidityArray = [];
|
|
1751
|
+
const relativeHumidityArray = [];
|
|
1752
|
+
const emtpyDataArray = {
|
|
1753
|
+
time: '',
|
|
1754
|
+
xCoord: NaN,
|
|
1755
|
+
yCoord: NaN,
|
|
1756
|
+
data: {
|
|
1757
|
+
pressureArray,
|
|
1758
|
+
tempArray,
|
|
1759
|
+
dewpointArray,
|
|
1760
|
+
windDirArray,
|
|
1761
|
+
windSpeedArray,
|
|
1762
|
+
windUArray,
|
|
1763
|
+
windVArray,
|
|
1764
|
+
specificHumidityArray,
|
|
1765
|
+
relativeHumidityArray
|
|
1766
|
+
}
|
|
1767
|
+
};
|
|
1768
|
+
const data = {};
|
|
1693
1769
|
const {
|
|
1694
1770
|
t: tAxis,
|
|
1695
1771
|
x: xAxis,
|
|
@@ -1706,60 +1782,139 @@ function parseCoverageCollection(response, parameterMappings) {
|
|
|
1706
1782
|
const time = coverage.domain.axes.t?.values[0];
|
|
1707
1783
|
const xCoord = coverage.domain.axes.x.values[0];
|
|
1708
1784
|
const yCoord = coverage.domain.axes.y.values[0];
|
|
1785
|
+
const coverageIdentifier = `${time}-${xCoord}-${yCoord}`;
|
|
1786
|
+
if (data[coverageIdentifier] === undefined) {
|
|
1787
|
+
data[coverageIdentifier] = {
|
|
1788
|
+
...emtpyDataArray
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
data[coverageIdentifier].time = time;
|
|
1792
|
+
data[coverageIdentifier].xCoord = xCoord;
|
|
1793
|
+
data[coverageIdentifier].yCoord = yCoord;
|
|
1709
1794
|
const zValues = coverage.domain.axes.z.values;
|
|
1710
1795
|
zValues.forEach((_, zIndex) => {
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
let dewpoint = NaN;
|
|
1716
|
-
if (parameterMappings.dewpoint) {
|
|
1717
|
-
dewpoint = parseFloat(ranges[parameterMappings.dewpoint]?.values[zIndex]);
|
|
1718
|
-
} else if (parameterMappings.specificHumidity) {
|
|
1719
|
-
const specificHumidity = parseFloat(ranges[parameterMappings.specificHumidity]?.values[zIndex]);
|
|
1720
|
-
dewpoint = dewpointFromSpecificHumidity(specificHumidity / 1000,
|
|
1721
|
-
// from g/kg to kg/kg
|
|
1722
|
-
pressToPascal(pressure));
|
|
1723
|
-
} else if (parameterMappings.relativeHumidity) {
|
|
1724
|
-
const relativeHumidity = parseFloat(ranges[parameterMappings.relativeHumidity]?.values[zIndex]);
|
|
1725
|
-
dewpoint = dewpointFromRelativeHumidity(temp, relativeHumidity);
|
|
1796
|
+
if (data[coverageIdentifier].data.pressureArray.length === zIndex || ranges[parameterMappings.pressure]?.values[zIndex]) {
|
|
1797
|
+
const pressureFromRange = parseFloat(ranges[parameterMappings.pressure]?.values[zIndex]);
|
|
1798
|
+
const pressure = !Number.isNaN(pressureFromRange) ? pressureFromRange : parseFloat(zValues[zIndex]);
|
|
1799
|
+
data[coverageIdentifier].data.pressureArray[zIndex] = pressure;
|
|
1726
1800
|
}
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
if (parameterMappings.windSpeed) {
|
|
1730
|
-
windSpeed = parseFloat(ranges[parameterMappings.windSpeed]?.values[zIndex]);
|
|
1731
|
-
} else if (parameterMappings.windU && parameterMappings.windV) {
|
|
1732
|
-
const windU = parseFloat(ranges[parameterMappings.windU]?.values[zIndex]);
|
|
1733
|
-
const windV = parseFloat(ranges[parameterMappings.windV]?.values[zIndex]);
|
|
1734
|
-
windSpeed = Math.sqrt(windU ** 2 + windV ** 2);
|
|
1801
|
+
if (ranges[parameterMappings.temperature]) {
|
|
1802
|
+
data[coverageIdentifier].data.tempArray.push(parseFloat(ranges[parameterMappings.temperature]?.values[zIndex]));
|
|
1735
1803
|
}
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1804
|
+
if (parameterMappings.dewpoint && ranges[parameterMappings.dewpoint]) {
|
|
1805
|
+
data[coverageIdentifier].data.dewpointArray.push(parseFloat(ranges[parameterMappings.dewpoint]?.values[zIndex]));
|
|
1806
|
+
} else if (parameterMappings.specificHumidity && ranges[parameterMappings.specificHumidity]) {
|
|
1807
|
+
data[coverageIdentifier].data.specificHumidityArray.push(parseFloat(ranges[parameterMappings.specificHumidity]?.values[zIndex]));
|
|
1808
|
+
} else if (parameterMappings.relativeHumidity && ranges[parameterMappings.relativeHumidity]) {
|
|
1809
|
+
data[coverageIdentifier].data.relativeHumidityArray.push(parseFloat(ranges[parameterMappings.relativeHumidity]?.values[zIndex]));
|
|
1810
|
+
}
|
|
1811
|
+
if (parameterMappings.windSpeed && ranges[parameterMappings.windSpeed]) {
|
|
1812
|
+
data[coverageIdentifier].data.windSpeedArray.push(parseFloat(ranges[parameterMappings.windSpeed]?.values[zIndex]));
|
|
1813
|
+
}
|
|
1814
|
+
if (parameterMappings.windDirection && ranges[parameterMappings.windDirection]) {
|
|
1815
|
+
data[coverageIdentifier].data.windDirArray.push(parseFloat(ranges[parameterMappings.windDirection]?.values[zIndex]));
|
|
1816
|
+
}
|
|
1817
|
+
if (parameterMappings.windU && ranges[parameterMappings.windU]) {
|
|
1818
|
+
data[coverageIdentifier].data.windUArray.push(parseFloat(ranges[parameterMappings.windU]?.values[zIndex]));
|
|
1819
|
+
}
|
|
1820
|
+
if (parameterMappings.windV && ranges[parameterMappings.windV]) {
|
|
1821
|
+
data[coverageIdentifier].data.windVArray.push(parseFloat(ranges[parameterMappings.windV]?.values[zIndex]));
|
|
1822
|
+
}
|
|
1823
|
+
});
|
|
1824
|
+
});
|
|
1825
|
+
// For now we only accept one data set per call so we only take the first one
|
|
1826
|
+
const firstCollectionIdentifier = Object.keys(data)[0];
|
|
1827
|
+
const collectionData = data[firstCollectionIdentifier].data;
|
|
1828
|
+
collectionData.pressureArray.forEach((pressure, index) => {
|
|
1829
|
+
// Extract dewpoint - use fallback calculations if no dewpoint in parameter mappings
|
|
1830
|
+
let dewpoint = NaN;
|
|
1831
|
+
if (parameterMappings.dewpoint) {
|
|
1832
|
+
dewpoint = collectionData.dewpointArray[index];
|
|
1833
|
+
} else if (parameterMappings.specificHumidity) {
|
|
1834
|
+
const specificHumidity = collectionData.specificHumidityArray[index];
|
|
1835
|
+
dewpoint = dewpointFromSpecificHumidity(specificHumidity / 1000,
|
|
1836
|
+
// from g/kg to kg/kg
|
|
1837
|
+
pressToPascal(pressure));
|
|
1838
|
+
} else if (parameterMappings.relativeHumidity) {
|
|
1839
|
+
const relativeHumidity = collectionData.relativeHumidityArray[index];
|
|
1840
|
+
dewpoint = dewpointFromRelativeHumidity(tempArray[index], relativeHumidity);
|
|
1841
|
+
}
|
|
1842
|
+
// Extract wind speed and direction - use fallback calculations if no speed and direction in parameter mappings
|
|
1843
|
+
let windSpeed = NaN;
|
|
1844
|
+
if (parameterMappings.windSpeed) {
|
|
1845
|
+
windSpeed = collectionData.windSpeedArray[index];
|
|
1846
|
+
} else if (parameterMappings.windU && parameterMappings.windV) {
|
|
1847
|
+
const windU = collectionData.windUArray[index];
|
|
1848
|
+
const windV = collectionData.windVArray[index];
|
|
1849
|
+
windSpeed = Math.sqrt(windU ** 2 + windV ** 2);
|
|
1850
|
+
}
|
|
1851
|
+
let windDirection = NaN;
|
|
1852
|
+
if (parameterMappings.windDirection) {
|
|
1853
|
+
windDirection = collectionData.windDirArray[index];
|
|
1854
|
+
} else if (parameterMappings.windU && parameterMappings.windV) {
|
|
1855
|
+
const windU = collectionData.windUArray[index];
|
|
1856
|
+
const windV = collectionData.windVArray[index];
|
|
1857
|
+
windDirection = (Math.atan2(-windU, -windV) * (180 / Math.PI) + 360) % 360;
|
|
1858
|
+
}
|
|
1859
|
+
features.push({
|
|
1860
|
+
type: 'Feature',
|
|
1861
|
+
geometry: {
|
|
1862
|
+
type: 'Point',
|
|
1863
|
+
coordinates: [data[firstCollectionIdentifier].xCoord, data[firstCollectionIdentifier].yCoord]
|
|
1864
|
+
},
|
|
1865
|
+
properties: {
|
|
1866
|
+
time: data[firstCollectionIdentifier].time,
|
|
1867
|
+
temp: tempArray[index],
|
|
1868
|
+
dewpoint,
|
|
1869
|
+
pressure: pressureArray[index],
|
|
1870
|
+
windSpeed,
|
|
1871
|
+
windDirection
|
|
1743
1872
|
}
|
|
1744
|
-
features.push({
|
|
1745
|
-
type: 'Feature',
|
|
1746
|
-
geometry: {
|
|
1747
|
-
type: 'Point',
|
|
1748
|
-
coordinates: [xCoord, yCoord]
|
|
1749
|
-
},
|
|
1750
|
-
properties: {
|
|
1751
|
-
time,
|
|
1752
|
-
temp,
|
|
1753
|
-
dewpoint,
|
|
1754
|
-
pressure,
|
|
1755
|
-
windSpeed,
|
|
1756
|
-
windDirection
|
|
1757
|
-
}
|
|
1758
|
-
});
|
|
1759
1873
|
});
|
|
1760
1874
|
});
|
|
1761
1875
|
return features;
|
|
1762
1876
|
}
|
|
1877
|
+
/**
|
|
1878
|
+
* Parse duration with repeats
|
|
1879
|
+
* @param dateString e.g. 'R67/2025-04-22T09:00:00Z/PT60M'
|
|
1880
|
+
* @returns Array of ISO datetime strings, e.g., ['2025-04-22T09:00:00.000Z', ...]
|
|
1881
|
+
*/
|
|
1882
|
+
const parseDurationWithRepeats = dateString => {
|
|
1883
|
+
const parts = dateString.split('/');
|
|
1884
|
+
const numOfRepeats = Number(parts[0].substring(1));
|
|
1885
|
+
const start = new Date(parts[1]).getTime();
|
|
1886
|
+
const interval = parseISO8601IntervalToDateInterval(parts[2]).getTime();
|
|
1887
|
+
const iterations = range(numOfRepeats);
|
|
1888
|
+
const dateStrings = iterations.map(i => start + i * interval).map(millis => new Date(millis).toISOString());
|
|
1889
|
+
return dateStrings;
|
|
1890
|
+
};
|
|
1891
|
+
const getTimestepsFromInstance = instance => {
|
|
1892
|
+
const timeExtentValues = instance?.extent?.temporal?.values;
|
|
1893
|
+
if (!timeExtentValues) {
|
|
1894
|
+
return [];
|
|
1895
|
+
}
|
|
1896
|
+
try {
|
|
1897
|
+
// parse the time extent, if needed
|
|
1898
|
+
if (timeExtentValues.length === 1) {
|
|
1899
|
+
const timeExtent = timeExtentValues[0];
|
|
1900
|
+
// duration with repeats
|
|
1901
|
+
if (timeExtent.match(/^R\d+\/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z\/\S+$/)) {
|
|
1902
|
+
return parseDurationWithRepeats(timeExtent);
|
|
1903
|
+
}
|
|
1904
|
+
// format without repeats
|
|
1905
|
+
const parseObject = new ParseISOTimeRangeDuration(timeExtent);
|
|
1906
|
+
const timeValues = range(parseObject.getTimeSteps()).map(timestep => parseObject.getDateAtTimeStep(timestep).toISOString());
|
|
1907
|
+
return timeValues;
|
|
1908
|
+
}
|
|
1909
|
+
// If more than one value, assume time extent is given as individual timesteps.
|
|
1910
|
+
// Pass through toISOString to ensure consistent timestamp format.
|
|
1911
|
+
const isoFormattedValues = timeExtentValues.map(value => new Date(value).toISOString());
|
|
1912
|
+
return isoFormattedValues;
|
|
1913
|
+
} catch (error) {
|
|
1914
|
+
console.warn(error);
|
|
1915
|
+
}
|
|
1916
|
+
return [];
|
|
1917
|
+
};
|
|
1763
1918
|
|
|
1764
1919
|
/* *
|
|
1765
1920
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -1795,8 +1950,8 @@ const constructQueryParams = (collectionId, baseUrl, instanceId, location, datet
|
|
|
1795
1950
|
return {
|
|
1796
1951
|
queryKey: ['edr-soundings-for-location', collectionId, instanceId || '', location, parameters.join(), datetime],
|
|
1797
1952
|
queryFn: async () => {
|
|
1798
|
-
// No collection or
|
|
1799
|
-
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 === '') {
|
|
1800
1955
|
return [];
|
|
1801
1956
|
}
|
|
1802
1957
|
try {
|
|
@@ -1813,8 +1968,8 @@ const constructQueryParams = (collectionId, baseUrl, instanceId, location, datet
|
|
|
1813
1968
|
return {
|
|
1814
1969
|
queryKey: ['edr-soundings-for-position', collectionId, location.join(), parameters.join(), datetime],
|
|
1815
1970
|
queryFn: async () => {
|
|
1816
|
-
// No collection or
|
|
1817
|
-
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 === '') {
|
|
1818
1973
|
return [];
|
|
1819
1974
|
}
|
|
1820
1975
|
try {
|
|
@@ -1827,17 +1982,6 @@ const constructQueryParams = (collectionId, baseUrl, instanceId, location, datet
|
|
|
1827
1982
|
}
|
|
1828
1983
|
};
|
|
1829
1984
|
};
|
|
1830
|
-
const useFakeTime = (collectionId, baseUrl, instanceId) => {
|
|
1831
|
-
return useQuery({
|
|
1832
|
-
queryKey: ['temporary-instance-call', collectionId, instanceId],
|
|
1833
|
-
queryFn: async () => {
|
|
1834
|
-
return fetch(`${baseUrl}/collections/${collectionId}/instances/${instanceId}`, {
|
|
1835
|
-
headers: getHeaders()
|
|
1836
|
-
}).then(handleResponse).then(data => collectionId === undefined || data === undefined ? '' : data.extent.temporal.interval[0][0]);
|
|
1837
|
-
},
|
|
1838
|
-
enabled: !!collectionId && !!instanceId
|
|
1839
|
-
});
|
|
1840
|
-
};
|
|
1841
1985
|
const defaultParameterMappings = {
|
|
1842
1986
|
temperature: 'temperature',
|
|
1843
1987
|
windDirection: 'winddirection',
|
|
@@ -1854,34 +1998,34 @@ const getParameterMappings = parameterMappings => {
|
|
|
1854
1998
|
return Object.keys(parameterMappings).map(key => parameterMappings[key]);
|
|
1855
1999
|
};
|
|
1856
2000
|
const useMultipleSources = (sources, locations, datetime) => {
|
|
1857
|
-
// TODO: Remove once we pass in the datetime coming from the timeslider!
|
|
1858
|
-
const {
|
|
1859
|
-
data: date0
|
|
1860
|
-
} = useFakeTime(sources[0]?.collectionId, sources[0]?.url, sources[0]?.instanceId);
|
|
1861
2001
|
const {
|
|
1862
|
-
data:
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
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]);
|
|
1877
2021
|
return useQueries({
|
|
1878
2022
|
queries,
|
|
1879
2023
|
combine: queryResults => {
|
|
1880
2024
|
const data = queryResults.map(result => {
|
|
1881
2025
|
return result.data || [];
|
|
1882
2026
|
});
|
|
1883
|
-
const isPending = queryResults.some(result => result.isPending);
|
|
1884
|
-
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;
|
|
1885
2029
|
return {
|
|
1886
2030
|
data,
|
|
1887
2031
|
isPending,
|
|
@@ -1890,6 +2034,41 @@ const useMultipleSources = (sources, locations, datetime) => {
|
|
|
1890
2034
|
}
|
|
1891
2035
|
});
|
|
1892
2036
|
};
|
|
2037
|
+
const useTimesteps = sourceSelections => {
|
|
2038
|
+
const queries = React.useMemo(() => sourceSelections.map(({
|
|
2039
|
+
collectionId,
|
|
2040
|
+
instanceId,
|
|
2041
|
+
url
|
|
2042
|
+
}) => ({
|
|
2043
|
+
queryKey: ['edr-soundings-instances', collectionId],
|
|
2044
|
+
queryFn: async () => {
|
|
2045
|
+
const data = await getCollectionInstances(collectionId, url);
|
|
2046
|
+
return data;
|
|
2047
|
+
},
|
|
2048
|
+
select: data => {
|
|
2049
|
+
const instance = data.instances.find(instance => instance.id === instanceId);
|
|
2050
|
+
return getTimestepsFromInstance(instance);
|
|
2051
|
+
}
|
|
2052
|
+
})), [sourceSelections]);
|
|
2053
|
+
const results = useQueries({
|
|
2054
|
+
queries,
|
|
2055
|
+
combine: queryResults => {
|
|
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
|
+
};
|
|
2068
|
+
}
|
|
2069
|
+
});
|
|
2070
|
+
return results;
|
|
2071
|
+
};
|
|
1893
2072
|
const useGetCollectionsInstances = (collections, isDialogOpen) => {
|
|
1894
2073
|
const collectionIds = useMemo(() => collections?.map(collection => {
|
|
1895
2074
|
return {
|
|
@@ -1904,7 +2083,7 @@ const useGetCollectionsInstances = (collections, isDialogOpen) => {
|
|
|
1904
2083
|
return {
|
|
1905
2084
|
queryKey: ['edr-soundings-instances', id],
|
|
1906
2085
|
queryFn: async () => {
|
|
1907
|
-
const data = await
|
|
2086
|
+
const data = await getCollectionInstances(id, url);
|
|
1908
2087
|
return data;
|
|
1909
2088
|
},
|
|
1910
2089
|
enabled: isDialogOpen
|
|
@@ -2002,6 +2181,16 @@ const translateKeyOutsideComponents = (key, params = undefined) => i18n.t(key, {
|
|
|
2002
2181
|
});
|
|
2003
2182
|
const useSoundingsTranslation = () => useTranslation(ns);
|
|
2004
2183
|
|
|
2184
|
+
const MAX_LABEL_COUNT = 15;
|
|
2185
|
+
const shouldHideLabel = (nItems, index) => {
|
|
2186
|
+
const nthLabelsToShow = Math.ceil(nItems / MAX_LABEL_COUNT);
|
|
2187
|
+
return nItems > MAX_LABEL_COUNT &&
|
|
2188
|
+
// hide labels if too many items
|
|
2189
|
+
index !== nItems - 1 && (
|
|
2190
|
+
// not last item (always shown)
|
|
2191
|
+
// only show every nth label, skip label within last n/2 items so end doesn't get cluttered
|
|
2192
|
+
index % nthLabelsToShow !== 0 || nItems - 1 - index < nthLabelsToShow / 2);
|
|
2193
|
+
};
|
|
2005
2194
|
const SoundingsTimeSlider = ({
|
|
2006
2195
|
timesteps,
|
|
2007
2196
|
selectedTime,
|
|
@@ -2010,9 +2199,9 @@ const SoundingsTimeSlider = ({
|
|
|
2010
2199
|
const {
|
|
2011
2200
|
i18n
|
|
2012
2201
|
} = useSoundingsTranslation();
|
|
2013
|
-
const marks = timesteps.filter(date => dateUtils.isValidIsoDateString(date)).map(date => ({
|
|
2202
|
+
const marks = timesteps.filter(date => dateUtils.isValidIsoDateString(date)).map((date, index, arr) => ({
|
|
2014
2203
|
value: dateUtils.unix(dateUtils.getUtcFromString(date)),
|
|
2015
|
-
label: dateUtils.dateToString(dateUtils.getUtcFromString(date), 'iii d. HH:mm', true, i18n.language)
|
|
2204
|
+
label: shouldHideLabel(arr.length, index) ? '' : dateUtils.dateToString(dateUtils.getUtcFromString(date), 'iii d. HH:mm', true, i18n.language)
|
|
2016
2205
|
}));
|
|
2017
2206
|
const onStepBackward = React.useCallback(() => {
|
|
2018
2207
|
const currentIndex = timesteps.indexOf(selectedTime);
|
|
@@ -2027,12 +2216,15 @@ const SoundingsTimeSlider = ({
|
|
|
2027
2216
|
return jsxs("div", {
|
|
2028
2217
|
style: {
|
|
2029
2218
|
display: 'flex',
|
|
2030
|
-
marginTop: '
|
|
2219
|
+
marginTop: '40px',
|
|
2220
|
+
marginRight: '4rem'
|
|
2031
2221
|
},
|
|
2032
2222
|
children: [jsxs("div", {
|
|
2033
2223
|
style: {
|
|
2034
2224
|
minWidth: '60px',
|
|
2035
|
-
marginTop: '2px'
|
|
2225
|
+
marginTop: '2px',
|
|
2226
|
+
display: 'flex',
|
|
2227
|
+
gap: '4px'
|
|
2036
2228
|
},
|
|
2037
2229
|
children: [jsx(BackwardForwardStepButton, {
|
|
2038
2230
|
isForwardStep: false,
|
|
@@ -2051,7 +2243,7 @@ const SoundingsTimeSlider = ({
|
|
|
2051
2243
|
disabled: true
|
|
2052
2244
|
}) : jsx(CustomSlider, {
|
|
2053
2245
|
orientation: "horizontal",
|
|
2054
|
-
valueLabelDisplay: "
|
|
2246
|
+
valueLabelDisplay: "on",
|
|
2055
2247
|
valueLabelFormat: value => dateUtils.dateToString(dateUtils.fromUnix(value), "yyyy iii dd MMM HH:mm 'UTC'", true, i18n.language),
|
|
2056
2248
|
marks: marks,
|
|
2057
2249
|
step: null,
|
|
@@ -2061,6 +2253,21 @@ const SoundingsTimeSlider = ({
|
|
|
2061
2253
|
onChange: (_, value) => {
|
|
2062
2254
|
updateSelectedTime(dateUtils.fromUnix(value).toISOString());
|
|
2063
2255
|
},
|
|
2256
|
+
// Workaround for MUI bug where labels are just draggable area and clicking on one
|
|
2257
|
+
// does not necessarily select the described timestep
|
|
2258
|
+
slotProps: {
|
|
2259
|
+
markLabel: {
|
|
2260
|
+
onPointerDown: event => {
|
|
2261
|
+
event.preventDefault();
|
|
2262
|
+
const markIndex = event.currentTarget.getAttribute('data-index');
|
|
2263
|
+
const mark = marks[Number(markIndex)];
|
|
2264
|
+
const {
|
|
2265
|
+
value
|
|
2266
|
+
} = mark;
|
|
2267
|
+
updateSelectedTime(dateUtils.fromUnix(value).toISOString());
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
},
|
|
2064
2271
|
extraStyles: {
|
|
2065
2272
|
margin: '0px 20px 0px 20px',
|
|
2066
2273
|
'.MuiSlider-markLabel': {
|
|
@@ -2174,19 +2381,41 @@ const getSelectedSourcesIds = createSelector(getSelectedSources, activeSources =
|
|
|
2174
2381
|
return activeSources.map(source => source.collectionId);
|
|
2175
2382
|
}, selectorMemoizationOptions);
|
|
2176
2383
|
|
|
2177
|
-
const
|
|
2178
|
-
|
|
2179
|
-
|
|
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
|
+
};
|
|
2394
|
+
const SoundingsTimeSliderConnect = () => {
|
|
2180
2395
|
const selectedTime = useSelector(getSelectedTime);
|
|
2396
|
+
const sourceSelections = useSelector(getSelectedSources);
|
|
2181
2397
|
const dispatch = useDispatch();
|
|
2182
2398
|
const updateSelectedTime = React.useCallback(selectedTime => {
|
|
2183
2399
|
dispatch(soundingsActions.updateSelectedTime({
|
|
2184
2400
|
selectedTime
|
|
2185
2401
|
}));
|
|
2186
2402
|
}, [dispatch]);
|
|
2403
|
+
const {
|
|
2404
|
+
data: timesteps
|
|
2405
|
+
} = useTimesteps(sourceSelections);
|
|
2406
|
+
const mergedTimesteps = Array.from(new Set(timesteps.flat())).sort();
|
|
2407
|
+
// TODO: next line should work in Storybook but does not: https://gitlab.com/opengeoweb/geoweb-assets/-/issues/4025
|
|
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]);
|
|
2187
2416
|
return jsx(SoundingsTimeSlider, {
|
|
2188
|
-
timesteps:
|
|
2189
|
-
selectedTime:
|
|
2417
|
+
timesteps: mergedTimesteps,
|
|
2418
|
+
selectedTime: validSelectedTime,
|
|
2190
2419
|
updateSelectedTime: updateSelectedTime
|
|
2191
2420
|
});
|
|
2192
2421
|
};
|
|
@@ -2275,18 +2504,92 @@ const MenuBarConnect = ({
|
|
|
2275
2504
|
});
|
|
2276
2505
|
};
|
|
2277
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;
|
|
2278
2580
|
const SoundingsConnect = ({
|
|
2279
|
-
viewId
|
|
2581
|
+
viewId,
|
|
2582
|
+
diagramType = 'SKEWT'
|
|
2280
2583
|
}) => {
|
|
2281
|
-
|
|
2282
|
-
|
|
2584
|
+
const [chosenDiagramType, setChosenDiagramType] = React.useState(diagramType);
|
|
2585
|
+
const selectedTime = useSelector(getSelectedTime);
|
|
2283
2586
|
const sourceSelections = useSelector(getSelectedSources);
|
|
2284
2587
|
const locationSelections = useSelector(getLocationSelections);
|
|
2285
2588
|
const {
|
|
2286
2589
|
error,
|
|
2287
2590
|
data,
|
|
2288
2591
|
isPending
|
|
2289
|
-
} = useMultipleSources(sourceSelections, locationSelections);
|
|
2592
|
+
} = useMultipleSources(sourceSelections, locationSelections, selectedTime);
|
|
2290
2593
|
return jsxs(Fragment, {
|
|
2291
2594
|
children: [error && jsx(AlertBanner, {
|
|
2292
2595
|
title: error.message,
|
|
@@ -2308,24 +2611,34 @@ const SoundingsConnect = ({
|
|
|
2308
2611
|
children: jsx(MenuBarConnect, {
|
|
2309
2612
|
viewId: viewId
|
|
2310
2613
|
})
|
|
2311
|
-
}),
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
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
|
+
})]
|
|
2641
|
+
}), jsx(SoundingsTimeSliderConnect, {})]
|
|
2329
2642
|
})]
|
|
2330
2643
|
});
|
|
2331
2644
|
};
|
|
@@ -2379,7 +2692,8 @@ const SoundingsSourceMenu = ({
|
|
|
2379
2692
|
children: soundingsCollections && soundingsCollections.length > 0 ? soundingsCollections.map(collection => {
|
|
2380
2693
|
const {
|
|
2381
2694
|
isChecked,
|
|
2382
|
-
instanceId
|
|
2695
|
+
instanceId,
|
|
2696
|
+
url
|
|
2383
2697
|
} = sourceSelections[collection.id] || {};
|
|
2384
2698
|
const hasInstances = collection.instances.length > 0;
|
|
2385
2699
|
const selectedInstanceId = instanceId || collection.instances[0]?.id || undefined;
|
|
@@ -2392,6 +2706,7 @@ const SoundingsSourceMenu = ({
|
|
|
2392
2706
|
children: jsx(Checkbox, {
|
|
2393
2707
|
checked: !!isChecked,
|
|
2394
2708
|
onChange: event => updateCollection(collection.id, {
|
|
2709
|
+
url,
|
|
2395
2710
|
isChecked: event.target.checked,
|
|
2396
2711
|
instanceId: selectedInstanceId
|
|
2397
2712
|
}),
|
|
@@ -2405,6 +2720,7 @@ const SoundingsSourceMenu = ({
|
|
|
2405
2720
|
skipLocalStyling: false,
|
|
2406
2721
|
value: selectedInstanceId || t('noInstancesAvailable'),
|
|
2407
2722
|
onChange: event => updateCollection(collection.id, {
|
|
2723
|
+
url,
|
|
2408
2724
|
instanceId: event.target.value
|
|
2409
2725
|
}),
|
|
2410
2726
|
renderValue: value => jsx(Box, {
|
|
@@ -2682,7 +2998,8 @@ const soundingsComponentType = 'Soundings';
|
|
|
2682
2998
|
const soundingsLookUp = payload => {
|
|
2683
2999
|
if (payload.componentType === soundingsComponentType) {
|
|
2684
3000
|
return jsx(SoundingsConnect, {
|
|
2685
|
-
viewId: payload.id
|
|
3001
|
+
viewId: payload.id,
|
|
3002
|
+
diagramType: payload.diagramType ? payload.diagramType : 'SKEWT'
|
|
2686
3003
|
});
|
|
2687
3004
|
}
|
|
2688
3005
|
return null;
|
|
@@ -2716,4 +3033,4 @@ const createMockStore = mockState => configureStore({
|
|
|
2716
3033
|
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(storeMiddlewares)
|
|
2717
3034
|
});
|
|
2718
3035
|
|
|
2719
|
-
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 };
|