@opengeoweb/soundings 12.1.2 → 12.2.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 +1136 -6
- package/package.json +13 -2
- package/src/index.d.ts +3 -1
- package/src/lib/api/edr-api/api.d.ts +6 -0
- package/src/lib/api/edr-api/crss-types.d.ts +26 -0
- package/src/lib/api/edr-api/hooks.d.ts +4 -0
- package/src/lib/api/edr-api/types.d.ts +118 -0
- package/src/lib/components/DrawUtils.d.ts +40 -0
- package/src/lib/components/EDRFetchAndParse.stories.d.ts +7 -0
- package/src/lib/components/Soundings.d.ts +14 -0
- package/src/lib/components/Soundings.spec.d.ts +1 -0
- package/src/lib/components/Soundings.stories.d.ts +7 -0
- package/src/lib/components/SoundingsConnect.d.ts +5 -0
- package/src/lib/components/SoundingsMenuBar/LocationsMenu.d.ts +11 -0
- package/src/lib/components/SoundingsMenuBar/LocationsMenuConnect.d.ts +6 -0
- package/src/lib/components/SoundingsMenuBar/MenuBar.d.ts +11 -0
- package/src/lib/components/SoundingsMenuBar/MenuBar.stories.d.ts +26 -0
- package/src/lib/components/SoundingsMenuBar/MenuBarConnect.d.ts +8 -0
- package/src/lib/components/SoundingsMenuBar/MenuBarConnect.spec.d.ts +1 -0
- package/src/lib/components/SoundingsSourceDialogConnect.d.ts +7 -0
- package/src/lib/components/SoundingsSourceMenu.d.ts +9 -0
- package/src/lib/components/StaticLines/drawDryAdiabats.d.ts +11 -0
- package/src/lib/components/StaticLines/drawIsobars.d.ts +10 -0
- package/src/lib/components/StaticLines/drawIsohumes.d.ts +10 -0
- package/src/lib/components/StaticLines/drawIsotherms.d.ts +10 -0
- package/src/lib/components/StaticLines/drawMoistAdiabats.d.ts +16 -0
- package/src/lib/components/StaticLines/utils.d.ts +15 -0
- package/src/lib/components/index.d.ts +3 -0
- package/src/lib/components/types.d.ts +5 -0
- package/src/lib/i18n.d.ts +6 -0
- package/src/lib/parser/coverages.d.ts +2 -0
- package/src/lib/utils/conversions.d.ts +11 -0
- package/src/lib/utils/soundingsLookUp.d.ts +8 -0
- package/src/lib/utils/testHandlers.d.ts +1 -0
- package/src/lib/components/Sounding.d.ts +0 -2
- package/src/lib/components/Sounding.spec.d.ts +0 -0
- package/src/lib/components/soundings.stories.d.ts +0 -9
package/index.esm.js
CHANGED
|
@@ -1,10 +1,1140 @@
|
|
|
1
|
-
import { jsx } from 'react/jsx-runtime';
|
|
1
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
import React, { useRef, useEffect, useState, Fragment as Fragment$1, useMemo } from 'react';
|
|
3
|
+
import * as d3 from 'd3';
|
|
4
|
+
import { Card, List, ListItem, ListItemIcon, Checkbox, MenuItem, Box, ListItemText, ListSubheader, ListItemButton, Collapse, Button } from '@mui/material';
|
|
5
|
+
import { useSetupDialog, uiTypes, uiSelectors, uiActions } from '@opengeoweb/store';
|
|
6
|
+
import { ToolContainerDraggable, StyledSelect, dateUtils } from '@opengeoweb/shared';
|
|
7
|
+
import 'i18next';
|
|
8
|
+
import { useTranslation } from 'react-i18next';
|
|
9
|
+
import { getHeaders, handleResponse } from '@opengeoweb/api';
|
|
10
|
+
import { useQueries } from '@tanstack/react-query';
|
|
11
|
+
import { ChevronDown, Sources } from '@opengeoweb/theme';
|
|
12
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
2
13
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
14
|
+
/* *
|
|
15
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
16
|
+
* you may not use this file except in compliance with the License.
|
|
17
|
+
* You may obtain a copy of the License at
|
|
18
|
+
*
|
|
19
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
20
|
+
*
|
|
21
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
22
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
23
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
24
|
+
* See the License for the specific language governing permissions and
|
|
25
|
+
* limitations under the License.
|
|
26
|
+
*
|
|
27
|
+
* Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
|
|
28
|
+
* Copyright 2025 - Finnish Meteorological Institute (FMI)
|
|
29
|
+
* Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
|
|
30
|
+
* */
|
|
31
|
+
const SOUNDING_COLORS = ['#4487f3'];
|
|
32
|
+
// vfx code
|
|
33
|
+
const initializeSvg = ({
|
|
34
|
+
svgRef,
|
|
35
|
+
width,
|
|
36
|
+
height,
|
|
37
|
+
margin
|
|
38
|
+
}) => {
|
|
39
|
+
const svg = d3.select(svgRef.current).attr('width', width + (margin.left + margin.right)).attr('height', height + (margin.top + margin.bottom)).attr('id', 'skewT').attr('data-testid', 'skewT').append('g').attr('transform', `translate(${margin.left + margin.right},${margin.top})`);
|
|
40
|
+
svg.append('clipPath').attr('id', 'clip-skewTd3').append('rect').attr('width', width).attr('height', height).attr('x', 0).attr('y', 0);
|
|
41
|
+
svg.append('rect').attr('height', height).attr('width', width).attr('fill', 'white').attr('opacity', 1).attr('id', 'skewTd3').attr('clip-path', 'url(#clip-skewTd3)');
|
|
42
|
+
return svg;
|
|
43
|
+
};
|
|
44
|
+
const drawAxes = ({
|
|
45
|
+
svg,
|
|
46
|
+
xScale,
|
|
47
|
+
yScale,
|
|
48
|
+
height,
|
|
49
|
+
width
|
|
50
|
+
}) => {
|
|
51
|
+
// Create x-axis with intervals
|
|
52
|
+
svg.append('g').attr('transform', `translate(0,${height})`).call(d3.axisBottom(xScale).ticks(10)).append('text').attr('fill', '#000').attr('x', `${width - 8}px`).attr('y', `${12}px`).attr('dy', '1em').attr('font-size', '12px').attr('text-anchor', 'middle').text('°C');
|
|
53
|
+
// Create y-axis with intervals
|
|
54
|
+
svg.append('g').call(d3.axisLeft(yScale).ticks(0, '')).append('text').attr('fill', '#000').attr('transform', 'translate(0)').attr('y', `${0}px`).attr('x', `${ -16}px`).attr('font-size', '12px').attr('dy', '1em').attr('text-anchor', 'middle').text('hPa');
|
|
55
|
+
};
|
|
56
|
+
const addLegends = ({
|
|
57
|
+
svg,
|
|
58
|
+
width
|
|
59
|
+
}) => {
|
|
60
|
+
// Add a legend for the temperature line
|
|
61
|
+
svg.append('text').attr('x', width - 160).attr('y', 20).attr('fill', SOUNDING_COLORS[0]).text('Temperature (°C)');
|
|
62
|
+
// Add a legend for the dewpoint line
|
|
63
|
+
svg.append('text').attr('x', width - 137).attr('y', 40).attr('fill', SOUNDING_COLORS[0]).text('Dewpoint (°C)');
|
|
64
|
+
};
|
|
65
|
+
const plotSounding = ({
|
|
66
|
+
temperatureValues,
|
|
67
|
+
pressureValues,
|
|
68
|
+
color,
|
|
69
|
+
lineStyle,
|
|
70
|
+
xScale,
|
|
71
|
+
yScale,
|
|
72
|
+
height,
|
|
73
|
+
svg
|
|
74
|
+
}) => {
|
|
75
|
+
// Map temp and pressure to pixels
|
|
76
|
+
const Px = d3.zip(pressureValues, temperatureValues).map(([pressure, temperature]) => [xScale(temperature), yScale(pressure)]);
|
|
77
|
+
// Skew/shear transformation for skew-t
|
|
78
|
+
const skewedPx = Px.map(([x, y]) => [x + height - y, y]);
|
|
79
|
+
const lineGenerator = d3.line();
|
|
80
|
+
const pathString = lineGenerator(skewedPx);
|
|
81
|
+
svg.append('path').attr('d', pathString).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-skewTd3)');
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/* *
|
|
85
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
86
|
+
* you may not use this file except in compliance with the License.
|
|
87
|
+
* You may obtain a copy of the License at
|
|
88
|
+
*
|
|
89
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
90
|
+
*
|
|
91
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
92
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
93
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
94
|
+
* See the License for the specific language governing permissions and
|
|
95
|
+
* limitations under the License.
|
|
96
|
+
*
|
|
97
|
+
* Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
|
|
98
|
+
* Copyright 2025 - Finnish Meteorological Institute (FMI)
|
|
99
|
+
* Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
|
|
100
|
+
* */
|
|
101
|
+
const CP$1 = 1.03e3;
|
|
102
|
+
const Po = 1000.0;
|
|
103
|
+
const dryAdiabaticLapserate = (theta, pressure, temperature, dp) => {
|
|
104
|
+
const Tt = (theta + 273.15) * (Po / pressure) ** (-287 / CP$1) - 273.15;
|
|
105
|
+
const dt = Tt - temperature;
|
|
106
|
+
return [dp, dt];
|
|
107
|
+
};
|
|
108
|
+
const drawDryAdiabats = ({
|
|
109
|
+
staticPressureArray,
|
|
110
|
+
height,
|
|
111
|
+
xScale,
|
|
112
|
+
yScale,
|
|
113
|
+
svg,
|
|
114
|
+
dp
|
|
115
|
+
}) => {
|
|
116
|
+
for (let Tbase = -40; Tbase < 240; Tbase += 10) {
|
|
117
|
+
let Pnew = staticPressureArray[0];
|
|
118
|
+
let Tnew = Tbase;
|
|
119
|
+
const Px = [];
|
|
120
|
+
for (const pressure of staticPressureArray) {
|
|
121
|
+
const DPDT = dryAdiabaticLapserate(Tbase, Pnew, Tnew, dp);
|
|
122
|
+
Tnew += DPDT[1];
|
|
123
|
+
Pnew += DPDT[0];
|
|
124
|
+
Px.push([xScale(Tnew), yScale(pressure)]);
|
|
125
|
+
}
|
|
126
|
+
// Skew/shear transformation for skew-t
|
|
127
|
+
for (const PxElement of Px) {
|
|
128
|
+
PxElement[0] = PxElement[0] + height - PxElement[1];
|
|
129
|
+
}
|
|
130
|
+
const lineGenerator = d3.line();
|
|
131
|
+
const daPathString = lineGenerator(Px);
|
|
132
|
+
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-skewTd3)');
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/* *
|
|
137
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
138
|
+
* you may not use this file except in compliance with the License.
|
|
139
|
+
* You may obtain a copy of the License at
|
|
140
|
+
*
|
|
141
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
142
|
+
*
|
|
143
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
144
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
145
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
146
|
+
* See the License for the specific language governing permissions and
|
|
147
|
+
* limitations under the License.
|
|
148
|
+
*
|
|
149
|
+
* Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
|
|
150
|
+
* Copyright 2025 - Finnish Meteorological Institute (FMI)
|
|
151
|
+
* Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
|
|
152
|
+
* */
|
|
153
|
+
const isobarValues = [200, 300, 400, 500, 700, 850, 925, 1000];
|
|
154
|
+
const drawIsobars = ({
|
|
155
|
+
minTemperature,
|
|
156
|
+
maxTemperature,
|
|
157
|
+
xScale,
|
|
158
|
+
yScale,
|
|
159
|
+
svg
|
|
160
|
+
}) => {
|
|
161
|
+
// Calculate X and Y coordinates for each pressure level
|
|
162
|
+
for (const pressureVal of isobarValues) {
|
|
163
|
+
const Px = [];
|
|
164
|
+
const tempBounds = [minTemperature, maxTemperature];
|
|
165
|
+
const Ppx = yScale(pressureVal);
|
|
166
|
+
for (const bound of tempBounds) {
|
|
167
|
+
Px.push([xScale(bound), yScale(pressureVal)]);
|
|
168
|
+
}
|
|
169
|
+
const lineGenerator = d3.line();
|
|
170
|
+
const isoPathString = lineGenerator(Px);
|
|
171
|
+
const strID = `${pressureVal}textId`;
|
|
172
|
+
svg.append('path').attr('id', strID).attr('d', isoPathString).style('fill', 'none').style('stroke-width', 1.5).style('stroke', '#B5B51F').style('stroke-opacity', 0.5).attr('clip-path', 'url(#clip-skewTd3)');
|
|
173
|
+
svg.append('text').text(pressureVal).attr('text-anchor', 'middle').attr('x', `${ -24}px`).attr('y', Ppx + 5) // Y-coordinate of the label
|
|
174
|
+
.attr('fill', '#000').attr('font-size', '12px');
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/* *
|
|
179
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
180
|
+
* you may not use this file except in compliance with the License.
|
|
181
|
+
* You may obtain a copy of the License at
|
|
182
|
+
*
|
|
183
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
184
|
+
*
|
|
185
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
186
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
187
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
188
|
+
* See the License for the specific language governing permissions and
|
|
189
|
+
* limitations under the License.
|
|
190
|
+
*
|
|
191
|
+
* Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
|
|
192
|
+
* Copyright 2025 - Finnish Meteorological Institute (FMI)
|
|
193
|
+
* Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
|
|
194
|
+
* */
|
|
195
|
+
const isohumeValues = [0.001, 0.01, 0.1, 0.5, 1.0, 2.0, 5.0, 8.0, 12.0, 16.0, 20.0];
|
|
196
|
+
const drawIsohumes = ({
|
|
197
|
+
staticPressureArray,
|
|
198
|
+
height,
|
|
199
|
+
xScale,
|
|
200
|
+
yScale,
|
|
201
|
+
svg
|
|
202
|
+
}) => {
|
|
203
|
+
for (const isohumeVal of isohumeValues) {
|
|
204
|
+
const Px = [];
|
|
205
|
+
for (const pressureVal of staticPressureArray) {
|
|
206
|
+
const es = pressureVal * isohumeVal / (isohumeVal + 622.0);
|
|
207
|
+
const logthing = Math.log(es / 6.11);
|
|
208
|
+
const temp = 237.3 * logthing / (17.269 - logthing);
|
|
209
|
+
Px.push([xScale(temp), yScale(pressureVal)]);
|
|
210
|
+
}
|
|
211
|
+
// Skew/shear transformation for skew-t
|
|
212
|
+
for (const PxElement of Px) {
|
|
213
|
+
PxElement[0] = PxElement[0] + height - PxElement[1];
|
|
214
|
+
}
|
|
215
|
+
const lineGenerator = d3.line();
|
|
216
|
+
const pathStringQ = lineGenerator(Px);
|
|
217
|
+
const strID = `${isohumeVal}isohume`;
|
|
218
|
+
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-skewTd3)');
|
|
219
|
+
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');
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
/* *
|
|
224
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
225
|
+
* you may not use this file except in compliance with the License.
|
|
226
|
+
* You may obtain a copy of the License at
|
|
227
|
+
*
|
|
228
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
229
|
+
*
|
|
230
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
231
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
232
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
233
|
+
* See the License for the specific language governing permissions and
|
|
234
|
+
* limitations under the License.
|
|
235
|
+
*
|
|
236
|
+
* Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
|
|
237
|
+
* Copyright 2025 - Finnish Meteorological Institute (FMI)
|
|
238
|
+
* Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
|
|
239
|
+
* */
|
|
240
|
+
const drawIsotherms = ({
|
|
241
|
+
staticPressureArray,
|
|
242
|
+
height,
|
|
243
|
+
xScale,
|
|
244
|
+
yScale,
|
|
245
|
+
svg
|
|
246
|
+
}) => {
|
|
247
|
+
// lines of constant temperature (10 degree interval from -80...+40C)
|
|
248
|
+
for (let temp = -80; temp <= 40; temp += 10) {
|
|
249
|
+
const points = [];
|
|
250
|
+
for (const pressure of staticPressureArray) {
|
|
251
|
+
const Ppx = yScale(pressure);
|
|
252
|
+
const Tpx = xScale(temp);
|
|
253
|
+
const skewedX = Tpx + (height - Ppx);
|
|
254
|
+
points.push([skewedX, Ppx]);
|
|
255
|
+
}
|
|
256
|
+
const lineGenerator = d3.line();
|
|
257
|
+
const pathString = lineGenerator(points);
|
|
258
|
+
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-skewTd3)');
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
/* *
|
|
263
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
264
|
+
* you may not use this file except in compliance with the License.
|
|
265
|
+
* You may obtain a copy of the License at
|
|
266
|
+
*
|
|
267
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
268
|
+
*
|
|
269
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
270
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
271
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
272
|
+
* See the License for the specific language governing permissions and
|
|
273
|
+
* limitations under the License.
|
|
274
|
+
*
|
|
275
|
+
* Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
|
|
276
|
+
* Copyright 2025 - Finnish Meteorological Institute (FMI)
|
|
277
|
+
* Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
|
|
278
|
+
* */
|
|
279
|
+
const CP = 1.03e3;
|
|
280
|
+
const KELVIN = 273.15;
|
|
281
|
+
const L_CONST = 2.5e6;
|
|
282
|
+
const RD = 287.0;
|
|
283
|
+
const RV = 461.0;
|
|
284
|
+
const moistAdiabaticLapserate = (minTemp, pressure, temperature, dp) => {
|
|
285
|
+
const kelvin = temperature + KELVIN;
|
|
286
|
+
const lsbc = L_CONST / RV * (1.0 / KELVIN - 1.0 / kelvin);
|
|
287
|
+
const rw = 6.11 * Math.exp(lsbc) * (0.622 / pressure);
|
|
288
|
+
const lrwbt = L_CONST * rw / (RD * kelvin);
|
|
289
|
+
const nume = RD * kelvin / (CP * pressure) * (1.0 + lrwbt);
|
|
290
|
+
const deno = 1.0 + lrwbt * (0.622 * L_CONST / (CP * kelvin));
|
|
291
|
+
const gradi = nume / deno;
|
|
292
|
+
const dt = dp * gradi;
|
|
293
|
+
return [dp, dt];
|
|
294
|
+
};
|
|
295
|
+
const drawMoistAdiabats = ({
|
|
296
|
+
staticPressureArray,
|
|
297
|
+
minTemperature,
|
|
298
|
+
maxTemperature,
|
|
299
|
+
minPressure,
|
|
300
|
+
maxPressure,
|
|
301
|
+
width,
|
|
302
|
+
height,
|
|
303
|
+
xScale,
|
|
304
|
+
yScale,
|
|
305
|
+
svg,
|
|
306
|
+
dp
|
|
307
|
+
}) => {
|
|
308
|
+
/* We need the moist adiabat to cross the isotherm at 1000hPa
|
|
309
|
+
Therefore we start the calculations from 1000hPa and work upwards (towards 0)
|
|
310
|
+
then draw a separate line starting from 1000hPa working down (towards defined maxPressure) */
|
|
311
|
+
for (let Tbase = -40; Tbase < 240; Tbase += 10) {
|
|
312
|
+
let Pnew = 1000;
|
|
313
|
+
let Tnew = Tbase;
|
|
314
|
+
const Px = [];
|
|
315
|
+
// First calculate position of moist adiabat at a pressure of 1000hPa
|
|
316
|
+
const Temp = Tnew + -1 * minTemperature;
|
|
317
|
+
const Tpx1000 = width * Temp / (maxTemperature - minTemperature);
|
|
318
|
+
const Ppx1000 = height * (Math.log(Pnew) - Math.log(minPressure)) / (Math.log(maxPressure) - Math.log(minPressure));
|
|
319
|
+
Px.push([Tpx1000, Ppx1000]);
|
|
320
|
+
// Then calculate the rest of the moist adiabat for pressures <1000hPa
|
|
321
|
+
for (const pressure of staticPressureArray) {
|
|
322
|
+
if (pressure < 1000) {
|
|
323
|
+
const DPDT = moistAdiabaticLapserate(-80, Pnew, Tnew, dp);
|
|
324
|
+
Tnew += DPDT[1];
|
|
325
|
+
Pnew += DPDT[0];
|
|
326
|
+
Px.push([xScale(Tnew), yScale(pressure)]);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
for (const PxElement of Px) {
|
|
330
|
+
PxElement[0] = PxElement[0] + height - PxElement[1];
|
|
331
|
+
}
|
|
332
|
+
const lineGenerator = d3.line();
|
|
333
|
+
const maPathString = lineGenerator(Px);
|
|
334
|
+
// Draw this first section of the line
|
|
335
|
+
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-skewTd3)');
|
|
336
|
+
// Now work backwards from 1000hPa to calculate most adiabat until the bottom of the plot
|
|
337
|
+
const PxBottom = [];
|
|
338
|
+
PxBottom.push([Tpx1000, Ppx1000]);
|
|
339
|
+
let PnewBottom = 1000;
|
|
340
|
+
let TnewBottom = Tbase;
|
|
341
|
+
const startIndex = staticPressureArray.findIndex(value => value === 1000);
|
|
342
|
+
for (let i = startIndex - 1; i >= 0; i -= 1) {
|
|
343
|
+
const DPDT = moistAdiabaticLapserate(-80, PnewBottom, TnewBottom, 5);
|
|
344
|
+
TnewBottom += DPDT[1];
|
|
345
|
+
PnewBottom += DPDT[0];
|
|
346
|
+
PxBottom.push([xScale(TnewBottom), yScale(staticPressureArray[i])]);
|
|
347
|
+
}
|
|
348
|
+
// Skew/shear transformation for skew-t
|
|
349
|
+
for (const PxElement of PxBottom) {
|
|
350
|
+
PxElement[0] = PxElement[0] + height - PxElement[1];
|
|
351
|
+
}
|
|
352
|
+
const maPathStringBottom = lineGenerator(PxBottom);
|
|
353
|
+
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-skewTd3)');
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
/* *
|
|
358
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
359
|
+
* you may not use this file except in compliance with the License.
|
|
360
|
+
* You may obtain a copy of the License at
|
|
361
|
+
*
|
|
362
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
363
|
+
*
|
|
364
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
365
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
366
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
367
|
+
* See the License for the specific language governing permissions and
|
|
368
|
+
* limitations under the License.
|
|
369
|
+
*
|
|
370
|
+
* Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
|
|
371
|
+
* Copyright 2025 - Finnish Meteorological Institute (FMI)
|
|
372
|
+
* Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
|
|
373
|
+
* */
|
|
374
|
+
const drawStaticLines = ({
|
|
375
|
+
dp,
|
|
376
|
+
staticPressureArray,
|
|
377
|
+
minTemperature,
|
|
378
|
+
maxTemperature,
|
|
379
|
+
height,
|
|
380
|
+
width,
|
|
381
|
+
xScale,
|
|
382
|
+
yScale,
|
|
383
|
+
minPressure,
|
|
384
|
+
maxPressure,
|
|
385
|
+
svg
|
|
386
|
+
}) => {
|
|
387
|
+
drawIsobars({
|
|
388
|
+
minTemperature,
|
|
389
|
+
maxTemperature,
|
|
390
|
+
xScale,
|
|
391
|
+
yScale,
|
|
392
|
+
svg
|
|
393
|
+
});
|
|
394
|
+
drawIsotherms({
|
|
395
|
+
staticPressureArray,
|
|
396
|
+
height,
|
|
397
|
+
xScale,
|
|
398
|
+
yScale,
|
|
399
|
+
svg
|
|
400
|
+
});
|
|
401
|
+
drawIsohumes({
|
|
402
|
+
staticPressureArray,
|
|
403
|
+
height,
|
|
404
|
+
xScale,
|
|
405
|
+
yScale,
|
|
406
|
+
svg
|
|
407
|
+
});
|
|
408
|
+
drawDryAdiabats({
|
|
409
|
+
staticPressureArray,
|
|
410
|
+
height,
|
|
411
|
+
xScale,
|
|
412
|
+
yScale,
|
|
413
|
+
svg,
|
|
414
|
+
dp
|
|
7
415
|
});
|
|
416
|
+
drawMoistAdiabats({
|
|
417
|
+
staticPressureArray,
|
|
418
|
+
minTemperature,
|
|
419
|
+
maxTemperature,
|
|
420
|
+
minPressure,
|
|
421
|
+
maxPressure,
|
|
422
|
+
width,
|
|
423
|
+
height,
|
|
424
|
+
xScale,
|
|
425
|
+
yScale,
|
|
426
|
+
svg,
|
|
427
|
+
dp
|
|
428
|
+
});
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
const Soundings = ({
|
|
432
|
+
data: {
|
|
433
|
+
features
|
|
434
|
+
},
|
|
435
|
+
svgDimensions
|
|
436
|
+
// tooltip
|
|
437
|
+
}) => {
|
|
438
|
+
const svgRef = useRef(null);
|
|
439
|
+
const svgDimensionsOptions = {
|
|
440
|
+
width: svgDimensions?.width || 812,
|
|
441
|
+
height: svgDimensions?.height || 802,
|
|
442
|
+
margin: {
|
|
443
|
+
top: svgDimensions?.margin?.top || 12,
|
|
444
|
+
right: svgDimensions?.margin?.right || 20,
|
|
445
|
+
bottom: svgDimensions?.margin?.bottom || 25,
|
|
446
|
+
left: svgDimensions?.margin?.left || 20
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
// Set SVG dimensions
|
|
450
|
+
const margin = {
|
|
451
|
+
top: svgDimensionsOptions?.margin?.top,
|
|
452
|
+
right: svgDimensionsOptions?.margin?.right,
|
|
453
|
+
bottom: svgDimensionsOptions?.margin?.bottom,
|
|
454
|
+
left: svgDimensionsOptions?.margin?.left
|
|
455
|
+
};
|
|
456
|
+
const baseWidth = svgDimensionsOptions?.width;
|
|
457
|
+
const baseHeight = svgDimensionsOptions?.height;
|
|
458
|
+
const width = baseWidth - (margin.left + margin.right);
|
|
459
|
+
const height = baseHeight - (margin.top + margin.bottom);
|
|
460
|
+
useEffect(() => {
|
|
461
|
+
if (!svgRef.current) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
// Define svg
|
|
465
|
+
const svg = initializeSvg({
|
|
466
|
+
svgRef,
|
|
467
|
+
width,
|
|
468
|
+
height,
|
|
469
|
+
margin
|
|
470
|
+
});
|
|
471
|
+
// Define pressure and temperature values for static lines
|
|
472
|
+
const maxPressure = 1050;
|
|
473
|
+
const minPressure = 150;
|
|
474
|
+
const staticPressureArray = [maxPressure];
|
|
475
|
+
const dp = -5;
|
|
476
|
+
const minTemperature = -40;
|
|
477
|
+
const maxTemperature = 40;
|
|
478
|
+
let pressure = maxPressure;
|
|
479
|
+
while (pressure > minPressure) {
|
|
480
|
+
pressure += dp;
|
|
481
|
+
staticPressureArray.push(pressure);
|
|
482
|
+
}
|
|
483
|
+
// Set scales for temperature and pressure
|
|
484
|
+
const xScale = d3.scaleLinear().domain([minTemperature, maxTemperature]).range([0, width]);
|
|
485
|
+
const yScale = d3.scaleLog().domain([maxPressure, minPressure]).range([height, 0]);
|
|
486
|
+
// Add axis'
|
|
487
|
+
drawAxes({
|
|
488
|
+
svg,
|
|
489
|
+
xScale,
|
|
490
|
+
yScale,
|
|
491
|
+
height,
|
|
492
|
+
width
|
|
493
|
+
});
|
|
494
|
+
// Draw static lines
|
|
495
|
+
drawStaticLines({
|
|
496
|
+
dp,
|
|
497
|
+
staticPressureArray,
|
|
498
|
+
minTemperature,
|
|
499
|
+
maxTemperature,
|
|
500
|
+
height,
|
|
501
|
+
width,
|
|
502
|
+
xScale,
|
|
503
|
+
yScale,
|
|
504
|
+
minPressure,
|
|
505
|
+
maxPressure,
|
|
506
|
+
svg
|
|
507
|
+
});
|
|
508
|
+
// Add legends
|
|
509
|
+
addLegends({
|
|
510
|
+
svg,
|
|
511
|
+
width
|
|
512
|
+
});
|
|
513
|
+
const temperatureValues = features.map(feature => feature.properties.temp);
|
|
514
|
+
const pressureValues = features.map(feature => feature.properties.pressure);
|
|
515
|
+
// Draw Temperature
|
|
516
|
+
plotSounding({
|
|
517
|
+
temperatureValues,
|
|
518
|
+
pressureValues,
|
|
519
|
+
color: SOUNDING_COLORS[0],
|
|
520
|
+
lineStyle: 'none',
|
|
521
|
+
xScale,
|
|
522
|
+
yScale,
|
|
523
|
+
height,
|
|
524
|
+
svg
|
|
525
|
+
});
|
|
526
|
+
const dewpointValues = features.map(feature => feature.properties.dewpoint);
|
|
527
|
+
// Dewpoint
|
|
528
|
+
plotSounding({
|
|
529
|
+
temperatureValues: dewpointValues,
|
|
530
|
+
color: SOUNDING_COLORS[0],
|
|
531
|
+
pressureValues,
|
|
532
|
+
lineStyle: '8, 2',
|
|
533
|
+
xScale,
|
|
534
|
+
yScale,
|
|
535
|
+
height,
|
|
536
|
+
svg
|
|
537
|
+
});
|
|
538
|
+
// eslint-disable-next-line consistent-return
|
|
539
|
+
return () => {
|
|
540
|
+
svg.selectAll('*').remove();
|
|
541
|
+
// svg.on('mousemove', null)
|
|
542
|
+
};
|
|
543
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
544
|
+
}, []);
|
|
545
|
+
return jsx(Card, {
|
|
546
|
+
children: jsx("svg", {
|
|
547
|
+
ref: svgRef
|
|
548
|
+
})
|
|
549
|
+
});
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
/* *
|
|
553
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
554
|
+
* you may not use this file except in compliance with the License.
|
|
555
|
+
* You may obtain a copy of the License at
|
|
556
|
+
*
|
|
557
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
558
|
+
*
|
|
559
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
560
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
561
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
562
|
+
* See the License for the specific language governing permissions and
|
|
563
|
+
* limitations under the License.
|
|
564
|
+
*
|
|
565
|
+
* Copyright 2023 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
|
|
566
|
+
* Copyright 2023 - Finnish Meteorological Institute (FMI)
|
|
567
|
+
* Copyright 2024 - The Norwegian Meteorological Institute (MET Norway)
|
|
568
|
+
* */
|
|
569
|
+
const SOUNDINGS_NAMESPACE = 'soundings';
|
|
570
|
+
const useSoundingsTranslation = () => useTranslation(SOUNDINGS_NAMESPACE);
|
|
571
|
+
|
|
572
|
+
const SoundingsSourceMenu = ({
|
|
573
|
+
soundingsCollections,
|
|
574
|
+
isOpen = false,
|
|
575
|
+
handleClose
|
|
576
|
+
}) => {
|
|
577
|
+
const {
|
|
578
|
+
t
|
|
579
|
+
} = useSoundingsTranslation();
|
|
580
|
+
const [currentSelection, setCurrentSelection] = React.useState({});
|
|
581
|
+
React.useEffect(() => {
|
|
582
|
+
const initialSelection = {};
|
|
583
|
+
soundingsCollections?.forEach(collection => {
|
|
584
|
+
const initialInstanceId = collection.instances.length > 0 ? collection.instances[0].id : undefined;
|
|
585
|
+
initialSelection[collection.id] = {
|
|
586
|
+
isChecked: false,
|
|
587
|
+
selectedInstanceId: initialInstanceId
|
|
588
|
+
};
|
|
589
|
+
});
|
|
590
|
+
setCurrentSelection(initialSelection);
|
|
591
|
+
}, [soundingsCollections]);
|
|
592
|
+
// TODO move to redux
|
|
593
|
+
const handleSelectionChange = (collectionId, update) => {
|
|
594
|
+
setCurrentSelection(prev => {
|
|
595
|
+
const currentCollectionState = prev[collectionId] || {};
|
|
596
|
+
return {
|
|
597
|
+
...prev,
|
|
598
|
+
[collectionId]: {
|
|
599
|
+
...currentCollectionState,
|
|
600
|
+
...update
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
});
|
|
604
|
+
};
|
|
605
|
+
const getDisplayValue = (collection, instanceId) => {
|
|
606
|
+
if (!instanceId) {
|
|
607
|
+
return jsx(Fragment, {
|
|
608
|
+
children: t('noInstancesAvailable')
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
const instanceIndex = collection.instances.findIndex(i => i.id === instanceId);
|
|
612
|
+
if (instanceIndex === 0) {
|
|
613
|
+
return jsxs(Box, {
|
|
614
|
+
display: "flex",
|
|
615
|
+
children: [jsx(Box, {
|
|
616
|
+
sx: {
|
|
617
|
+
minWidth: 200
|
|
618
|
+
},
|
|
619
|
+
children: collection.name
|
|
620
|
+
}), jsx(Box, {
|
|
621
|
+
sx: {
|
|
622
|
+
textOverflow: 'ellipsis',
|
|
623
|
+
overflow: 'hidden'
|
|
624
|
+
},
|
|
625
|
+
children: t('latest')
|
|
626
|
+
})]
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
return jsxs(Box, {
|
|
630
|
+
display: "flex",
|
|
631
|
+
children: [jsx(Box, {
|
|
632
|
+
sx: {
|
|
633
|
+
minWidth: 200
|
|
634
|
+
},
|
|
635
|
+
children: collection.name
|
|
636
|
+
}), jsx(Box, {
|
|
637
|
+
sx: {
|
|
638
|
+
textOverflow: 'ellipsis',
|
|
639
|
+
overflow: 'hidden'
|
|
640
|
+
},
|
|
641
|
+
children: dateUtils.dateToIsoString(dateUtils.isoStringToDate(instanceId))
|
|
642
|
+
})]
|
|
643
|
+
});
|
|
644
|
+
};
|
|
645
|
+
return jsx(ToolContainerDraggable, {
|
|
646
|
+
startSize: {
|
|
647
|
+
width: 320
|
|
648
|
+
},
|
|
649
|
+
minWidth: 300,
|
|
650
|
+
title: t('sources'),
|
|
651
|
+
isOpen: isOpen,
|
|
652
|
+
onClose: () => handleClose(),
|
|
653
|
+
bounds: "parent",
|
|
654
|
+
children: jsx(List, {
|
|
655
|
+
sx: {
|
|
656
|
+
pr: 1
|
|
657
|
+
},
|
|
658
|
+
children: soundingsCollections && soundingsCollections.length > 0 ? soundingsCollections.map(collection => {
|
|
659
|
+
const currentCollectionState = currentSelection[collection.id];
|
|
660
|
+
const isChecked = currentCollectionState?.isChecked ?? false;
|
|
661
|
+
const selectedInstanceId = currentCollectionState?.selectedInstanceId ?? ''; // Use empty string for Select value if undefined
|
|
662
|
+
const hasInstances = collection.instances.length > 0;
|
|
663
|
+
return jsxs(ListItem, {
|
|
664
|
+
disablePadding: true,
|
|
665
|
+
children: [jsx(ListItemIcon, {
|
|
666
|
+
sx: {
|
|
667
|
+
minWidth: 42
|
|
668
|
+
},
|
|
669
|
+
children: jsx(Checkbox, {
|
|
670
|
+
checked: isChecked,
|
|
671
|
+
onChange: event => handleSelectionChange(collection.id, {
|
|
672
|
+
isChecked: event.target.checked
|
|
673
|
+
}),
|
|
674
|
+
disableRipple: true
|
|
675
|
+
})
|
|
676
|
+
}), jsxs(StyledSelect, {
|
|
677
|
+
isEnabled: true,
|
|
678
|
+
skipLocalStyling: false,
|
|
679
|
+
value: selectedInstanceId,
|
|
680
|
+
onChange: event => handleSelectionChange(collection.id, {
|
|
681
|
+
selectedInstanceId: event.target.value
|
|
682
|
+
}),
|
|
683
|
+
renderValue: value => jsx(Box, {
|
|
684
|
+
sx: {
|
|
685
|
+
textOverflow: 'ellipsis',
|
|
686
|
+
overflow: 'hidden'
|
|
687
|
+
},
|
|
688
|
+
children: getDisplayValue(collection, value)
|
|
689
|
+
}),
|
|
690
|
+
children: [!hasInstances && jsx(MenuItem, {
|
|
691
|
+
value: "",
|
|
692
|
+
disabled: true,
|
|
693
|
+
sx: {
|
|
694
|
+
fontSize: 12
|
|
695
|
+
},
|
|
696
|
+
children: t('noInstancesAvailable')
|
|
697
|
+
}), collection.instances.map((instance, index) => jsx(MenuItem, {
|
|
698
|
+
value: instance.id,
|
|
699
|
+
children: index === 0 ? t('latest') : dateUtils.dateToIsoString(dateUtils.isoStringToDate(instance.id)) || instance.id
|
|
700
|
+
}, instance.id))]
|
|
701
|
+
})]
|
|
702
|
+
}, collection.id);
|
|
703
|
+
}) : jsx(ListItem, {
|
|
704
|
+
children: jsx(ListItemText, {
|
|
705
|
+
primary: t('noServicesFound'),
|
|
706
|
+
sx: {
|
|
707
|
+
pl: 4
|
|
708
|
+
}
|
|
709
|
+
})
|
|
710
|
+
})
|
|
711
|
+
})
|
|
712
|
+
});
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
/* *
|
|
716
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
717
|
+
* you may not use this file except in compliance with the License.
|
|
718
|
+
* You may obtain a copy of the License at
|
|
719
|
+
*
|
|
720
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
721
|
+
*
|
|
722
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
723
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
724
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
725
|
+
* See the License for the specific language governing permissions and
|
|
726
|
+
* limitations under the License.
|
|
727
|
+
*
|
|
728
|
+
* Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
|
|
729
|
+
* Copyright 2025 - Finnish Meteorological Institute (FMI)
|
|
730
|
+
* Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
|
|
731
|
+
* */
|
|
732
|
+
const BASE_URL = 'https://opendata.fmi.fi/edr';
|
|
733
|
+
const getCollectionLocations = collectionId => fetch(`${BASE_URL}/collections/${collectionId}/locations`, {
|
|
734
|
+
headers: getHeaders()
|
|
735
|
+
}).then(handleResponse);
|
|
736
|
+
const getCollectionInstance = (baseURL, collectionId) => fetch(`${baseURL}/collections/${collectionId}/instances`, {
|
|
737
|
+
headers: getHeaders()
|
|
738
|
+
}).then(handleResponse);
|
|
739
|
+
|
|
740
|
+
const SoundingsSourceDialogConnect = ({
|
|
741
|
+
soundingsCollections
|
|
742
|
+
}) => {
|
|
743
|
+
const [soundingsCollectionsWithInstances, setSoundingsCollectionsWithInstances] = React.useState();
|
|
744
|
+
const {
|
|
745
|
+
isDialogOpen,
|
|
746
|
+
onCloseDialog
|
|
747
|
+
} = useSetupDialog(uiTypes.DialogTypes.SoundingsSources);
|
|
748
|
+
React.useEffect(() => {
|
|
749
|
+
if (soundingsCollections && isDialogOpen) {
|
|
750
|
+
try {
|
|
751
|
+
void Promise.all(soundingsCollections.map(async collection => {
|
|
752
|
+
const data = await getCollectionInstance(collection.url, collection.id);
|
|
753
|
+
return {
|
|
754
|
+
...collection,
|
|
755
|
+
instances: data.instances.reverse()
|
|
756
|
+
}; // Reverse to get latest first
|
|
757
|
+
})).then(results => setSoundingsCollectionsWithInstances(results));
|
|
758
|
+
} catch (error) {
|
|
759
|
+
console.error('Error fetching soundings instances:', error);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}, [soundingsCollections, isDialogOpen]);
|
|
763
|
+
return jsx(SoundingsSourceMenu, {
|
|
764
|
+
soundingsCollections: soundingsCollectionsWithInstances,
|
|
765
|
+
isOpen: isDialogOpen,
|
|
766
|
+
handleClose: () => onCloseDialog()
|
|
767
|
+
});
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
const LocationsMenu = ({
|
|
771
|
+
services,
|
|
772
|
+
isOpen,
|
|
773
|
+
onClose
|
|
774
|
+
}) => {
|
|
775
|
+
const [collapseMap, setCollapseMap] = useState({});
|
|
776
|
+
const {
|
|
777
|
+
t
|
|
778
|
+
} = useSoundingsTranslation();
|
|
779
|
+
return jsx(ToolContainerDraggable, {
|
|
780
|
+
startSize: {
|
|
781
|
+
width: 320,
|
|
782
|
+
height: 500
|
|
783
|
+
},
|
|
784
|
+
title: t('locations'),
|
|
785
|
+
headerSize: "medium",
|
|
786
|
+
isOpen: isOpen,
|
|
787
|
+
onClose: () => onClose(),
|
|
788
|
+
bounds: "parent",
|
|
789
|
+
children: jsx(List, {
|
|
790
|
+
disablePadding: true,
|
|
791
|
+
sx: {
|
|
792
|
+
maxHeight: '100%',
|
|
793
|
+
overflowY: 'auto'
|
|
794
|
+
},
|
|
795
|
+
children: services.map(service => jsxs(Fragment$1, {
|
|
796
|
+
children: [jsx(ListSubheader, {
|
|
797
|
+
disableGutters: true,
|
|
798
|
+
sx: {
|
|
799
|
+
borderBottom: '1px solid'
|
|
800
|
+
},
|
|
801
|
+
children: jsxs(ListItemButton, {
|
|
802
|
+
onClick: () => setCollapseMap({
|
|
803
|
+
...collapseMap,
|
|
804
|
+
[service.id]: !collapseMap[service.id]
|
|
805
|
+
}),
|
|
806
|
+
children: [jsx(ListItemText, {
|
|
807
|
+
children: service.id
|
|
808
|
+
}), jsx(ListItemIcon, {
|
|
809
|
+
children: jsx(ChevronDown, {})
|
|
810
|
+
})]
|
|
811
|
+
})
|
|
812
|
+
}), jsx(Collapse, {
|
|
813
|
+
in: collapseMap[service.id],
|
|
814
|
+
children: jsx(List, {
|
|
815
|
+
component: "div",
|
|
816
|
+
disablePadding: true,
|
|
817
|
+
sx: {
|
|
818
|
+
overflowY: 'auto',
|
|
819
|
+
overflow: 'hidden'
|
|
820
|
+
},
|
|
821
|
+
children: service.features?.map(location => jsx(ListItem, {
|
|
822
|
+
sx: {
|
|
823
|
+
pl: 4
|
|
824
|
+
},
|
|
825
|
+
children: location.properties.name
|
|
826
|
+
}, `${service.id}-${location.id}`))
|
|
827
|
+
})
|
|
828
|
+
})]
|
|
829
|
+
}, service.id))
|
|
830
|
+
})
|
|
831
|
+
});
|
|
832
|
+
};
|
|
833
|
+
|
|
834
|
+
const LocationsMenuConnect = ({
|
|
835
|
+
soundingsCollections
|
|
836
|
+
}) => {
|
|
837
|
+
const {
|
|
838
|
+
isDialogOpen,
|
|
839
|
+
onCloseDialog
|
|
840
|
+
} = useSetupDialog(uiTypes.DialogTypes.soundingsLocations);
|
|
841
|
+
const edrQueries = useMemo(() => soundingsCollections.map(collection => ({
|
|
842
|
+
queryKey: ['edr-locations', collection.id],
|
|
843
|
+
queryFn: async () => {
|
|
844
|
+
const locations = await getCollectionLocations(collection.id);
|
|
845
|
+
return {
|
|
846
|
+
...locations,
|
|
847
|
+
id: collection.id
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
})), [soundingsCollections]);
|
|
851
|
+
const services = useQueries({
|
|
852
|
+
queries: edrQueries,
|
|
853
|
+
combine: results => results.map(result => result.data).filter(result => !!result)
|
|
854
|
+
});
|
|
855
|
+
return jsx(LocationsMenu, {
|
|
856
|
+
isOpen: isDialogOpen,
|
|
857
|
+
onClose: onCloseDialog,
|
|
858
|
+
services: services
|
|
859
|
+
});
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
var SoundingFeatures = [
|
|
863
|
+
{
|
|
864
|
+
type: "Feature",
|
|
865
|
+
geometry: {
|
|
866
|
+
type: "Point",
|
|
867
|
+
coordinates: [
|
|
868
|
+
24.9727,
|
|
869
|
+
60.3294
|
|
870
|
+
]
|
|
871
|
+
},
|
|
872
|
+
properties: {
|
|
873
|
+
time: "2025-02-05T00:00:00Z",
|
|
874
|
+
temp: -4.74961,
|
|
875
|
+
dewpoint: -5.646749999999998,
|
|
876
|
+
pressure: 1000,
|
|
877
|
+
windSpeed: 9.291457267156751,
|
|
878
|
+
windDirection: -18.803465916723095
|
|
879
|
+
}
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
type: "Feature",
|
|
883
|
+
geometry: {
|
|
884
|
+
type: "Point",
|
|
885
|
+
coordinates: [
|
|
886
|
+
24.9727,
|
|
887
|
+
60.3294
|
|
888
|
+
]
|
|
889
|
+
},
|
|
890
|
+
properties: {
|
|
891
|
+
time: "2025-02-05T00:00:00Z",
|
|
892
|
+
temp: -5.72214,
|
|
893
|
+
dewpoint: -5.740760000000001,
|
|
894
|
+
pressure: 925,
|
|
895
|
+
windSpeed: 11.482972536081412,
|
|
896
|
+
windDirection: 18.797638010822038
|
|
897
|
+
}
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
type: "Feature",
|
|
901
|
+
geometry: {
|
|
902
|
+
type: "Point",
|
|
903
|
+
coordinates: [
|
|
904
|
+
24.9727,
|
|
905
|
+
60.3294
|
|
906
|
+
]
|
|
907
|
+
},
|
|
908
|
+
properties: {
|
|
909
|
+
time: "2025-02-05T00:00:00Z",
|
|
910
|
+
temp: -5.94461,
|
|
911
|
+
dewpoint: -6.284489999999999,
|
|
912
|
+
pressure: 850,
|
|
913
|
+
windSpeed: 10.355155067433804,
|
|
914
|
+
windDirection: 46.95919904362452
|
|
915
|
+
}
|
|
916
|
+
},
|
|
917
|
+
{
|
|
918
|
+
type: "Feature",
|
|
919
|
+
geometry: {
|
|
920
|
+
type: "Point",
|
|
921
|
+
coordinates: [
|
|
922
|
+
24.9727,
|
|
923
|
+
60.3294
|
|
924
|
+
]
|
|
925
|
+
},
|
|
926
|
+
properties: {
|
|
927
|
+
time: "2025-02-05T00:00:00Z",
|
|
928
|
+
temp: -7.85121,
|
|
929
|
+
dewpoint: -7.903229999999999,
|
|
930
|
+
pressure: 700,
|
|
931
|
+
windSpeed: 10.216260991987234,
|
|
932
|
+
windDirection: 125.33681135255785
|
|
933
|
+
}
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
type: "Feature",
|
|
937
|
+
geometry: {
|
|
938
|
+
type: "Point",
|
|
939
|
+
coordinates: [
|
|
940
|
+
24.9727,
|
|
941
|
+
60.3294
|
|
942
|
+
]
|
|
943
|
+
},
|
|
944
|
+
properties: {
|
|
945
|
+
time: "2025-02-05T00:00:00Z",
|
|
946
|
+
temp: -20.9667,
|
|
947
|
+
dewpoint: -22.12218,
|
|
948
|
+
pressure: 500,
|
|
949
|
+
windSpeed: 14.222681508365433,
|
|
950
|
+
windDirection: 120.16238041101973
|
|
951
|
+
}
|
|
952
|
+
},
|
|
953
|
+
{
|
|
954
|
+
type: "Feature",
|
|
955
|
+
geometry: {
|
|
956
|
+
type: "Point",
|
|
957
|
+
coordinates: [
|
|
958
|
+
24.9727,
|
|
959
|
+
60.3294
|
|
960
|
+
]
|
|
961
|
+
},
|
|
962
|
+
properties: {
|
|
963
|
+
time: "2025-02-05T00:00:00Z",
|
|
964
|
+
temp: -48.8678,
|
|
965
|
+
dewpoint: -60.802800000000005,
|
|
966
|
+
pressure: 300,
|
|
967
|
+
windSpeed: 23.436991331013456,
|
|
968
|
+
windDirection: 128.0547684118523
|
|
969
|
+
}
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
type: "Feature",
|
|
973
|
+
geometry: {
|
|
974
|
+
type: "Point",
|
|
975
|
+
coordinates: [
|
|
976
|
+
24.9727,
|
|
977
|
+
60.3294
|
|
978
|
+
]
|
|
979
|
+
},
|
|
980
|
+
properties: {
|
|
981
|
+
time: "2025-02-05T00:00:00Z",
|
|
982
|
+
temp: -58.3,
|
|
983
|
+
dewpoint: -69.68567999999999,
|
|
984
|
+
pressure: 250,
|
|
985
|
+
windSpeed: 28.155356371568097,
|
|
986
|
+
windDirection: 133.50317473958455
|
|
987
|
+
}
|
|
988
|
+
},
|
|
989
|
+
{
|
|
990
|
+
type: "Feature",
|
|
991
|
+
geometry: {
|
|
992
|
+
type: "Point",
|
|
993
|
+
coordinates: [
|
|
994
|
+
24.9727,
|
|
995
|
+
60.3294
|
|
996
|
+
]
|
|
997
|
+
},
|
|
998
|
+
properties: {
|
|
999
|
+
time: "2025-02-05T00:00:00Z",
|
|
1000
|
+
temp: -68.6166,
|
|
1001
|
+
dewpoint: -71.9641,
|
|
1002
|
+
pressure: 200,
|
|
1003
|
+
windSpeed: 24.833678713593763,
|
|
1004
|
+
windDirection: 137.492458365928
|
|
1005
|
+
}
|
|
1006
|
+
},
|
|
1007
|
+
{
|
|
1008
|
+
type: "Feature",
|
|
1009
|
+
geometry: {
|
|
1010
|
+
type: "Point",
|
|
1011
|
+
coordinates: [
|
|
1012
|
+
24.9727,
|
|
1013
|
+
60.3294
|
|
1014
|
+
]
|
|
1015
|
+
},
|
|
1016
|
+
properties: {
|
|
1017
|
+
time: "2025-02-05T00:00:00Z",
|
|
1018
|
+
temp: -69.4935,
|
|
1019
|
+
dewpoint: -87.827772,
|
|
1020
|
+
pressure: 50,
|
|
1021
|
+
windSpeed: 35.1450619746217,
|
|
1022
|
+
windDirection: 129.8915278264372
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
];
|
|
1026
|
+
|
|
1027
|
+
var MenuOptions;
|
|
1028
|
+
(function (MenuOptions) {
|
|
1029
|
+
MenuOptions["sources"] = "sources";
|
|
1030
|
+
MenuOptions["locations"] = "locations";
|
|
1031
|
+
})(MenuOptions || (MenuOptions = {}));
|
|
1032
|
+
const SoundingsMenuBar = ({
|
|
1033
|
+
handleClick,
|
|
1034
|
+
isOpen
|
|
1035
|
+
}) => {
|
|
1036
|
+
const {
|
|
1037
|
+
t
|
|
1038
|
+
} = useSoundingsTranslation();
|
|
1039
|
+
return jsxs(Box, {
|
|
1040
|
+
display: "flex",
|
|
1041
|
+
gap: 1,
|
|
1042
|
+
children: [jsx(Button, {
|
|
1043
|
+
variant: isOpen[MenuOptions.sources] ? 'primary' : 'tertiary',
|
|
1044
|
+
disableRipple: true,
|
|
1045
|
+
"aria-pressed": isOpen[MenuOptions.sources],
|
|
1046
|
+
"data-testid": "soundingsSourcesButton",
|
|
1047
|
+
onClick: () => handleClick(MenuOptions.sources),
|
|
1048
|
+
startIcon: jsx(Sources, {}),
|
|
1049
|
+
size: "small",
|
|
1050
|
+
children: t('sources')
|
|
1051
|
+
}), jsx(Button, {
|
|
1052
|
+
variant: isOpen[MenuOptions.locations] ? 'primary' : 'tertiary',
|
|
1053
|
+
disableRipple: true,
|
|
1054
|
+
"data-testid": "soundingsLocationsButton",
|
|
1055
|
+
"aria-pressed": isOpen[MenuOptions.locations],
|
|
1056
|
+
onClick: () => handleClick(MenuOptions.locations),
|
|
1057
|
+
size: "small",
|
|
1058
|
+
children: t('locations')
|
|
1059
|
+
})]
|
|
1060
|
+
});
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
const MenuBarConnect = ({
|
|
1064
|
+
viewId,
|
|
1065
|
+
source
|
|
1066
|
+
}) => {
|
|
1067
|
+
const dispatch = useDispatch();
|
|
1068
|
+
const currentActiveMapId = useSelector(store => uiSelectors.getDialogMapId(store, uiTypes.DialogTypes.SoundingsSources));
|
|
1069
|
+
const isSourcesOpenInStore = useSelector(store => uiSelectors.getisDialogOpen(store, uiTypes.DialogTypes.SoundingsSources));
|
|
1070
|
+
const isLocationsOpenInStore = useSelector(store => uiSelectors.getisDialogOpen(store, uiTypes.DialogTypes.soundingsLocations));
|
|
1071
|
+
const dispatchActiveMapId = React.useCallback((dialogType, isOpen) => void dispatch(uiActions.setActiveMapIdForDialog({
|
|
1072
|
+
type: dialogType,
|
|
1073
|
+
mapId: viewId,
|
|
1074
|
+
setOpen: currentActiveMapId !== viewId ? true : !isOpen,
|
|
1075
|
+
source
|
|
1076
|
+
})), [dispatch, currentActiveMapId, viewId, source]);
|
|
1077
|
+
const handleClick = React.useCallback(menuType => {
|
|
1078
|
+
switch (menuType) {
|
|
1079
|
+
case MenuOptions.sources:
|
|
1080
|
+
dispatchActiveMapId(uiTypes.DialogTypes.SoundingsSources, isSourcesOpenInStore);
|
|
1081
|
+
break;
|
|
1082
|
+
case MenuOptions.locations:
|
|
1083
|
+
dispatchActiveMapId(uiTypes.DialogTypes.soundingsLocations, isLocationsOpenInStore);
|
|
1084
|
+
break;
|
|
1085
|
+
}
|
|
1086
|
+
}, [dispatchActiveMapId, isSourcesOpenInStore, isLocationsOpenInStore]);
|
|
1087
|
+
const isOpen = {
|
|
1088
|
+
[MenuOptions.sources]: currentActiveMapId === viewId && isSourcesOpenInStore,
|
|
1089
|
+
[MenuOptions.locations]: currentActiveMapId === viewId && isLocationsOpenInStore
|
|
1090
|
+
};
|
|
1091
|
+
return jsx(SoundingsMenuBar, {
|
|
1092
|
+
isOpen: isOpen,
|
|
1093
|
+
handleClick: handleClick
|
|
1094
|
+
});
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1097
|
+
const SoundingsConnect = ({
|
|
1098
|
+
viewId
|
|
1099
|
+
}) => {
|
|
1100
|
+
return jsxs(Box, {
|
|
1101
|
+
sx: {
|
|
1102
|
+
padding: 2
|
|
1103
|
+
},
|
|
1104
|
+
children: [jsx(Box, {
|
|
1105
|
+
paddingLeft: 5,
|
|
1106
|
+
children: jsx(MenuBarConnect, {
|
|
1107
|
+
viewId: viewId
|
|
1108
|
+
})
|
|
1109
|
+
}), jsx(Soundings
|
|
1110
|
+
// TODO: use real data
|
|
1111
|
+
, {
|
|
1112
|
+
// TODO: use real data
|
|
1113
|
+
data: {
|
|
1114
|
+
features: SoundingFeatures
|
|
1115
|
+
},
|
|
1116
|
+
svgDimensions: {
|
|
1117
|
+
width: 600,
|
|
1118
|
+
height: 600,
|
|
1119
|
+
margin: {
|
|
1120
|
+
bottom: 0,
|
|
1121
|
+
left: 0,
|
|
1122
|
+
right: 0,
|
|
1123
|
+
top: 0
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
})]
|
|
1127
|
+
});
|
|
1128
|
+
};
|
|
1129
|
+
|
|
1130
|
+
const soundingsComponentType = 'Soundings';
|
|
1131
|
+
const soundingsLookUp = payload => {
|
|
1132
|
+
if (payload.componentType === soundingsComponentType) {
|
|
1133
|
+
return jsx(SoundingsConnect, {
|
|
1134
|
+
viewId: payload.id
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
return null;
|
|
8
1138
|
};
|
|
9
1139
|
|
|
10
|
-
export {
|
|
1140
|
+
export { LocationsMenuConnect, Soundings, SoundingsSourceDialogConnect, soundingsComponentType, soundingsLookUp };
|