@opengeoweb/soundings 12.6.0 → 12.8.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 +342 -128
- package/package.json +10 -9
- package/src/lib/api/edr-api/api.d.ts +2 -2
- package/src/lib/api/edr-api/hooks.d.ts +6 -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/store/types.d.ts +2 -0
- package/src/lib/utils/soundingsLookUp.d.ts +2 -0
- /package/{index.esm.d.ts → index.d.ts} +0 -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',
|
|
@@ -1622,20 +1670,22 @@ const getCollectionLocations = (collectionId, baseUrl) => fetch(`${baseUrl}/coll
|
|
|
1622
1670
|
const getCollectionInstances = (collectionId, baseUrl) => fetch(`${baseUrl}/collections/${collectionId}/instances`, {
|
|
1623
1671
|
headers: getHeaders()
|
|
1624
1672
|
}).then(handleResponse);
|
|
1625
|
-
const getEdrCollectionForLocation = (collectionId, baseUrl, instanceId, locationId, parameters, datetime) => {
|
|
1673
|
+
const getEdrCollectionForLocation = (collectionId, baseUrl, instanceId, locationId, parameters, datetime, verticalRange) => {
|
|
1626
1674
|
const url = new URL(`${baseUrl}/collections/${collectionId}${instanceId ? `/instances/${instanceId}` : ''}/locations/${locationId}`);
|
|
1627
1675
|
url.searchParams.append('parameter-name', parameters.join(','));
|
|
1628
1676
|
url.searchParams.append('datetime', datetime);
|
|
1677
|
+
url.searchParams.append('z', verticalRange);
|
|
1629
1678
|
url.searchParams.append('f', 'CoverageJSON');
|
|
1630
1679
|
return fetch(url.toString(), {
|
|
1631
1680
|
headers: getHeaders()
|
|
1632
1681
|
}).then(handleResponse);
|
|
1633
1682
|
};
|
|
1634
|
-
const getEdrCollectionForPosition = (collectionId, baseUrl, instanceId, coordinates, parameters, datetime) => {
|
|
1683
|
+
const getEdrCollectionForPosition = (collectionId, baseUrl, instanceId, coordinates, parameters, datetime, verticalRange) => {
|
|
1635
1684
|
const url = new URL(`${baseUrl}/collections/${collectionId}${instanceId ? `/instances/${instanceId}` : ''}/position`);
|
|
1636
1685
|
url.searchParams.append('coords', `POINT(${coordinates[0]} ${coordinates[1]})`);
|
|
1637
1686
|
url.searchParams.append('parameter-name', parameters.join(','));
|
|
1638
1687
|
url.searchParams.append('datetime', datetime);
|
|
1688
|
+
url.searchParams.append('z', verticalRange);
|
|
1639
1689
|
url.searchParams.append('f', 'CoverageJSON');
|
|
1640
1690
|
return fetch(url.toString(), {
|
|
1641
1691
|
headers: getHeaders()
|
|
@@ -1845,27 +1895,29 @@ const getTimestepsFromInstance = instance => {
|
|
|
1845
1895
|
if (!timeExtentValues) {
|
|
1846
1896
|
return [];
|
|
1847
1897
|
}
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
return parseDurationWithRepeats(timeExtent);
|
|
1898
|
+
const allParsedTimesteps = timeExtentValues.reduce((parsedSteps, currentValue) => {
|
|
1899
|
+
try {
|
|
1900
|
+
// Try to parse EDR time extent value in different possible formats
|
|
1901
|
+
// R<repeats>/<start>/P<period>
|
|
1902
|
+
if (currentValue.match(/^R\d+\/.*\/P.*$/)) {
|
|
1903
|
+
return [...parsedSteps, ...parseDurationWithRepeats(currentValue)];
|
|
1855
1904
|
}
|
|
1856
|
-
//
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1905
|
+
// <start>/<end>[/P<period>]
|
|
1906
|
+
if (currentValue.match(/.*\/.*P?.*/)) {
|
|
1907
|
+
const parseObject = new ParseISOTimeRangeDuration(currentValue);
|
|
1908
|
+
const timeValues = range(parseObject.getTimeSteps()).map(timestep => parseObject.getDateAtTimeStep(timestep).toISOString());
|
|
1909
|
+
return [...parsedSteps, ...timeValues];
|
|
1910
|
+
}
|
|
1911
|
+
// individual datetime string
|
|
1912
|
+
const isoFormattedValue = new Date(currentValue).toISOString();
|
|
1913
|
+
return [...parsedSteps, isoFormattedValue];
|
|
1914
|
+
} catch (error) {
|
|
1915
|
+
console.warn(error);
|
|
1916
|
+
// skip unsupported format
|
|
1917
|
+
return [...parsedSteps];
|
|
1860
1918
|
}
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
const isoFormattedValues = timeExtentValues.map(value => new Date(value).toISOString());
|
|
1864
|
-
return isoFormattedValues;
|
|
1865
|
-
} catch (error) {
|
|
1866
|
-
console.warn(error);
|
|
1867
|
-
}
|
|
1868
|
-
return [];
|
|
1919
|
+
}, []);
|
|
1920
|
+
return allParsedTimesteps;
|
|
1869
1921
|
};
|
|
1870
1922
|
|
|
1871
1923
|
/* *
|
|
@@ -1895,19 +1947,19 @@ const sortSoundingFeatures = data => {
|
|
|
1895
1947
|
return featureB.properties.pressure - featureA.properties.pressure;
|
|
1896
1948
|
});
|
|
1897
1949
|
};
|
|
1898
|
-
const constructQueryParams = (collectionId, baseUrl, instanceId, location, datetime, parameterMappings) => {
|
|
1950
|
+
const constructQueryParams = (collectionId, baseUrl, instanceId, location, datetime, parameterMappings, verticalRange) => {
|
|
1899
1951
|
const parameters = getParameterMappings(parameterMappings);
|
|
1900
1952
|
if (typeof location === 'string') {
|
|
1901
1953
|
// LOCATION call
|
|
1902
1954
|
return {
|
|
1903
|
-
queryKey: ['edr-soundings-for-location', collectionId, instanceId || '', location, parameters.join(), datetime],
|
|
1955
|
+
queryKey: ['edr-soundings-for-location', collectionId, instanceId || '', location, parameters.join(), datetime, verticalRange],
|
|
1904
1956
|
queryFn: async () => {
|
|
1905
|
-
// No collection or
|
|
1906
|
-
if (collectionId === undefined || location === '') {
|
|
1957
|
+
// No collection, location or time selected, don't do EDR call but return empty array
|
|
1958
|
+
if (collectionId === undefined || location === '' || datetime === '') {
|
|
1907
1959
|
return [];
|
|
1908
1960
|
}
|
|
1909
1961
|
try {
|
|
1910
|
-
const rawData = await getEdrCollectionForLocation(collectionId, baseUrl, instanceId, location, parameters, datetime);
|
|
1962
|
+
const rawData = await getEdrCollectionForLocation(collectionId, baseUrl, instanceId, location, parameters, datetime, verticalRange);
|
|
1911
1963
|
const parsedData = parseCoverageCollection(rawData, parameterMappings);
|
|
1912
1964
|
return sortSoundingFeatures(parsedData);
|
|
1913
1965
|
} catch (err) {
|
|
@@ -1918,14 +1970,14 @@ const constructQueryParams = (collectionId, baseUrl, instanceId, location, datet
|
|
|
1918
1970
|
}
|
|
1919
1971
|
// POSITION call
|
|
1920
1972
|
return {
|
|
1921
|
-
queryKey: ['edr-soundings-for-position', collectionId, location.join(), parameters.join(), datetime],
|
|
1973
|
+
queryKey: ['edr-soundings-for-position', collectionId, location.join(), parameters.join(), datetime, verticalRange],
|
|
1922
1974
|
queryFn: async () => {
|
|
1923
|
-
// No collection or
|
|
1924
|
-
if (collectionId === undefined || location === undefined) {
|
|
1975
|
+
// No collection, position or time selected, don't do EDR call but return empty array
|
|
1976
|
+
if (collectionId === undefined || location === undefined || datetime === '') {
|
|
1925
1977
|
return [];
|
|
1926
1978
|
}
|
|
1927
1979
|
try {
|
|
1928
|
-
const rawData = await getEdrCollectionForPosition(collectionId, baseUrl, instanceId, location, parameters, datetime);
|
|
1980
|
+
const rawData = await getEdrCollectionForPosition(collectionId, baseUrl, instanceId, location, parameters, datetime, verticalRange);
|
|
1929
1981
|
const parsedData = parseCoverageCollection(rawData, parameterMappings);
|
|
1930
1982
|
return sortSoundingFeatures(parsedData);
|
|
1931
1983
|
} catch (err) {
|
|
@@ -1950,23 +2002,35 @@ const getParameterMappings = parameterMappings => {
|
|
|
1950
2002
|
return Object.keys(parameterMappings).map(key => parameterMappings[key]);
|
|
1951
2003
|
};
|
|
1952
2004
|
const useMultipleSources = (sources, locations, datetime) => {
|
|
1953
|
-
const
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
2005
|
+
const {
|
|
2006
|
+
data: timesteps,
|
|
2007
|
+
error: timestepsError,
|
|
2008
|
+
isPending: timestepsPending
|
|
2009
|
+
} = useTimesteps(sources);
|
|
2010
|
+
const queries = useMemo(() => {
|
|
2011
|
+
return sources.map(({
|
|
2012
|
+
collectionId,
|
|
2013
|
+
url,
|
|
2014
|
+
instanceId,
|
|
2015
|
+
parameterMappings,
|
|
2016
|
+
verticalRange
|
|
2017
|
+
}, index) => {
|
|
2018
|
+
// If acceptance time logic is needed, it could come here.
|
|
2019
|
+
const dateToUse = datetime &&
|
|
2020
|
+
// Only use selected time if exact match if found
|
|
2021
|
+
timesteps[index].includes(new Date(datetime).toISOString()) ? datetime : '';
|
|
2022
|
+
const queryParams = constructQueryParams(collectionId, url, instanceId, locations[collectionId] || '', dateToUse || '', parameterMappings || defaultParameterMappings, verticalRange || '');
|
|
2023
|
+
return queryParams;
|
|
2024
|
+
});
|
|
2025
|
+
}, [datetime, locations, sources, timesteps]);
|
|
1962
2026
|
return useQueries({
|
|
1963
2027
|
queries,
|
|
1964
2028
|
combine: queryResults => {
|
|
1965
2029
|
const data = queryResults.map(result => {
|
|
1966
2030
|
return result.data || [];
|
|
1967
2031
|
});
|
|
1968
|
-
const isPending = queryResults.some(result => result.isPending);
|
|
1969
|
-
const error = queryResults.find(result => result?.error)?.error;
|
|
2032
|
+
const isPending = timestepsPending || queryResults.some(result => result.isPending);
|
|
2033
|
+
const error = timestepsError || queryResults.find(result => result?.error)?.error;
|
|
1970
2034
|
return {
|
|
1971
2035
|
data,
|
|
1972
2036
|
isPending,
|
|
@@ -1994,8 +2058,18 @@ const useTimesteps = sourceSelections => {
|
|
|
1994
2058
|
const results = useQueries({
|
|
1995
2059
|
queries,
|
|
1996
2060
|
combine: queryResults => {
|
|
1997
|
-
const
|
|
1998
|
-
|
|
2061
|
+
const data = queryResults.map(result => result.data || []);
|
|
2062
|
+
// The underlying query is generic, so we rewrap the error to be more specific
|
|
2063
|
+
const foundError = queryResults.find(result => result?.error)?.error;
|
|
2064
|
+
const error = foundError ? new Error('Error fetching timesteps by collection', {
|
|
2065
|
+
cause: foundError
|
|
2066
|
+
}) : null;
|
|
2067
|
+
const isPending = queryResults.some(result => result.isPending);
|
|
2068
|
+
return {
|
|
2069
|
+
data,
|
|
2070
|
+
error,
|
|
2071
|
+
isPending
|
|
2072
|
+
};
|
|
1999
2073
|
}
|
|
2000
2074
|
});
|
|
2001
2075
|
return results;
|
|
@@ -2298,6 +2372,7 @@ const getSelectedSources = createSelector(getSoundingsStore, store => {
|
|
|
2298
2372
|
collectionId: key,
|
|
2299
2373
|
url: value.url,
|
|
2300
2374
|
instanceId: value.instanceId,
|
|
2375
|
+
verticalRange: value.verticalRange,
|
|
2301
2376
|
parameterMappings: value.parameterMappings
|
|
2302
2377
|
});
|
|
2303
2378
|
}
|
|
@@ -2312,6 +2387,16 @@ const getSelectedSourcesIds = createSelector(getSelectedSources, activeSources =
|
|
|
2312
2387
|
return activeSources.map(source => source.collectionId);
|
|
2313
2388
|
}, selectorMemoizationOptions);
|
|
2314
2389
|
|
|
2390
|
+
const roundToClosestTimestep = (timeToRound, availableSteps) => {
|
|
2391
|
+
if (availableSteps.length > 0 && !availableSteps.includes(timeToRound)) {
|
|
2392
|
+
const selectionValue = dateUtils.unix(new Date(timeToRound));
|
|
2393
|
+
const closestTimestep = minBy(availableSteps, timeValue =>
|
|
2394
|
+
// distance to selected time
|
|
2395
|
+
Math.abs(selectionValue - dateUtils.unix(new Date(timeValue))));
|
|
2396
|
+
return closestTimestep || availableSteps[0];
|
|
2397
|
+
}
|
|
2398
|
+
return timeToRound;
|
|
2399
|
+
};
|
|
2315
2400
|
const SoundingsTimeSliderConnect = () => {
|
|
2316
2401
|
const selectedTime = useSelector(getSelectedTime);
|
|
2317
2402
|
const sourceSelections = useSelector(getSelectedSources);
|
|
@@ -2321,13 +2406,22 @@ const SoundingsTimeSliderConnect = () => {
|
|
|
2321
2406
|
selectedTime
|
|
2322
2407
|
}));
|
|
2323
2408
|
}, [dispatch]);
|
|
2324
|
-
const
|
|
2409
|
+
const {
|
|
2410
|
+
data: timesteps
|
|
2411
|
+
} = useTimesteps(sourceSelections);
|
|
2325
2412
|
const mergedTimesteps = Array.from(new Set(timesteps.flat())).sort();
|
|
2326
2413
|
// TODO: next line should work in Storybook but does not: https://gitlab.com/opengeoweb/geoweb-assets/-/issues/4025
|
|
2327
2414
|
// const mergedTimesteps = [...new Set(timestepArrays.flat())].sort();
|
|
2415
|
+
// Ensure that the selected time is always one of the available timesteps
|
|
2416
|
+
const validSelectedTime = roundToClosestTimestep(selectedTime, mergedTimesteps);
|
|
2417
|
+
useEffect(() => {
|
|
2418
|
+
if (selectedTime !== validSelectedTime) {
|
|
2419
|
+
updateSelectedTime(validSelectedTime);
|
|
2420
|
+
}
|
|
2421
|
+
}, [selectedTime, updateSelectedTime, validSelectedTime]);
|
|
2328
2422
|
return jsx(SoundingsTimeSlider, {
|
|
2329
2423
|
timesteps: mergedTimesteps,
|
|
2330
|
-
selectedTime:
|
|
2424
|
+
selectedTime: validSelectedTime,
|
|
2331
2425
|
updateSelectedTime: updateSelectedTime
|
|
2332
2426
|
});
|
|
2333
2427
|
};
|
|
@@ -2416,9 +2510,84 @@ const MenuBarConnect = ({
|
|
|
2416
2510
|
});
|
|
2417
2511
|
};
|
|
2418
2512
|
|
|
2513
|
+
const soundingDiagrams = ['SKEWT', 'BIJVOET', 'EMAGRAM'];
|
|
2514
|
+
|
|
2515
|
+
const DiagramTypeButton = ({
|
|
2516
|
+
diagramType,
|
|
2517
|
+
setDiagramType
|
|
2518
|
+
}) => {
|
|
2519
|
+
const {
|
|
2520
|
+
t
|
|
2521
|
+
} = useSoundingsTranslation();
|
|
2522
|
+
const [anchorEl, setAnchorEl] = React.useState(null);
|
|
2523
|
+
const isOpen = Boolean(anchorEl);
|
|
2524
|
+
const handleClick = event => {
|
|
2525
|
+
setAnchorEl(anchorEl ? null : event.currentTarget);
|
|
2526
|
+
};
|
|
2527
|
+
return jsxs(CustomToggleButton, {
|
|
2528
|
+
variant: "tool",
|
|
2529
|
+
selected: isOpen,
|
|
2530
|
+
onClick: handleClick,
|
|
2531
|
+
"data-testid": "soundings-diagramtype-options-toolbutton",
|
|
2532
|
+
disableRipple: true,
|
|
2533
|
+
size: "small",
|
|
2534
|
+
sx: {
|
|
2535
|
+
height: '24px !important',
|
|
2536
|
+
width: '75px'
|
|
2537
|
+
},
|
|
2538
|
+
role: "button",
|
|
2539
|
+
children: [getDiagramName(t, diagramType), jsxs(Menu, {
|
|
2540
|
+
autoFocus: false,
|
|
2541
|
+
slotProps: {
|
|
2542
|
+
backdrop: {
|
|
2543
|
+
style: {
|
|
2544
|
+
opacity: 0,
|
|
2545
|
+
transition: 'none'
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
},
|
|
2549
|
+
open: isOpen,
|
|
2550
|
+
anchorEl: anchorEl,
|
|
2551
|
+
anchorOrigin: {
|
|
2552
|
+
vertical: 21,
|
|
2553
|
+
horizontal: 6
|
|
2554
|
+
},
|
|
2555
|
+
sx: {
|
|
2556
|
+
'& .MuiMenu-list': {
|
|
2557
|
+
padding: 0
|
|
2558
|
+
},
|
|
2559
|
+
'& .MuiMenuItem-root': {
|
|
2560
|
+
fontSize: 16
|
|
2561
|
+
}
|
|
2562
|
+
},
|
|
2563
|
+
children: [jsx(ListSubheader, {
|
|
2564
|
+
sx: {
|
|
2565
|
+
paddingTop: '8px'
|
|
2566
|
+
},
|
|
2567
|
+
children: jsx(Typography, {
|
|
2568
|
+
sx: {
|
|
2569
|
+
letterSpacing: '0.4px',
|
|
2570
|
+
fontSize: 12
|
|
2571
|
+
},
|
|
2572
|
+
children: t('options')
|
|
2573
|
+
})
|
|
2574
|
+
}), soundingDiagrams.map(id => jsx(MenuItem, {
|
|
2575
|
+
onClick: () => {
|
|
2576
|
+
setDiagramType(id);
|
|
2577
|
+
},
|
|
2578
|
+
selected: diagramType === id,
|
|
2579
|
+
children: getDiagramName(t, id)
|
|
2580
|
+
}, id))]
|
|
2581
|
+
})]
|
|
2582
|
+
});
|
|
2583
|
+
};
|
|
2584
|
+
|
|
2585
|
+
const size = 600;
|
|
2419
2586
|
const SoundingsConnect = ({
|
|
2420
|
-
viewId
|
|
2587
|
+
viewId,
|
|
2588
|
+
diagramType = 'SKEWT'
|
|
2421
2589
|
}) => {
|
|
2590
|
+
const [chosenDiagramType, setChosenDiagramType] = React.useState(diagramType);
|
|
2422
2591
|
const selectedTime = useSelector(getSelectedTime);
|
|
2423
2592
|
const sourceSelections = useSelector(getSelectedSources);
|
|
2424
2593
|
const locationSelections = useSelector(getLocationSelections);
|
|
@@ -2448,23 +2617,58 @@ const SoundingsConnect = ({
|
|
|
2448
2617
|
children: jsx(MenuBarConnect, {
|
|
2449
2618
|
viewId: viewId
|
|
2450
2619
|
})
|
|
2451
|
-
}),
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2620
|
+
}), jsxs(Card, {
|
|
2621
|
+
children: [jsx(Box, {
|
|
2622
|
+
sx: {
|
|
2623
|
+
display: 'flex',
|
|
2624
|
+
justifyContent: 'flex-end',
|
|
2625
|
+
paddingTop: '2px',
|
|
2626
|
+
paddingRight: '2px',
|
|
2627
|
+
width: size
|
|
2628
|
+
},
|
|
2629
|
+
children: jsx(DiagramTypeButton, {
|
|
2630
|
+
diagramType: chosenDiagramType,
|
|
2631
|
+
setDiagramType: setChosenDiagramType
|
|
2632
|
+
})
|
|
2633
|
+
}), jsx(Soundings, {
|
|
2634
|
+
data: data,
|
|
2635
|
+
svgDimensions: {
|
|
2636
|
+
width: size,
|
|
2637
|
+
height: size,
|
|
2638
|
+
margin: {
|
|
2639
|
+
bottom: 0,
|
|
2640
|
+
left: 0,
|
|
2641
|
+
right: 0,
|
|
2642
|
+
top: 0
|
|
2643
|
+
}
|
|
2644
|
+
},
|
|
2645
|
+
diagramType: chosenDiagramType
|
|
2646
|
+
})]
|
|
2463
2647
|
}), jsx(SoundingsTimeSliderConnect, {})]
|
|
2464
2648
|
})]
|
|
2465
2649
|
});
|
|
2466
2650
|
};
|
|
2467
2651
|
|
|
2652
|
+
const extractVerticalRange = (collection, selectedInstanceId) => {
|
|
2653
|
+
const instance = collection.instances.find(instance => instance.id === selectedInstanceId);
|
|
2654
|
+
const interval = instance?.extent?.vertical?.interval;
|
|
2655
|
+
if (interval) {
|
|
2656
|
+
// Fallback for FMI server while it sends a string array instead of string[][] as specified in documentation
|
|
2657
|
+
if (typeof interval[0] === 'string') {
|
|
2658
|
+
const verticalDims = interval.toSorted((a, b) => a.localeCompare(b, undefined, {
|
|
2659
|
+
numeric: true
|
|
2660
|
+
})) ?? [];
|
|
2661
|
+
return verticalDims.join('/');
|
|
2662
|
+
}
|
|
2663
|
+
const firstEl = interval[0][0];
|
|
2664
|
+
const lastEl = interval[interval.length - 1][1];
|
|
2665
|
+
const verticalDims = [firstEl, lastEl].toSorted((a, b) => a.localeCompare(b, undefined, {
|
|
2666
|
+
numeric: true
|
|
2667
|
+
})) ?? [];
|
|
2668
|
+
return verticalDims.join('/');
|
|
2669
|
+
}
|
|
2670
|
+
return '';
|
|
2671
|
+
};
|
|
2468
2672
|
const SoundingsSourceMenu = ({
|
|
2469
2673
|
soundingsCollections,
|
|
2470
2674
|
isOpen = false,
|
|
@@ -2527,11 +2731,15 @@ const SoundingsSourceMenu = ({
|
|
|
2527
2731
|
},
|
|
2528
2732
|
children: jsx(Checkbox, {
|
|
2529
2733
|
checked: !!isChecked,
|
|
2530
|
-
onChange: event =>
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2734
|
+
onChange: event => {
|
|
2735
|
+
const verticalRange = selectedInstanceId !== undefined ? extractVerticalRange(collection, selectedInstanceId) : '';
|
|
2736
|
+
updateCollection(collection.id, {
|
|
2737
|
+
url,
|
|
2738
|
+
isChecked: event.target.checked,
|
|
2739
|
+
instanceId: selectedInstanceId,
|
|
2740
|
+
verticalRange
|
|
2741
|
+
});
|
|
2742
|
+
},
|
|
2535
2743
|
disableRipple: true
|
|
2536
2744
|
})
|
|
2537
2745
|
}), jsxs(StyledSelect, {
|
|
@@ -2541,10 +2749,15 @@ const SoundingsSourceMenu = ({
|
|
|
2541
2749
|
},
|
|
2542
2750
|
skipLocalStyling: false,
|
|
2543
2751
|
value: selectedInstanceId || t('noInstancesAvailable'),
|
|
2544
|
-
onChange: event =>
|
|
2545
|
-
|
|
2546
|
-
instanceId
|
|
2547
|
-
|
|
2752
|
+
onChange: event => {
|
|
2753
|
+
const instanceId = event.target.value;
|
|
2754
|
+
const verticalRange = extractVerticalRange(collection, instanceId);
|
|
2755
|
+
updateCollection(collection.id, {
|
|
2756
|
+
url,
|
|
2757
|
+
instanceId,
|
|
2758
|
+
verticalRange
|
|
2759
|
+
});
|
|
2760
|
+
},
|
|
2548
2761
|
renderValue: value => jsx(Box, {
|
|
2549
2762
|
sx: {
|
|
2550
2763
|
textOverflow: 'ellipsis',
|
|
@@ -2820,7 +3033,8 @@ const soundingsComponentType = 'Soundings';
|
|
|
2820
3033
|
const soundingsLookUp = payload => {
|
|
2821
3034
|
if (payload.componentType === soundingsComponentType) {
|
|
2822
3035
|
return jsx(SoundingsConnect, {
|
|
2823
|
-
viewId: payload.id
|
|
3036
|
+
viewId: payload.id,
|
|
3037
|
+
diagramType: payload.diagramType ? payload.diagramType : 'SKEWT'
|
|
2824
3038
|
});
|
|
2825
3039
|
}
|
|
2826
3040
|
return null;
|
|
@@ -2854,4 +3068,4 @@ const createMockStore = mockState => configureStore({
|
|
|
2854
3068
|
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(storeMiddlewares)
|
|
2855
3069
|
});
|
|
2856
3070
|
|
|
2857
|
-
export { LocationsMenuConnect, SOUNDINGS_NAMESPACE, SoundingsConnect, SoundingsSourceDialogConnect, createMockStore, initSoundingsI18n, soundingsComponentType, soundingsLookUp, soundingsReducersMap, soundingsTranslations, translateKeyOutsideComponents, useSoundingsTranslation };
|
|
3071
|
+
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.8.0",
|
|
4
4
|
"description": "GeoWeb soundings library for the opengeoweb project",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -8,25 +8,26 @@
|
|
|
8
8
|
"url": "git@gitlab.com:opengeoweb/opengeoweb.git"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@opengeoweb/api": "12.
|
|
11
|
+
"@opengeoweb/api": "12.8.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.8.0",
|
|
18
18
|
"@mui/material": "^7.0.1",
|
|
19
|
-
"@opengeoweb/store": "12.
|
|
19
|
+
"@opengeoweb/store": "12.8.0",
|
|
20
20
|
"react-redux": "^9.2.0",
|
|
21
|
-
"@opengeoweb/theme": "12.
|
|
21
|
+
"@opengeoweb/theme": "12.8.0",
|
|
22
22
|
"@reduxjs/toolkit": "^2.6.1",
|
|
23
|
-
"@opengeoweb/timeslider": "12.
|
|
23
|
+
"@opengeoweb/timeslider": "12.8.0",
|
|
24
24
|
"react-window": "^1.8.10",
|
|
25
|
-
"@opengeoweb/webmap": "12.
|
|
26
|
-
"lodash": "^4.17.21"
|
|
25
|
+
"@opengeoweb/webmap": "12.8.0",
|
|
26
|
+
"lodash": "^4.17.21",
|
|
27
|
+
"@types/geojson": "^7946.0.14"
|
|
27
28
|
},
|
|
28
29
|
"module": "./index.esm.js",
|
|
29
30
|
"type": "module",
|
|
30
31
|
"main": "./index.esm.js",
|
|
31
|
-
"types": "./index.
|
|
32
|
+
"types": "./index.d.ts"
|
|
32
33
|
}
|
|
@@ -2,5 +2,5 @@ import { CoverageCollection } from '@opengeoweb/shared';
|
|
|
2
2
|
import { LocationsResponseFromBE, SoundingsInstances } from './types';
|
|
3
3
|
export declare const getCollectionLocations: (collectionId: string, baseUrl: string) => Promise<LocationsResponseFromBE>;
|
|
4
4
|
export declare const getCollectionInstances: (collectionId: string, baseUrl: string) => Promise<SoundingsInstances>;
|
|
5
|
-
export declare const getEdrCollectionForLocation: (collectionId: string, baseUrl: string, instanceId: string | undefined, locationId: string, parameters: string[], datetime: string) => Promise<CoverageCollection>;
|
|
6
|
-
export declare const getEdrCollectionForPosition: (collectionId: string, baseUrl: string, instanceId: string | undefined, coordinates: [number, number], parameters: string[], datetime: string) => Promise<CoverageCollection>;
|
|
5
|
+
export declare const getEdrCollectionForLocation: (collectionId: string, baseUrl: string, instanceId: string | undefined, locationId: string, parameters: string[], datetime: string, verticalRange: string) => Promise<CoverageCollection>;
|
|
6
|
+
export declare const getEdrCollectionForPosition: (collectionId: string, baseUrl: string, instanceId: string | undefined, coordinates: [number, number], parameters: string[], datetime: string, verticalRange: string) => Promise<CoverageCollection>;
|
|
@@ -3,6 +3,7 @@ import { SoundingFeature } from './types';
|
|
|
3
3
|
import { SourceType } from '../../store/types';
|
|
4
4
|
import { SoundingsCollectionsWithInstances } from '../../components/Soundings/types';
|
|
5
5
|
import { ServiceWithLocations } from '../../components/MenuBar/LocationsMenu';
|
|
6
|
+
export declare const sortSoundingFeatures: (data: SoundingFeature[]) => SoundingFeature[];
|
|
6
7
|
type Coordinates = [number, number];
|
|
7
8
|
export declare const defaultParameterMappings: {
|
|
8
9
|
temperature: string;
|
|
@@ -22,7 +23,11 @@ export declare const useMultipleSources: (sources: SourceType[], locations: Reco
|
|
|
22
23
|
isPending: boolean;
|
|
23
24
|
error: Error | null | undefined;
|
|
24
25
|
};
|
|
25
|
-
export declare const useTimesteps: (sourceSelections: SourceType[]) =>
|
|
26
|
+
export declare const useTimesteps: (sourceSelections: SourceType[]) => {
|
|
27
|
+
data: string[][];
|
|
28
|
+
error: Error | null;
|
|
29
|
+
isPending: boolean;
|
|
30
|
+
};
|
|
26
31
|
export declare const useGetCollectionsInstances: (collections?: SoundingsCollection[], isDialogOpen?: boolean) => {
|
|
27
32
|
data: SoundingsCollectionsWithInstances[] | undefined;
|
|
28
33
|
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;
|
package/src/lib/store/types.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export interface SourceType {
|
|
|
2
2
|
collectionId: string;
|
|
3
3
|
url: string;
|
|
4
4
|
instanceId?: string;
|
|
5
|
+
verticalRange?: string;
|
|
5
6
|
parameterMappings?: Record<string, string>;
|
|
6
7
|
}
|
|
7
8
|
export interface UpdateSourcePayload {
|
|
@@ -12,6 +13,7 @@ export interface SourceSelection {
|
|
|
12
13
|
url: string;
|
|
13
14
|
instanceId?: string;
|
|
14
15
|
isChecked?: boolean;
|
|
16
|
+
verticalRange?: string;
|
|
15
17
|
parameterMappings?: Record<string, string>;
|
|
16
18
|
}
|
|
17
19
|
export interface UpdateLocationPayload {
|
|
@@ -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;
|
|
File without changes
|