@opengeoweb/soundings 12.2.0 → 12.3.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 +1159 -345
- package/package.json +8 -8
- package/src/index.d.ts +4 -0
- package/src/lib/api/edr-api/api.d.ts +2 -2
- package/src/lib/api/edr-api/hooks.d.ts +3 -2
- package/src/lib/components/DrawUtils.d.ts +1 -2
- package/src/lib/components/MenuBar/LocationsMenu.d.ts +13 -0
- package/src/lib/components/{SoundingsMenuBar → MenuBar}/MenuBar.d.ts +2 -1
- package/src/lib/components/MenuBar/MenuBar.stories.d.ts +20 -0
- package/src/lib/components/MenuBar/SoundingsSourceMenu.d.ts +12 -0
- package/src/lib/components/Providers/Providers.d.ts +12 -0
- package/src/lib/components/SoundingsConnect.d.ts +1 -0
- package/src/lib/components/StaticLines/drawIsobars.d.ts +1 -0
- package/src/lib/components/WindBarbs/utils.d.ts +25 -0
- package/src/lib/components/WindBarbs/windsymbols.d.ts +5 -0
- package/src/lib/components/index.d.ts +2 -2
- package/src/lib/i18n.d.ts +1 -0
- package/src/lib/store/reducer.d.ts +7 -0
- package/src/lib/store/selectors.d.ts +105 -0
- package/src/lib/store/selectors.spec.d.ts +1 -0
- package/src/lib/store/store.d.ts +9 -0
- package/src/lib/store/types.d.ts +25 -0
- package/src/lib/components/SoundingsMenuBar/LocationsMenu.d.ts +0 -11
- package/src/lib/components/SoundingsMenuBar/MenuBar.stories.d.ts +0 -26
- package/src/lib/components/SoundingsSourceMenu.d.ts +0 -9
- /package/src/lib/{utils/testHandlers.d.ts → api/edr-api/fakeApi.d.ts} +0 -0
- /package/src/lib/components/{SoundingsMenuBar → MenuBar}/LocationsMenuConnect.d.ts +0 -0
- /package/src/lib/components/{SoundingsMenuBar → MenuBar}/MenuBarConnect.d.ts +0 -0
- /package/src/lib/components/{SoundingsMenuBar → MenuBar}/MenuBarConnect.spec.d.ts +0 -0
- /package/src/lib/components/{SoundingsSourceDialogConnect.d.ts → MenuBar/SoundingsSourceDialogConnect.d.ts} +0 -0
package/index.esm.js
CHANGED
|
@@ -1,15 +1,95 @@
|
|
|
1
|
-
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import React, { useRef, useEffect, useState, Fragment
|
|
1
|
+
import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
|
|
2
|
+
import React, { useRef, useEffect, useState, Fragment, useMemo } from 'react';
|
|
3
3
|
import * as d3 from 'd3';
|
|
4
|
-
import { Card, List, ListItem, ListItemIcon, Checkbox, MenuItem, Box, ListItemText, ListSubheader, ListItemButton, Collapse,
|
|
5
|
-
import { useSetupDialog, uiTypes, uiSelectors, uiActions } from '@opengeoweb/store';
|
|
6
|
-
import {
|
|
7
|
-
import '
|
|
8
|
-
import
|
|
4
|
+
import { Card, List, ListItem, ListItemIcon, Checkbox, MenuItem, Box, ListItemText, Typography, ListSubheader, ListItemButton, Collapse, Stack, LinearProgress } from '@mui/material';
|
|
5
|
+
import { selectorMemoizationOptions, useSetupDialog, uiTypes, uiSelectors, uiActions, uiReducer, mapReducer } from '@opengeoweb/store';
|
|
6
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
7
|
+
import { ToolContainerDraggable, StyledSelect, dateUtils, CustomToggleButton, AlertBanner } from '@opengeoweb/shared';
|
|
8
|
+
import i18n from 'i18next';
|
|
9
|
+
export { default as i18n } from 'i18next';
|
|
10
|
+
import { useTranslation, initReactI18next } from 'react-i18next';
|
|
9
11
|
import { getHeaders, handleResponse } from '@opengeoweb/api';
|
|
10
|
-
import {
|
|
12
|
+
import { createSlice, createSelector, combineReducers, configureStore } from '@reduxjs/toolkit';
|
|
13
|
+
import { useQueries, useQuery } from '@tanstack/react-query';
|
|
11
14
|
import { ChevronDown, Sources } from '@opengeoweb/theme';
|
|
12
|
-
|
|
15
|
+
|
|
16
|
+
var en = {
|
|
17
|
+
sources: "Sources",
|
|
18
|
+
latest: "Latest",
|
|
19
|
+
noInstancesAvailable: "No instances found",
|
|
20
|
+
noServicesFound: "No source available",
|
|
21
|
+
locations: "Locations",
|
|
22
|
+
"locations-menu-warning": "You have to choose your sources in the sources menu"
|
|
23
|
+
};
|
|
24
|
+
var no = {
|
|
25
|
+
sources: "Kilder",
|
|
26
|
+
latest: "Siste",
|
|
27
|
+
noInstancesAvailable: "Ingen instanser funnet",
|
|
28
|
+
noServicesFound: "Ingen tjenester funnet",
|
|
29
|
+
locations: "Lokasjoner",
|
|
30
|
+
"locations-menu-warning": "Du må velge kilder i kilde-menyen"
|
|
31
|
+
};
|
|
32
|
+
var nl = {
|
|
33
|
+
sources: "Bronnen",
|
|
34
|
+
latest: "Nieuwste",
|
|
35
|
+
noInstancesAvailable: "Geen exemplaren gevonden",
|
|
36
|
+
noServicesFound: "Geen bron beschikbaar",
|
|
37
|
+
locations: "Locaties",
|
|
38
|
+
"locations-menu-warning": "Selecteer eerst een bron in het bron menu"
|
|
39
|
+
};
|
|
40
|
+
var fi = {
|
|
41
|
+
sources: "Lähteet",
|
|
42
|
+
latest: "Viimeisin",
|
|
43
|
+
noInstancesAvailable: "Tapauksia ei löytynyt",
|
|
44
|
+
noServicesFound: "Lähdettä ei ole saatavilla",
|
|
45
|
+
locations: "Locations",
|
|
46
|
+
"locations-menu-warning": "Valitse ensin lähde lähdevalikosta"
|
|
47
|
+
};
|
|
48
|
+
var soundingsTranslations = {
|
|
49
|
+
en: en,
|
|
50
|
+
no: no,
|
|
51
|
+
nl: nl,
|
|
52
|
+
fi: fi
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/* *
|
|
56
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
57
|
+
* you may not use this file except in compliance with the License.
|
|
58
|
+
* You may obtain a copy of the License at
|
|
59
|
+
*
|
|
60
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
61
|
+
*
|
|
62
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
63
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
64
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
65
|
+
* See the License for the specific language governing permissions and
|
|
66
|
+
* limitations under the License.
|
|
67
|
+
*
|
|
68
|
+
* Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
|
|
69
|
+
* Copyright 2025 - Finnish Meteorological Institute (FMI)
|
|
70
|
+
* Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
|
|
71
|
+
* */
|
|
72
|
+
const isobarValues = [200, 300, 400, 500, 700, 850, 925, 1000];
|
|
73
|
+
const drawIsobars = ({
|
|
74
|
+
minTemperature,
|
|
75
|
+
maxTemperature,
|
|
76
|
+
xScale,
|
|
77
|
+
yScale,
|
|
78
|
+
svg
|
|
79
|
+
}) => {
|
|
80
|
+
// Calculate X and Y coordinates for each pressure level
|
|
81
|
+
for (const pressureVal of isobarValues) {
|
|
82
|
+
const Px = [];
|
|
83
|
+
const tempBounds = [minTemperature, maxTemperature];
|
|
84
|
+
for (const bound of tempBounds) {
|
|
85
|
+
Px.push([xScale(bound), yScale(pressureVal)]);
|
|
86
|
+
}
|
|
87
|
+
const lineGenerator = d3.line();
|
|
88
|
+
const isoPathString = lineGenerator(Px);
|
|
89
|
+
const strID = `${pressureVal}textId`;
|
|
90
|
+
svg.append('path').attr('id', strID).attr('type', 'isobar').attr('d', isoPathString).style('fill', 'none').style('stroke-width', 1.5).style('stroke', '#B5B51F').style('stroke-opacity', 0.5);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
13
93
|
|
|
14
94
|
/* *
|
|
15
95
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -38,20 +118,33 @@ const initializeSvg = ({
|
|
|
38
118
|
}) => {
|
|
39
119
|
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
120
|
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', '
|
|
121
|
+
svg.append('rect').attr('height', height).attr('width', width).attr('fill', 'none').attr('opacity', 1).attr('id', 'skewTd3').attr('clip-path', 'url(#clip-skewTd3)');
|
|
42
122
|
return svg;
|
|
43
123
|
};
|
|
44
124
|
const drawAxes = ({
|
|
45
125
|
svg,
|
|
46
126
|
xScale,
|
|
47
127
|
yScale,
|
|
48
|
-
height
|
|
49
|
-
width
|
|
128
|
+
height
|
|
50
129
|
}) => {
|
|
51
130
|
// Create x-axis with intervals
|
|
52
|
-
|
|
131
|
+
const xTicks = xScale.ticks(10);
|
|
132
|
+
const xMiddleValue = xTicks[Math.floor(xTicks.length / 2)];
|
|
133
|
+
svg.append('g').attr('type', 'x-axis').attr('transform', `translate(0,${height})`).call(d3.axisBottom(xScale).tickValues(xTicks).tickSizeInner(0).tickSizeOuter(0));
|
|
134
|
+
// Reformat in the case of the middle tick label
|
|
135
|
+
// to add the unit part
|
|
136
|
+
svg.selectAll('g[type="x-axis"] .tick').filter(function () {
|
|
137
|
+
return parseInt(d3.select(this).select('text').text(), 10) === xMiddleValue;
|
|
138
|
+
}).each(function () {
|
|
139
|
+
const unitPart = '°C';
|
|
140
|
+
d3.select(this).select('text')
|
|
141
|
+
// tspan necessary to use a different font-size
|
|
142
|
+
.append('tspan').text(unitPart).attr('font-size', '11px').attr('dy', '0.04em');
|
|
143
|
+
});
|
|
53
144
|
// Create y-axis with intervals
|
|
54
|
-
|
|
145
|
+
// Make sure all zeroes are printed (1000 vs 1k)
|
|
146
|
+
const formatTicks = d3.format('.0f');
|
|
147
|
+
svg.append('g').call(d3.axisLeft(yScale).tickValues(isobarValues).tickFormat(formatTicks).tickSizeInner(0).tickSizeOuter(0)).call(g => g.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');
|
|
55
148
|
};
|
|
56
149
|
const addLegends = ({
|
|
57
150
|
svg,
|
|
@@ -81,6 +174,493 @@ const plotSounding = ({
|
|
|
81
174
|
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
175
|
};
|
|
83
176
|
|
|
177
|
+
/* *
|
|
178
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
179
|
+
* you may not use this file except in compliance with the License.
|
|
180
|
+
* You may obtain a copy of the License at
|
|
181
|
+
*
|
|
182
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
183
|
+
*
|
|
184
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
185
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
186
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
187
|
+
* See the License for the specific language governing permissions and
|
|
188
|
+
* limitations under the License.
|
|
189
|
+
*
|
|
190
|
+
* Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
|
|
191
|
+
* Copyright 2025 - Finnish Meteorological Institute (FMI)
|
|
192
|
+
* Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
|
|
193
|
+
* */
|
|
194
|
+
const windSymbolDefinitions = {
|
|
195
|
+
calm: `<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
196
|
+
<path d="M25 45C36.0456 45 45 36.0456 45 25C45 13.9543 36.0456 5 25 5C13.9543 5 5 13.9543 5 25C5 36.0456 13.9543 45 25 45Z" stroke="black" stroke-width="1.8"/>
|
|
197
|
+
</svg>
|
|
198
|
+
`,
|
|
199
|
+
lightAir: `<svg width="14" height="48" viewBox="0 0 14 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
200
|
+
<path d="M7.22656 4.89307V48.0001M7.22656 43.0262V44.6842" stroke="black" stroke-width="1.8"/>
|
|
201
|
+
</svg>
|
|
202
|
+
`,
|
|
203
|
+
fiveKnots: `<svg width="15" height="49" viewBox="0 0 15 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
204
|
+
<path d="M8.26953 5.57324V48.608M8.26953 40.5757L0.269531 42.2309" stroke="black" stroke-width="1.8"/>
|
|
205
|
+
</svg>`,
|
|
206
|
+
tenKnots: `<svg width="24" height="49" viewBox="0 0 24 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
207
|
+
<path d="M17.0781 4.64502V44.9031L1.07812 47.9999" stroke="black" stroke-width="1.8"/>
|
|
208
|
+
</svg>`,
|
|
209
|
+
fifteenKnots: `<svg width="23" height="50" viewBox="0 0 23 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
210
|
+
<path d="M16.4727 5.55469V45.8128L0.472656 48.9095M16.4727 38.0708L9.61551 39.6192" stroke="black" stroke-width="1.8"/>
|
|
211
|
+
</svg>
|
|
212
|
+
`,
|
|
213
|
+
twentyKnots: `<svg width="24" height="49" viewBox="0 0 24 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
214
|
+
<path d="M16.8555 4.71924V44.9773L0.855469 48.0741M16.8555 38.4093L0.855469 41.5061" stroke="black" stroke-width="1.8"/>
|
|
215
|
+
</svg>`,
|
|
216
|
+
twentyFiveKnots: `<svg width="23" height="51" viewBox="0 0 23 51" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
217
|
+
<path d="M16.2383 4.94434V46.1541L0.238281 49.3241M16.2383 40.3787L0.238281 43.5486M16.2383 33.9801L9.38114 35.5651" stroke="black" stroke-width="1.8"/>
|
|
218
|
+
</svg>`,
|
|
219
|
+
thirtyKnots: `<svg width="23" height="50" viewBox="0 0 23 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
220
|
+
<path d="M16.1914 5.64502V45.9031L0.191406 48.9999M16.1914 40.3459L0.191406 43.4427M16.1914 34.9407L0.191406 38.0375" stroke="black" stroke-width="1.8"/>
|
|
221
|
+
</svg>`,
|
|
222
|
+
thirtyFiveKnots: `<svg width="19" height="50" viewBox="0 0 19 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
223
|
+
<path d="M12.0028 4.96729V45.2253L1.16406 48.3221M12.0028 39.6443L1.16406 42.7411M12.0028 34.4793L1.16406 37.576M12.0028 29.2735L7.35761 30.8219" stroke="black" stroke-width="1.8"/>
|
|
224
|
+
</svg>`,
|
|
225
|
+
fortyKnots: `<svg width="24" height="50" viewBox="0 0 24 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
226
|
+
<path d="M16.5391 5.40311V45.6612L0.539062 48.7579M16.5391 40.4525L0.539062 43.5493M16.5391 35.0449L0.539062 38.1417M16.5391 30.2009L0.539062 33.2976" stroke="black" stroke-width="1.8"/>
|
|
227
|
+
</svg>`,
|
|
228
|
+
fortyFiveKnots: `<svg width="23" height="50" viewBox="0 0 23 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
229
|
+
<path d="M16.4492 5.4043V45.6624L0.449219 48.7591M16.4492 40.1513L0.449219 43.2481M16.4492 34.7124L0.449219 37.8091M16.4492 29.2013L0.449219 32.2981M16.4492 24.3066L9.59208 25.855" stroke="black" stroke-width="1.8"/>
|
|
230
|
+
</svg>`,
|
|
231
|
+
fiftyKnots: `<svg width="24" height="49" viewBox="0 0 24 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
232
|
+
<path d="M16.9062 5.27002V45.5281L0.90625 48.6249L16.9062 37.7861" fill="black"/>
|
|
233
|
+
<path d="M16.9053 5.27002V45.5281L4.35132 47.2337L16.9053 37.7861" stroke="black" stroke-width="1.8"/>
|
|
234
|
+
</svg>
|
|
235
|
+
`,
|
|
236
|
+
fiftyFiveKnots: `<svg width="24" height="49" viewBox="0 0 24 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
237
|
+
<path d="M16.728 5.55371V45.8118L0.894531 48.9085L16.728 38.0698" fill="black"/>
|
|
238
|
+
<path d="M16.73 5.55371V45.8118L4.33984 47.5245L16.73 38.0698" stroke="black" stroke-width="1.8"/>
|
|
239
|
+
<path d="M16.7292 32.7358C13.6376 33.3406 8.8125 34.2842 8.8125 34.2842" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
240
|
+
</svg>`,
|
|
241
|
+
sixtyKnots: `<svg width="23" height="49" viewBox="0 0 23 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
242
|
+
<path d="M16.2891 5.27051V45.5286L0.289062 48.6253L16.2891 37.7866" fill="black"/>
|
|
243
|
+
<path d="M16.2897 5.27051V45.5286L3.7422 47.2478L16.2897 37.7866" stroke="black" stroke-width="1.8"/>
|
|
244
|
+
<path d="M16.2891 34.6899L0.289062 37.7867" stroke="black" stroke-width="1.8"/>
|
|
245
|
+
</svg>`,
|
|
246
|
+
sixtyFiveKnots: `<svg width="24" height="47" viewBox="0 0 24 47" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
247
|
+
<path d="M17 3.26807V43.5261L1 46.6229L17 35.7842" fill="black"/>
|
|
248
|
+
<path d="M16.9984 3.26807V43.5261L4.35938 45.2985L16.9984 35.7842" stroke="black" stroke-width="1.8"/>
|
|
249
|
+
<path d="M17 32.0788L1 35.1756M17 26.8862L9.5 28.5" stroke="black" stroke-width="1.8"/>
|
|
250
|
+
</svg>`,
|
|
251
|
+
seventyKnots: `<svg width="24" height="47" viewBox="0 0 24 47" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
252
|
+
<path d="M17 3.26807V43.5261L1 46.6229L17 35.7842" fill="black"/>
|
|
253
|
+
<path d="M16.9984 3.26807V43.5261L4.35938 45.2985L16.9984 35.7842" stroke="black" stroke-width="1.8"/>
|
|
254
|
+
<path d="M17 32.0788L1 35.1756M17 26.8862L1 29.983" stroke="black" stroke-width="1.8"/>
|
|
255
|
+
</svg>`,
|
|
256
|
+
seventyFiveKnots: `<svg width="24" height="47" viewBox="0 0 24 47" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
257
|
+
<path d="M17 3.26807V43.5261L1 46.6229L17 35.7842" fill="black"/>
|
|
258
|
+
<path d="M16.9984 3.26807V43.5261L4.35938 45.2985L16.9984 35.7842" stroke="black" stroke-width="1.8"/>
|
|
259
|
+
<path d="M17 32.0788L1 35.1756M17 26.8862L1 29.983" stroke="black" stroke-width="1.8"/>
|
|
260
|
+
<path d="M16.9167 22C13.8251 22.6048 9 23.5484 9 23.5484" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
261
|
+
</svg>`,
|
|
262
|
+
eightyKnots: `<svg width="24" height="47" viewBox="0 0 24 47" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
263
|
+
<path d="M17 3.26807V43.5261L1 46.6229L17 35.7842" fill="black"/>
|
|
264
|
+
<path d="M16.9984 3.26807V43.5261L4.35938 45.2985L16.9984 35.7842" stroke="black" stroke-width="1.8"/>
|
|
265
|
+
<path d="M17 32.0788L1 35.1756M17 26.8862L1 29.983" stroke="black" stroke-width="1.8"/>
|
|
266
|
+
<path d="M17 22C10.7518 23.1718 1 25 1 25" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
267
|
+
</svg>`,
|
|
268
|
+
eightyFiveKnots: `<svg width="24" height="47" viewBox="0 0 24 47" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
269
|
+
<path d="M17 3.26807V43.5261L1 46.6229L17 35.7842" fill="black"/>
|
|
270
|
+
<path d="M16.9984 3.26807V43.5261L4.35938 45.2985L16.9984 35.7842" stroke="black" stroke-width="1.8"/>
|
|
271
|
+
<path d="M16.9167 17C13.8251 17.6048 9 18.5484 9 18.5484" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
272
|
+
<path d="M17 32.0788L1 35.1756M17 26.8862L1 29.983" stroke="black" stroke-width="1.8"/>
|
|
273
|
+
<path d="M17 22C10.7518 23.1718 1 25 1 25" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
274
|
+
</svg>
|
|
275
|
+
`,
|
|
276
|
+
ninetyKnots: `<svg width="24" height="47" viewBox="0 0 24 47" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
277
|
+
<path d="M17 3.26807V43.5261L1 46.6229L17 35.7842" fill="black"/>
|
|
278
|
+
<path d="M16.9984 3.26807V43.5261L4.35938 45.2985L16.9984 35.7842" stroke="black" stroke-width="1.8"/>
|
|
279
|
+
<path d="M17 32.0788L1 35.1756M17 26.8862L1 29.983" stroke="black" stroke-width="1.8"/>
|
|
280
|
+
<path d="M17 22C10.7518 23.1718 1 25 1 25" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
281
|
+
<path d="M17 17C10.7518 18.1718 1 20 1 20" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
282
|
+
</svg>
|
|
283
|
+
`,
|
|
284
|
+
ninetyFiveKnots: `<svg width="24" height="47" viewBox="0 0 24 47" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
285
|
+
<path d="M17 3.26807V43.5261L1 46.6229L17 35.7842" fill="black"/>
|
|
286
|
+
<path d="M16.9984 3.26807V43.5261L4.35938 45.2985L16.9984 35.7842" stroke="black" stroke-width="1.8"/>
|
|
287
|
+
<path d="M17 32.0788L1 35.1756M17 26.8862L1 29.983" stroke="black" stroke-width="1.8"/>
|
|
288
|
+
<path d="M17 22C10.7518 23.1718 1 25 1 25" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
289
|
+
<path d="M17 17C10.7518 18.1718 1 20 1 20" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
290
|
+
<path d="M16.9167 12C13.8251 12.6048 9 13.5484 9 13.5484" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
291
|
+
</svg>`,
|
|
292
|
+
hundredKnots: `<svg width="24" height="47" viewBox="0 0 24 47" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
293
|
+
<path d="M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842" fill="black"/>
|
|
294
|
+
<path d="M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34" stroke="black" stroke-width="1.8"/>
|
|
295
|
+
</svg>
|
|
296
|
+
`,
|
|
297
|
+
hundredFiveKnots: `<svg width="24" height="47" viewBox="0 0 24 47" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
298
|
+
<path d="M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842" fill="black"/>
|
|
299
|
+
<path d="M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34" stroke="black" stroke-width="1.8"/>
|
|
300
|
+
<path d="M16.9167 25C13.8251 25.6048 9 26.5484 9 26.5484" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
301
|
+
</svg>
|
|
302
|
+
`,
|
|
303
|
+
hundredTenKnots: `<svg width="24" height="47" viewBox="0 0 24 47" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
304
|
+
<path d="M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842" fill="black"/>
|
|
305
|
+
<path d="M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34" stroke="black" stroke-width="1.8"/>
|
|
306
|
+
<path d="M17 25C10.7518 26.1718 1 28 1 28" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
307
|
+
</svg>`,
|
|
308
|
+
hundredFifteenKnots: `<svg width="24" height="47" viewBox="0 0 24 47" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
309
|
+
<path d="M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842" fill="black"/>
|
|
310
|
+
<path d="M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34" stroke="black" stroke-width="1.8"/>
|
|
311
|
+
<path d="M17 25C10.7518 26.1718 1 28 1 28" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
312
|
+
<path d="M16.9167 21C13.8251 21.6048 9 22.5484 9 22.5484" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
313
|
+
</svg>`,
|
|
314
|
+
hundredTwentyKnots: `<svg width="24" height="47" viewBox="0 0 24 47" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
315
|
+
<path d="M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842" fill="black"/>
|
|
316
|
+
<path d="M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34" stroke="black" stroke-width="1.8"/>
|
|
317
|
+
<path d="M17 25C10.7518 26.1718 1 28 1 28" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
318
|
+
<path d="M17 21C10.7518 22.1718 1 24 1 24" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
319
|
+
</svg>`,
|
|
320
|
+
hundredTwentyFiveKnots: `<svg width="24" height="47" viewBox="0 0 24 47" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
321
|
+
<path d="M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842" fill="black"/>
|
|
322
|
+
<path d="M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34" stroke="black" stroke-width="1.8"/>
|
|
323
|
+
<path d="M17 25C10.7518 26.1718 1 28 1 28" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
324
|
+
<path d="M17 21C10.7518 22.1718 1 24 1 24" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
325
|
+
<path d="M16.9167 17C13.8251 17.6048 9 18.5484 9 18.5484" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
326
|
+
</svg>
|
|
327
|
+
`,
|
|
328
|
+
hundredThirtyKnots: `<svg width="24" height="47" viewBox="0 0 24 47" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
329
|
+
<path d="M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842" fill="black"/>
|
|
330
|
+
<path d="M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34" stroke="black" stroke-width="1.8"/>
|
|
331
|
+
<path d="M17 25C10.7518 26.1718 1 28 1 28" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
332
|
+
<path d="M17 21C10.7518 22.1718 1 24 1 24" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
333
|
+
<path d="M17 17C10.7518 18.1718 1 20 1 20" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
334
|
+
</svg>`,
|
|
335
|
+
hundredThirtyFiveKnots: `<svg width="24" height="47" viewBox="0 0 24 47" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
336
|
+
<path d="M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842" fill="black"/>
|
|
337
|
+
<path d="M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34" stroke="black" stroke-width="1.8"/>
|
|
338
|
+
<path d="M17 25C10.7518 26.1718 1 28 1 28" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
339
|
+
<path d="M17 21C10.7518 22.1718 1 24 1 24" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
340
|
+
<path d="M17 17C10.7518 18.1718 1 20 1 20" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
341
|
+
<path d="M16.9167 13C13.8251 13.6048 9 14.5484 9 14.5484" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
342
|
+
</svg>`,
|
|
343
|
+
hundredFortyKnots: `<svg width="24" height="47" viewBox="0 0 24 47" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
344
|
+
<path d="M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842" fill="black"/>
|
|
345
|
+
<path d="M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34" stroke="black" stroke-width="1.8"/>
|
|
346
|
+
<path d="M17 25C10.7518 26.1718 1 28 1 28" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
347
|
+
<path d="M17 21C10.7518 22.1718 1 24 1 24" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
348
|
+
<path d="M17 17C10.7518 18.1718 1 20 1 20" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
349
|
+
<path d="M17 13C10.7518 14.1718 1 16 1 16" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
350
|
+
</svg>`,
|
|
351
|
+
hundredFortyFiveKnots: `<svg width="24" height="49" viewBox="0 0 24 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
352
|
+
<path d="M17 5.26807V25.3971V45.5261L1 48.6229L17 37.7842" fill="black"/>
|
|
353
|
+
<path d="M16.9984 5.26807V30M16.9984 37.7842L4.35938 47.2985L16.9984 45.5261V37.5M16.9984 30C16.9984 30 5.67406 37.7842 4 39M16.9984 30V31.5M4 39C9.07621 38.4142 16.9984 37.5 16.9984 37.5M4 39C9.07621 36.0711 16.9984 31.5 16.9984 31.5M4 39C9.07682 36.6569 17 33 17 33M4 39C9.07621 37.2426 16.9984 34.5 16.9984 34.5M4 39C9.07621 37.8284 16.9984 36 16.9984 36M16.9984 37.5V36M16.9984 31.5V34.5M16.9984 34.5V36" stroke="black" stroke-width="1.8"/>
|
|
354
|
+
<path d="M16.9167 12C13.8251 12.6048 9 13.5484 9 13.5484" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
355
|
+
<path d="M17 27C10.7518 28.1718 1 30 1 30" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
356
|
+
<path d="M17 23C10.7518 24.1718 1 26 1 26" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
357
|
+
<path d="M17 19C10.7518 20.1718 1 22 1 22" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
358
|
+
<path d="M17 15C10.7518 16.1718 1 18 1 18" stroke="black" stroke-width="1.8" stroke-miterlimit="16"/>
|
|
359
|
+
</svg>`,
|
|
360
|
+
hundredFiftyKnots: `<svg width="24" height="47" viewBox="0 0 24 47" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
361
|
+
<path d="M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842" fill="black"/>
|
|
362
|
+
<path d="M16.9984 3.26807V19M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M16.9984 28V26.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34M16.9984 19C16.9984 19 8 25.5 4 29M16.9984 19V20.5M4 29C9.07621 28.0237 16.9984 26.5 16.9984 26.5M4 29C9.07621 25.6805 16.9984 20.5 16.9984 20.5M4 29C9.07621 26.4616 16.9984 22.5 16.9984 22.5M4 29C9.07621 27.0474 16.9984 24 16.9984 24M4 29C9.07621 27.5355 16.9984 25.25 16.9984 25.25M16.9984 26.5V25.25M16.9984 20.5V22.5M16.9984 22.5V24M16.9984 24V25.25" stroke="black" stroke-width="1.8"/>
|
|
363
|
+
</svg>`
|
|
364
|
+
};
|
|
365
|
+
const windSpeedRanges = [{
|
|
366
|
+
min: 0,
|
|
367
|
+
max: 1,
|
|
368
|
+
symbol: windSymbolDefinitions.calm
|
|
369
|
+
}, {
|
|
370
|
+
min: 1,
|
|
371
|
+
max: 4.5,
|
|
372
|
+
symbol: windSymbolDefinitions.lightAir
|
|
373
|
+
}, {
|
|
374
|
+
min: 4.5,
|
|
375
|
+
max: 7.5,
|
|
376
|
+
symbol: windSymbolDefinitions.fiveKnots
|
|
377
|
+
}, {
|
|
378
|
+
min: 7.5,
|
|
379
|
+
max: 12.5,
|
|
380
|
+
symbol: windSymbolDefinitions.tenKnots
|
|
381
|
+
}, {
|
|
382
|
+
min: 12.5,
|
|
383
|
+
max: 17.5,
|
|
384
|
+
symbol: windSymbolDefinitions.fifteenKnots
|
|
385
|
+
}, {
|
|
386
|
+
min: 17.5,
|
|
387
|
+
max: 22.5,
|
|
388
|
+
symbol: windSymbolDefinitions.twentyKnots
|
|
389
|
+
}, {
|
|
390
|
+
min: 22.5,
|
|
391
|
+
max: 27.5,
|
|
392
|
+
symbol: windSymbolDefinitions.twentyFiveKnots
|
|
393
|
+
}, {
|
|
394
|
+
min: 27.5,
|
|
395
|
+
max: 32.5,
|
|
396
|
+
symbol: windSymbolDefinitions.thirtyKnots
|
|
397
|
+
}, {
|
|
398
|
+
min: 32.5,
|
|
399
|
+
max: 37.5,
|
|
400
|
+
symbol: windSymbolDefinitions.thirtyFiveKnots
|
|
401
|
+
}, {
|
|
402
|
+
min: 37.5,
|
|
403
|
+
max: 42.5,
|
|
404
|
+
symbol: windSymbolDefinitions.fortyKnots
|
|
405
|
+
}, {
|
|
406
|
+
min: 42.5,
|
|
407
|
+
max: 47.5,
|
|
408
|
+
symbol: windSymbolDefinitions.fortyFiveKnots
|
|
409
|
+
}, {
|
|
410
|
+
min: 47.5,
|
|
411
|
+
max: 52.5,
|
|
412
|
+
symbol: windSymbolDefinitions.fiftyKnots
|
|
413
|
+
}, {
|
|
414
|
+
min: 52.5,
|
|
415
|
+
max: 57.5,
|
|
416
|
+
symbol: windSymbolDefinitions.fiftyFiveKnots
|
|
417
|
+
}, {
|
|
418
|
+
min: 57.5,
|
|
419
|
+
max: 62.5,
|
|
420
|
+
symbol: windSymbolDefinitions.sixtyKnots
|
|
421
|
+
}, {
|
|
422
|
+
min: 62.5,
|
|
423
|
+
max: 67.5,
|
|
424
|
+
symbol: windSymbolDefinitions.sixtyFiveKnots
|
|
425
|
+
}, {
|
|
426
|
+
min: 67.5,
|
|
427
|
+
max: 72.5,
|
|
428
|
+
symbol: windSymbolDefinitions.seventyKnots
|
|
429
|
+
}, {
|
|
430
|
+
min: 72.5,
|
|
431
|
+
max: 77.5,
|
|
432
|
+
symbol: windSymbolDefinitions.seventyFiveKnots
|
|
433
|
+
}, {
|
|
434
|
+
min: 77.5,
|
|
435
|
+
max: 82.5,
|
|
436
|
+
symbol: windSymbolDefinitions.eightyKnots
|
|
437
|
+
}, {
|
|
438
|
+
min: 82.5,
|
|
439
|
+
max: 87.5,
|
|
440
|
+
symbol: windSymbolDefinitions.eightyFiveKnots
|
|
441
|
+
}, {
|
|
442
|
+
min: 87.5,
|
|
443
|
+
max: 92.5,
|
|
444
|
+
symbol: windSymbolDefinitions.ninetyKnots
|
|
445
|
+
}, {
|
|
446
|
+
min: 92.5,
|
|
447
|
+
max: 97.5,
|
|
448
|
+
symbol: windSymbolDefinitions.ninetyFiveKnots
|
|
449
|
+
}, {
|
|
450
|
+
min: 97.5,
|
|
451
|
+
max: 102.5,
|
|
452
|
+
symbol: windSymbolDefinitions.hundredKnots
|
|
453
|
+
}, {
|
|
454
|
+
min: 102.5,
|
|
455
|
+
max: 107.5,
|
|
456
|
+
symbol: windSymbolDefinitions.hundredFiveKnots
|
|
457
|
+
}, {
|
|
458
|
+
min: 107.5,
|
|
459
|
+
max: 112.5,
|
|
460
|
+
symbol: windSymbolDefinitions.hundredTenKnots
|
|
461
|
+
}, {
|
|
462
|
+
min: 112.5,
|
|
463
|
+
max: 117.5,
|
|
464
|
+
symbol: windSymbolDefinitions.hundredFifteenKnots
|
|
465
|
+
}, {
|
|
466
|
+
min: 117.5,
|
|
467
|
+
max: 122.5,
|
|
468
|
+
symbol: windSymbolDefinitions.hundredTwentyKnots
|
|
469
|
+
}, {
|
|
470
|
+
min: 122.5,
|
|
471
|
+
max: 127.5,
|
|
472
|
+
symbol: windSymbolDefinitions.hundredTwentyFiveKnots
|
|
473
|
+
}, {
|
|
474
|
+
min: 127.5,
|
|
475
|
+
max: 132.5,
|
|
476
|
+
symbol: windSymbolDefinitions.hundredThirtyKnots
|
|
477
|
+
}, {
|
|
478
|
+
min: 132.5,
|
|
479
|
+
max: 137.5,
|
|
480
|
+
symbol: windSymbolDefinitions.hundredThirtyFiveKnots
|
|
481
|
+
}, {
|
|
482
|
+
min: 137.5,
|
|
483
|
+
max: 142.5,
|
|
484
|
+
symbol: windSymbolDefinitions.hundredFortyKnots
|
|
485
|
+
}, {
|
|
486
|
+
min: 142.5,
|
|
487
|
+
max: 147.5,
|
|
488
|
+
symbol: windSymbolDefinitions.hundredFortyFiveKnots
|
|
489
|
+
}, {
|
|
490
|
+
min: 147.5,
|
|
491
|
+
max: 152,
|
|
492
|
+
symbol: windSymbolDefinitions.hundredFiftyKnots
|
|
493
|
+
}];
|
|
494
|
+
|
|
495
|
+
/* *
|
|
496
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
497
|
+
* you may not use this file except in compliance with the License.
|
|
498
|
+
* You may obtain a copy of the License at
|
|
499
|
+
*
|
|
500
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
501
|
+
*
|
|
502
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
503
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
504
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
505
|
+
* See the License for the specific language governing permissions and
|
|
506
|
+
* limitations under the License.
|
|
507
|
+
*
|
|
508
|
+
* Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
|
|
509
|
+
* Copyright 2025 - Finnish Meteorological Institute (FMI)
|
|
510
|
+
* Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
|
|
511
|
+
* */
|
|
512
|
+
const metersPerSecondToKnots = metersPerSecond => metersPerSecond * 1.94384;
|
|
513
|
+
const extendIsoBar = (path, horizontalBarbMargin) => {
|
|
514
|
+
const match = path.match(/^M([\d.-]+),([\d.-]+)\s*L([\d.-]+),([\d.-]+)/);
|
|
515
|
+
if (match) {
|
|
516
|
+
const startX = parseFloat(match[1]);
|
|
517
|
+
const startY = parseFloat(match[2]);
|
|
518
|
+
const endX = parseFloat(match[3]);
|
|
519
|
+
const endY = parseFloat(match[4]);
|
|
520
|
+
return `M${startX},${startY}L${endX + horizontalBarbMargin * 3},${endY}`;
|
|
521
|
+
}
|
|
522
|
+
return path;
|
|
523
|
+
};
|
|
524
|
+
const extendXAxis = (path, horizontalBarbMargin) => {
|
|
525
|
+
const match = path.match(/^M([\d.-]+),([\d.-]+)H([\d.-]+)/);
|
|
526
|
+
if (match) {
|
|
527
|
+
const endX = parseFloat(match[3]);
|
|
528
|
+
return path.replace(/H[\d.-]+/, `H${endX + horizontalBarbMargin * 3}`);
|
|
529
|
+
}
|
|
530
|
+
return path;
|
|
531
|
+
};
|
|
532
|
+
const drawWindBarbs = ({
|
|
533
|
+
svgRef,
|
|
534
|
+
data,
|
|
535
|
+
width,
|
|
536
|
+
height,
|
|
537
|
+
margin,
|
|
538
|
+
minPressure,
|
|
539
|
+
maxPressure,
|
|
540
|
+
barbColor = 'black'
|
|
541
|
+
}) => {
|
|
542
|
+
const barbsSize = 25;
|
|
543
|
+
const scalingFactor = 0.5;
|
|
544
|
+
const horizontalBarbMargin = barbsSize * scalingFactor;
|
|
545
|
+
const visibleItems = removeItemsByGrowthType(data.reverse(), GrowthType.EXPONENTIAL, 1.15, 1).filter(item => item.pressure >= 150);
|
|
546
|
+
// Measure width and height and make extra room for one column
|
|
547
|
+
// of windbarbs
|
|
548
|
+
const svg = d3.select(svgRef.current);
|
|
549
|
+
const currentWidth = svg.node()?.getBoundingClientRect().width;
|
|
550
|
+
if (!currentWidth) {
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
// Add extra width to all isobars needed for the windbarbs
|
|
554
|
+
d3.selectAll('path[type="isobar"]').each(function () {
|
|
555
|
+
const path = d3.select(this).attr('d');
|
|
556
|
+
const newD = extendIsoBar(path, horizontalBarbMargin);
|
|
557
|
+
d3.select(this).attr('d', newD).attr('clip-path', null);
|
|
558
|
+
});
|
|
559
|
+
// Add extra width to x-axis needed for the windbarbs
|
|
560
|
+
d3.select('g[type="x-axis"] path.domain').each(function () {
|
|
561
|
+
const path = d3.select(this).attr('d');
|
|
562
|
+
const newD = extendXAxis(path, horizontalBarbMargin);
|
|
563
|
+
d3.select(this).attr('d', newD);
|
|
564
|
+
});
|
|
565
|
+
svg.attr('width', currentWidth + margin.right + margin.left + barbsSize);
|
|
566
|
+
const pressureScale = d3.scaleLog().domain([maxPressure, minPressure]).range([height, 0]).clamp(true);
|
|
567
|
+
const barbGroup = svg.append('g').attr('class', 'wind-group').attr('transform',
|
|
568
|
+
// This translation should be revisited when implementing
|
|
569
|
+
// the plotting of several datasets at once
|
|
570
|
+
`translate(${(currentWidth ?? width) + horizontalBarbMargin}, ${margin.top})`);
|
|
571
|
+
barbGroup.selectAll('g').data(visibleItems).enter().append('g').attr('id', 'wind-barb').each(function handleBarb(data) {
|
|
572
|
+
const group = d3.select(this);
|
|
573
|
+
const windSymbol = getWindSymbol(data.windSpeed);
|
|
574
|
+
if (windSymbol) {
|
|
575
|
+
// Inner group will have rotation and scaling applied to it
|
|
576
|
+
const innerGroup = group.append('g');
|
|
577
|
+
innerGroup.node()?.appendChild(windSymbol);
|
|
578
|
+
// Update stroke color
|
|
579
|
+
innerGroup.selectAll('*').attr('stroke', barbColor);
|
|
580
|
+
// Only update the fill for the paths that already used a fill
|
|
581
|
+
// in their original svg form.
|
|
582
|
+
innerGroup.selectAll('*').each(function () {
|
|
583
|
+
const el = d3.select(this);
|
|
584
|
+
const currentFill = el.attr('fill');
|
|
585
|
+
if (currentFill && currentFill !== 'none') {
|
|
586
|
+
el.attr('fill', barbColor);
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
const bbox = windSymbol.getBBox();
|
|
590
|
+
// Central point
|
|
591
|
+
const cx = bbox.x + bbox.width / 2;
|
|
592
|
+
const cy = bbox.y + bbox.height / 2;
|
|
593
|
+
const rotation = 180 + data.windDirection;
|
|
594
|
+
const y = pressureScale(data.pressure);
|
|
595
|
+
// Place items in the correct vertical position
|
|
596
|
+
// Correcting for barbSize
|
|
597
|
+
group.attr('transform', `translate(0, ${y - barbsSize * scalingFactor / 8})`);
|
|
598
|
+
// Rotate accordingly, and scale
|
|
599
|
+
innerGroup.attr('transform', `rotate(${rotation}) scale(${scalingFactor}) translate(${-cx}, ${-cy})`);
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
};
|
|
603
|
+
const GrowthType = {
|
|
604
|
+
LINEAR: 'LINEAR',
|
|
605
|
+
EXPONENTIAL: 'EXPONENTIAL',
|
|
606
|
+
CUBIC: 'CUBIC'
|
|
607
|
+
};
|
|
608
|
+
const removeItemsByGrowthType = (array, growthType = 'LINEAR', base = 2, initial = 1) => {
|
|
609
|
+
if (!Object.values(GrowthType).includes(growthType)) {
|
|
610
|
+
throw new Error('Unsupported growth type');
|
|
611
|
+
}
|
|
612
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
613
|
+
return [];
|
|
614
|
+
}
|
|
615
|
+
if (growthType === GrowthType.LINEAR && base === 0) {
|
|
616
|
+
throw new Error('Base must be greater than 0');
|
|
617
|
+
}
|
|
618
|
+
if (growthType === GrowthType.EXPONENTIAL && base <= 1) {
|
|
619
|
+
throw new Error('Base must be greater than 1');
|
|
620
|
+
}
|
|
621
|
+
if (growthType === GrowthType.CUBIC && base <= 0) {
|
|
622
|
+
throw new Error('Base must be greater than 0');
|
|
623
|
+
}
|
|
624
|
+
const indicesToKeep = [];
|
|
625
|
+
let i = 0;
|
|
626
|
+
let nextIndex = 0;
|
|
627
|
+
while (nextIndex < array.length) {
|
|
628
|
+
switch (growthType) {
|
|
629
|
+
case GrowthType.LINEAR:
|
|
630
|
+
nextIndex = initial + i * base;
|
|
631
|
+
break;
|
|
632
|
+
case GrowthType.EXPONENTIAL:
|
|
633
|
+
nextIndex = Math.floor(initial * base ** i);
|
|
634
|
+
break;
|
|
635
|
+
case GrowthType.CUBIC:
|
|
636
|
+
nextIndex = initial + Math.floor(i ** 3 * base);
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
if (nextIndex < array.length) {
|
|
640
|
+
indicesToKeep.push(nextIndex);
|
|
641
|
+
}
|
|
642
|
+
i += 1;
|
|
643
|
+
}
|
|
644
|
+
return array.filter((_, index) => indicesToKeep.includes(index));
|
|
645
|
+
};
|
|
646
|
+
const getWindSymbol = value => {
|
|
647
|
+
const valueKts = metersPerSecondToKnots(value);
|
|
648
|
+
const matchedRange = windSpeedRanges.find(({
|
|
649
|
+
min,
|
|
650
|
+
max
|
|
651
|
+
}) => valueKts >= min && valueKts < max);
|
|
652
|
+
// Out of range
|
|
653
|
+
if (!matchedRange) {
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
// SVG string to actual SVG element
|
|
657
|
+
const parser = new DOMParser();
|
|
658
|
+
const svgDoc = parser.parseFromString(matchedRange.symbol, 'image/svg+xml');
|
|
659
|
+
const svgNode = svgDoc.documentElement;
|
|
660
|
+
// Return the parsed SVG node
|
|
661
|
+
return svgNode;
|
|
662
|
+
};
|
|
663
|
+
|
|
84
664
|
/* *
|
|
85
665
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
86
666
|
* you may not use this file except in compliance with the License.
|
|
@@ -133,48 +713,6 @@ const drawDryAdiabats = ({
|
|
|
133
713
|
}
|
|
134
714
|
};
|
|
135
715
|
|
|
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
716
|
/* *
|
|
179
717
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
180
718
|
* you may not use this file except in compliance with the License.
|
|
@@ -474,7 +1012,7 @@ const Soundings = ({
|
|
|
474
1012
|
const staticPressureArray = [maxPressure];
|
|
475
1013
|
const dp = -5;
|
|
476
1014
|
const minTemperature = -40;
|
|
477
|
-
const maxTemperature = 40;
|
|
1015
|
+
const maxTemperature = 40.6;
|
|
478
1016
|
let pressure = maxPressure;
|
|
479
1017
|
while (pressure > minPressure) {
|
|
480
1018
|
pressure += dp;
|
|
@@ -488,8 +1026,7 @@ const Soundings = ({
|
|
|
488
1026
|
svg,
|
|
489
1027
|
xScale,
|
|
490
1028
|
yScale,
|
|
491
|
-
height
|
|
492
|
-
width
|
|
1029
|
+
height
|
|
493
1030
|
});
|
|
494
1031
|
// Draw static lines
|
|
495
1032
|
drawStaticLines({
|
|
@@ -535,13 +1072,30 @@ const Soundings = ({
|
|
|
535
1072
|
height,
|
|
536
1073
|
svg
|
|
537
1074
|
});
|
|
1075
|
+
const windData = features.map(feature => ({
|
|
1076
|
+
windDirection: feature.properties.windDirection,
|
|
1077
|
+
windSpeed: feature.properties.windSpeed,
|
|
1078
|
+
pressure: feature.properties.pressure
|
|
1079
|
+
}));
|
|
1080
|
+
drawWindBarbs({
|
|
1081
|
+
svgRef,
|
|
1082
|
+
data: windData,
|
|
1083
|
+
width,
|
|
1084
|
+
height,
|
|
1085
|
+
margin,
|
|
1086
|
+
minPressure,
|
|
1087
|
+
maxPressure,
|
|
1088
|
+
barbColor: SOUNDING_COLORS[0]
|
|
1089
|
+
});
|
|
1090
|
+
const svgRefCur = svgRef.current;
|
|
538
1091
|
// eslint-disable-next-line consistent-return
|
|
539
1092
|
return () => {
|
|
1093
|
+
d3.select(svgRefCur).selectAll('.wind-group').remove();
|
|
540
1094
|
svg.selectAll('*').remove();
|
|
541
1095
|
// svg.on('mousemove', null)
|
|
542
1096
|
};
|
|
543
1097
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
544
|
-
}, []);
|
|
1098
|
+
}, [features]);
|
|
545
1099
|
return jsx(Card, {
|
|
546
1100
|
children: jsx("svg", {
|
|
547
1101
|
ref: svgRef
|
|
@@ -567,80 +1121,55 @@ const Soundings = ({
|
|
|
567
1121
|
* Copyright 2024 - The Norwegian Meteorological Institute (MET Norway)
|
|
568
1122
|
* */
|
|
569
1123
|
const SOUNDINGS_NAMESPACE = 'soundings';
|
|
1124
|
+
const defaultLanguage = 'en';
|
|
1125
|
+
const initSoundingsI18n = () => {
|
|
1126
|
+
void i18n.use(initReactI18next).init({
|
|
1127
|
+
lng: defaultLanguage,
|
|
1128
|
+
ns: SOUNDINGS_NAMESPACE,
|
|
1129
|
+
interpolation: {
|
|
1130
|
+
escapeValue: false
|
|
1131
|
+
},
|
|
1132
|
+
resources: {
|
|
1133
|
+
en: {
|
|
1134
|
+
[SOUNDINGS_NAMESPACE]: soundingsTranslations.en
|
|
1135
|
+
},
|
|
1136
|
+
nl: {
|
|
1137
|
+
[SOUNDINGS_NAMESPACE]: soundingsTranslations.nl
|
|
1138
|
+
},
|
|
1139
|
+
fi: {
|
|
1140
|
+
[SOUNDINGS_NAMESPACE]: soundingsTranslations.fi
|
|
1141
|
+
},
|
|
1142
|
+
no: {
|
|
1143
|
+
[SOUNDINGS_NAMESPACE]: soundingsTranslations.no
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1147
|
+
};
|
|
1148
|
+
const translateKeyOutsideComponents = (key, params = undefined) => i18n.t(key, {
|
|
1149
|
+
ns: [SOUNDINGS_NAMESPACE],
|
|
1150
|
+
...params
|
|
1151
|
+
});
|
|
570
1152
|
const useSoundingsTranslation = () => useTranslation(SOUNDINGS_NAMESPACE);
|
|
571
1153
|
|
|
572
1154
|
const SoundingsSourceMenu = ({
|
|
573
1155
|
soundingsCollections,
|
|
574
1156
|
isOpen = false,
|
|
575
|
-
handleClose
|
|
1157
|
+
handleClose,
|
|
1158
|
+
updateCollection,
|
|
1159
|
+
sourceSelections
|
|
576
1160
|
}) => {
|
|
577
1161
|
const {
|
|
578
1162
|
t
|
|
579
1163
|
} = 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
1164
|
const handleSelectionChange = (collectionId, update) => {
|
|
594
|
-
|
|
595
|
-
const currentCollectionState = prev[collectionId] || {};
|
|
596
|
-
return {
|
|
597
|
-
...prev,
|
|
598
|
-
[collectionId]: {
|
|
599
|
-
...currentCollectionState,
|
|
600
|
-
...update
|
|
601
|
-
}
|
|
602
|
-
};
|
|
603
|
-
});
|
|
1165
|
+
updateCollection(collectionId, update);
|
|
604
1166
|
};
|
|
605
|
-
const getDisplayValue =
|
|
1167
|
+
const getDisplayValue = instanceId => {
|
|
606
1168
|
if (!instanceId) {
|
|
607
|
-
return
|
|
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
|
-
});
|
|
1169
|
+
return t('noInstancesAvailable');
|
|
628
1170
|
}
|
|
629
|
-
|
|
630
|
-
|
|
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
|
-
});
|
|
1171
|
+
const instanceAsDate = dateUtils.isoStringToDate(instanceId);
|
|
1172
|
+
return instanceAsDate ? dateUtils.dateToIsoString(instanceAsDate) : instanceId;
|
|
644
1173
|
};
|
|
645
1174
|
return jsx(ToolContainerDraggable, {
|
|
646
1175
|
startSize: {
|
|
@@ -656,10 +1185,12 @@ const SoundingsSourceMenu = ({
|
|
|
656
1185
|
pr: 1
|
|
657
1186
|
},
|
|
658
1187
|
children: soundingsCollections && soundingsCollections.length > 0 ? soundingsCollections.map(collection => {
|
|
659
|
-
const
|
|
660
|
-
|
|
661
|
-
|
|
1188
|
+
const {
|
|
1189
|
+
isChecked,
|
|
1190
|
+
instanceId
|
|
1191
|
+
} = sourceSelections[collection.id] || {};
|
|
662
1192
|
const hasInstances = collection.instances.length > 0;
|
|
1193
|
+
const selectedInstanceId = instanceId || collection.instances[0].id;
|
|
663
1194
|
return jsxs(ListItem, {
|
|
664
1195
|
disablePadding: true,
|
|
665
1196
|
children: [jsx(ListItemIcon, {
|
|
@@ -667,25 +1198,43 @@ const SoundingsSourceMenu = ({
|
|
|
667
1198
|
minWidth: 42
|
|
668
1199
|
},
|
|
669
1200
|
children: jsx(Checkbox, {
|
|
670
|
-
checked: isChecked,
|
|
1201
|
+
checked: !!isChecked,
|
|
671
1202
|
onChange: event => handleSelectionChange(collection.id, {
|
|
672
|
-
isChecked: event.target.checked
|
|
1203
|
+
isChecked: event.target.checked,
|
|
1204
|
+
instanceId: selectedInstanceId
|
|
673
1205
|
}),
|
|
674
1206
|
disableRipple: true
|
|
675
1207
|
})
|
|
676
1208
|
}), jsxs(StyledSelect, {
|
|
677
1209
|
isEnabled: true,
|
|
1210
|
+
additionalStyling: {
|
|
1211
|
+
width: 'calc(100% - 24px)'
|
|
1212
|
+
},
|
|
678
1213
|
skipLocalStyling: false,
|
|
679
1214
|
value: selectedInstanceId,
|
|
680
1215
|
onChange: event => handleSelectionChange(collection.id, {
|
|
681
|
-
|
|
1216
|
+
instanceId: event.target.value
|
|
682
1217
|
}),
|
|
683
1218
|
renderValue: value => jsx(Box, {
|
|
684
1219
|
sx: {
|
|
685
1220
|
textOverflow: 'ellipsis',
|
|
686
1221
|
overflow: 'hidden'
|
|
687
1222
|
},
|
|
688
|
-
children:
|
|
1223
|
+
children: jsxs(Box, {
|
|
1224
|
+
display: "flex",
|
|
1225
|
+
children: [jsx(Box, {
|
|
1226
|
+
sx: {
|
|
1227
|
+
minWidth: 200
|
|
1228
|
+
},
|
|
1229
|
+
children: collection.name
|
|
1230
|
+
}), jsx(Box, {
|
|
1231
|
+
sx: {
|
|
1232
|
+
textOverflow: 'ellipsis',
|
|
1233
|
+
overflow: 'hidden'
|
|
1234
|
+
},
|
|
1235
|
+
children: getDisplayValue(value)
|
|
1236
|
+
})]
|
|
1237
|
+
})
|
|
689
1238
|
}),
|
|
690
1239
|
children: [!hasInstances && jsx(MenuItem, {
|
|
691
1240
|
value: "",
|
|
@@ -694,9 +1243,9 @@ const SoundingsSourceMenu = ({
|
|
|
694
1243
|
fontSize: 12
|
|
695
1244
|
},
|
|
696
1245
|
children: t('noInstancesAvailable')
|
|
697
|
-
}), collection.instances.map(
|
|
1246
|
+
}), collection.instances.map(instance => jsx(MenuItem, {
|
|
698
1247
|
value: instance.id,
|
|
699
|
-
children:
|
|
1248
|
+
children: getDisplayValue(instance.id)
|
|
700
1249
|
}, instance.id))]
|
|
701
1250
|
})]
|
|
702
1251
|
}, collection.id);
|
|
@@ -736,15 +1285,131 @@ const getCollectionLocations = collectionId => fetch(`${BASE_URL}/collections/${
|
|
|
736
1285
|
const getCollectionInstance = (baseURL, collectionId) => fetch(`${baseURL}/collections/${collectionId}/instances`, {
|
|
737
1286
|
headers: getHeaders()
|
|
738
1287
|
}).then(handleResponse);
|
|
1288
|
+
const getEdrCollectionForLocation = (collectionId, instanceId, locationId, parameters, datetime) => {
|
|
1289
|
+
const url = new URL(`${BASE_URL}/collections/${collectionId}${instanceId ? `/instances/${instanceId}` : ''}/locations/${locationId}`);
|
|
1290
|
+
url.searchParams.append('parameter-name', parameters.join(','));
|
|
1291
|
+
url.searchParams.append('datetime', datetime);
|
|
1292
|
+
url.searchParams.append('f', 'CoverageJSON');
|
|
1293
|
+
return fetch(url.toString(), {
|
|
1294
|
+
headers: getHeaders()
|
|
1295
|
+
}).then(handleResponse);
|
|
1296
|
+
};
|
|
1297
|
+
const getEdrCollectionForPosition = (collectionId, instanceId, coordinates, parameters, datetime) => {
|
|
1298
|
+
const url = new URL(`${BASE_URL}/collections/${collectionId}${instanceId ? `/instances/${instanceId}` : ''}/position`);
|
|
1299
|
+
url.searchParams.append('coords', `POINT(${coordinates[0]} ${coordinates[1]})`);
|
|
1300
|
+
url.searchParams.append('parameter-name', parameters.join(','));
|
|
1301
|
+
url.searchParams.append('datetime', datetime);
|
|
1302
|
+
url.searchParams.append('f', 'CoverageJSON');
|
|
1303
|
+
return fetch(url.toString(), {
|
|
1304
|
+
headers: getHeaders()
|
|
1305
|
+
}).then(handleResponse);
|
|
1306
|
+
};
|
|
1307
|
+
|
|
1308
|
+
/* *
|
|
1309
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1310
|
+
* you may not use this file except in compliance with the License.
|
|
1311
|
+
* You may obtain a copy of the License at
|
|
1312
|
+
*
|
|
1313
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1314
|
+
*
|
|
1315
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1316
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1317
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1318
|
+
* See the License for the specific language governing permissions and
|
|
1319
|
+
* limitations under the License.
|
|
1320
|
+
*
|
|
1321
|
+
* Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
|
|
1322
|
+
* Copyright 2025 - Finnish Meteorological Institute (FMI)
|
|
1323
|
+
* Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
|
|
1324
|
+
* */
|
|
1325
|
+
const initialSoundingsState = {
|
|
1326
|
+
selectedSources: {},
|
|
1327
|
+
selectedLocations: {}
|
|
1328
|
+
};
|
|
1329
|
+
const slice = createSlice({
|
|
1330
|
+
initialState: initialSoundingsState,
|
|
1331
|
+
name: 'soundings',
|
|
1332
|
+
reducers: {
|
|
1333
|
+
updateSelectedSource: (draft, payloadAction) => {
|
|
1334
|
+
const {
|
|
1335
|
+
sourceId,
|
|
1336
|
+
value
|
|
1337
|
+
} = payloadAction.payload;
|
|
1338
|
+
draft.selectedSources[sourceId] = {
|
|
1339
|
+
...draft.selectedSources[sourceId],
|
|
1340
|
+
...value
|
|
1341
|
+
};
|
|
1342
|
+
},
|
|
1343
|
+
updateSelectedLocation: (draft, payloadAction) => {
|
|
1344
|
+
const {
|
|
1345
|
+
sourceId,
|
|
1346
|
+
locationId
|
|
1347
|
+
} = payloadAction.payload;
|
|
1348
|
+
draft.selectedLocations[sourceId] = locationId;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
});
|
|
1352
|
+
const {
|
|
1353
|
+
reducer: soundingsReducer,
|
|
1354
|
+
actions: soundingsActions
|
|
1355
|
+
} = slice;
|
|
1356
|
+
|
|
1357
|
+
/* *
|
|
1358
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1359
|
+
* you may not use this file except in compliance with the License.
|
|
1360
|
+
* You may obtain a copy of the License at
|
|
1361
|
+
*
|
|
1362
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1363
|
+
*
|
|
1364
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1365
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1366
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1367
|
+
* See the License for the specific language governing permissions and
|
|
1368
|
+
* limitations under the License.
|
|
1369
|
+
*
|
|
1370
|
+
* Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
|
|
1371
|
+
* Copyright 2025 - Finnish Meteorological Institute (FMI)
|
|
1372
|
+
* Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
|
|
1373
|
+
* */
|
|
1374
|
+
const getSoundingsStore = store => store?.soundings || initialSoundingsState;
|
|
1375
|
+
const getSourceSelections = createSelector(getSoundingsStore, store => {
|
|
1376
|
+
return store.selectedSources;
|
|
1377
|
+
}, selectorMemoizationOptions);
|
|
1378
|
+
const getLocationSelections = createSelector(getSoundingsStore, store => store.selectedLocations, selectorMemoizationOptions);
|
|
1379
|
+
const getSelectedSources = createSelector(getSoundingsStore, store => {
|
|
1380
|
+
const sources = store.selectedSources;
|
|
1381
|
+
const activeSources = Object.keys(sources).reduce((list, key) => {
|
|
1382
|
+
const value = sources[key];
|
|
1383
|
+
if (value.isChecked === true) {
|
|
1384
|
+
return list.concat({
|
|
1385
|
+
collectionId: key,
|
|
1386
|
+
instanceId: value.instanceId
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1389
|
+
return list;
|
|
1390
|
+
}, []);
|
|
1391
|
+
return activeSources;
|
|
1392
|
+
}, selectorMemoizationOptions);
|
|
1393
|
+
const getSelectedSourcesAmount = createSelector(getSelectedSources, activeSources => {
|
|
1394
|
+
return activeSources.length;
|
|
1395
|
+
}, selectorMemoizationOptions);
|
|
739
1396
|
|
|
740
1397
|
const SoundingsSourceDialogConnect = ({
|
|
741
1398
|
soundingsCollections
|
|
742
1399
|
}) => {
|
|
743
1400
|
const [soundingsCollectionsWithInstances, setSoundingsCollectionsWithInstances] = React.useState();
|
|
1401
|
+
const dispatch = useDispatch();
|
|
744
1402
|
const {
|
|
745
1403
|
isDialogOpen,
|
|
746
1404
|
onCloseDialog
|
|
747
1405
|
} = useSetupDialog(uiTypes.DialogTypes.SoundingsSources);
|
|
1406
|
+
const updateCollection = React.useCallback((sourceId, value) => {
|
|
1407
|
+
dispatch(soundingsActions.updateSelectedSource({
|
|
1408
|
+
sourceId,
|
|
1409
|
+
value
|
|
1410
|
+
}));
|
|
1411
|
+
}, [dispatch]);
|
|
1412
|
+
const sourceSelections = useSelector(getSourceSelections);
|
|
748
1413
|
React.useEffect(() => {
|
|
749
1414
|
if (soundingsCollections && isDialogOpen) {
|
|
750
1415
|
try {
|
|
@@ -763,36 +1428,45 @@ const SoundingsSourceDialogConnect = ({
|
|
|
763
1428
|
return jsx(SoundingsSourceMenu, {
|
|
764
1429
|
soundingsCollections: soundingsCollectionsWithInstances,
|
|
765
1430
|
isOpen: isDialogOpen,
|
|
1431
|
+
updateCollection: (sourceId, value) => updateCollection(sourceId, value),
|
|
1432
|
+
sourceSelections: sourceSelections,
|
|
766
1433
|
handleClose: () => onCloseDialog()
|
|
767
1434
|
});
|
|
768
1435
|
};
|
|
769
1436
|
|
|
770
1437
|
const LocationsMenu = ({
|
|
771
1438
|
services,
|
|
1439
|
+
selectedLocations,
|
|
772
1440
|
isOpen,
|
|
773
|
-
onClose
|
|
1441
|
+
onClose,
|
|
1442
|
+
updateSelectedLocation
|
|
774
1443
|
}) => {
|
|
775
1444
|
const [collapseMap, setCollapseMap] = useState({});
|
|
776
1445
|
const {
|
|
777
1446
|
t
|
|
778
1447
|
} = useSoundingsTranslation();
|
|
1448
|
+
const isChecked = (sourceId, locationId) => selectedLocations[sourceId] === locationId;
|
|
779
1449
|
return jsx(ToolContainerDraggable, {
|
|
780
1450
|
startSize: {
|
|
781
1451
|
width: 320,
|
|
782
1452
|
height: 500
|
|
783
1453
|
},
|
|
784
1454
|
title: t('locations'),
|
|
785
|
-
headerSize: "medium",
|
|
786
1455
|
isOpen: isOpen,
|
|
787
1456
|
onClose: () => onClose(),
|
|
788
1457
|
bounds: "parent",
|
|
789
|
-
children:
|
|
1458
|
+
children: jsxs(List, {
|
|
790
1459
|
disablePadding: true,
|
|
791
1460
|
sx: {
|
|
792
1461
|
maxHeight: '100%',
|
|
793
1462
|
overflowY: 'auto'
|
|
794
1463
|
},
|
|
795
|
-
children: services.
|
|
1464
|
+
children: [services.length === 0 && jsx(Typography, {
|
|
1465
|
+
sx: {
|
|
1466
|
+
marginLeft: 1
|
|
1467
|
+
},
|
|
1468
|
+
children: t('locations-menu-warning')
|
|
1469
|
+
}), services.map(service => jsxs(Fragment, {
|
|
796
1470
|
children: [jsx(ListSubheader, {
|
|
797
1471
|
disableGutters: true,
|
|
798
1472
|
sx: {
|
|
@@ -819,14 +1493,32 @@ const LocationsMenu = ({
|
|
|
819
1493
|
overflow: 'hidden'
|
|
820
1494
|
},
|
|
821
1495
|
children: service.features?.map(location => jsx(ListItem, {
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
1496
|
+
disablePadding: true,
|
|
1497
|
+
disableGutters: true,
|
|
1498
|
+
children: jsxs(ListItemButton, {
|
|
1499
|
+
onClick: () => {
|
|
1500
|
+
updateSelectedLocation(service.id, isChecked(service.id, location.id) ? undefined : location.id);
|
|
1501
|
+
},
|
|
1502
|
+
dense: true,
|
|
1503
|
+
children: [jsx(ListItemIcon, {
|
|
1504
|
+
children: jsx(Checkbox, {
|
|
1505
|
+
edge: "start",
|
|
1506
|
+
checked: isChecked(service.id, location.id),
|
|
1507
|
+
tabIndex: -1,
|
|
1508
|
+
disableRipple: true,
|
|
1509
|
+
inputProps: {
|
|
1510
|
+
'aria-labelledby': location.id
|
|
1511
|
+
}
|
|
1512
|
+
})
|
|
1513
|
+
}), jsx(ListItemText, {
|
|
1514
|
+
id: location.id,
|
|
1515
|
+
primary: location.properties.name
|
|
1516
|
+
})]
|
|
1517
|
+
})
|
|
826
1518
|
}, `${service.id}-${location.id}`))
|
|
827
1519
|
})
|
|
828
1520
|
})]
|
|
829
|
-
}, service.id))
|
|
1521
|
+
}, service.id))]
|
|
830
1522
|
})
|
|
831
1523
|
});
|
|
832
1524
|
};
|
|
@@ -834,10 +1526,14 @@ const LocationsMenu = ({
|
|
|
834
1526
|
const LocationsMenuConnect = ({
|
|
835
1527
|
soundingsCollections
|
|
836
1528
|
}) => {
|
|
1529
|
+
const dispatch = useDispatch();
|
|
837
1530
|
const {
|
|
838
1531
|
isDialogOpen,
|
|
839
1532
|
onCloseDialog
|
|
840
1533
|
} = useSetupDialog(uiTypes.DialogTypes.soundingsLocations);
|
|
1534
|
+
const locationSelections = useSelector(getLocationSelections);
|
|
1535
|
+
const sourceSelections = useSelector(getSourceSelections);
|
|
1536
|
+
const selectedSourceIds = useMemo(() => Object.keys(sourceSelections).filter(key => sourceSelections[key].isChecked), [sourceSelections]);
|
|
841
1537
|
const edrQueries = useMemo(() => soundingsCollections.map(collection => ({
|
|
842
1538
|
queryKey: ['edr-locations', collection.id],
|
|
843
1539
|
queryFn: async () => {
|
|
@@ -855,206 +1551,57 @@ const LocationsMenuConnect = ({
|
|
|
855
1551
|
return jsx(LocationsMenu, {
|
|
856
1552
|
isOpen: isDialogOpen,
|
|
857
1553
|
onClose: onCloseDialog,
|
|
858
|
-
services: services
|
|
1554
|
+
services: services.filter(service => selectedSourceIds.includes(service.id)),
|
|
1555
|
+
selectedLocations: locationSelections,
|
|
1556
|
+
updateSelectedLocation: (sourceId, locationId) => void dispatch(soundingsActions.updateSelectedLocation({
|
|
1557
|
+
sourceId,
|
|
1558
|
+
locationId
|
|
1559
|
+
}))
|
|
859
1560
|
});
|
|
860
1561
|
};
|
|
861
1562
|
|
|
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
1563
|
var MenuOptions;
|
|
1028
1564
|
(function (MenuOptions) {
|
|
1029
1565
|
MenuOptions["sources"] = "sources";
|
|
1030
1566
|
MenuOptions["locations"] = "locations";
|
|
1031
1567
|
})(MenuOptions || (MenuOptions = {}));
|
|
1568
|
+
const buttonStyle = {
|
|
1569
|
+
height: '10px !important',
|
|
1570
|
+
width: '130px'
|
|
1571
|
+
};
|
|
1032
1572
|
const SoundingsMenuBar = ({
|
|
1033
1573
|
handleClick,
|
|
1034
|
-
isOpen
|
|
1574
|
+
isOpen,
|
|
1575
|
+
selectedSourcesAmount = 0
|
|
1035
1576
|
}) => {
|
|
1036
1577
|
const {
|
|
1037
1578
|
t
|
|
1038
1579
|
} = useSoundingsTranslation();
|
|
1039
|
-
return jsxs(
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1580
|
+
return jsxs(Stack, {
|
|
1581
|
+
direction: "row",
|
|
1582
|
+
spacing: 1,
|
|
1583
|
+
sx: {
|
|
1584
|
+
marginBottom: '4px'
|
|
1585
|
+
},
|
|
1586
|
+
children: [jsxs(CustomToggleButton, {
|
|
1587
|
+
variant: "tool",
|
|
1588
|
+
selected: isOpen[MenuOptions.sources],
|
|
1046
1589
|
"data-testid": "soundingsSourcesButton",
|
|
1047
1590
|
onClick: () => handleClick(MenuOptions.sources),
|
|
1048
|
-
startIcon: jsx(Sources, {}),
|
|
1049
1591
|
size: "small",
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1592
|
+
sx: buttonStyle,
|
|
1593
|
+
children: [jsx(Sources, {
|
|
1594
|
+
sx: {
|
|
1595
|
+
marginRight: '4px!important'
|
|
1596
|
+
}
|
|
1597
|
+
}), selectedSourcesAmount ? `${t('sources')} (${selectedSourcesAmount})` : t('sources')]
|
|
1598
|
+
}), jsx(CustomToggleButton, {
|
|
1599
|
+
variant: "tool",
|
|
1054
1600
|
"data-testid": "soundingsLocationsButton",
|
|
1055
|
-
|
|
1601
|
+
selected: isOpen[MenuOptions.locations],
|
|
1056
1602
|
onClick: () => handleClick(MenuOptions.locations),
|
|
1057
1603
|
size: "small",
|
|
1604
|
+
sx: buttonStyle,
|
|
1058
1605
|
children: t('locations')
|
|
1059
1606
|
})]
|
|
1060
1607
|
});
|
|
@@ -1068,6 +1615,7 @@ const MenuBarConnect = ({
|
|
|
1068
1615
|
const currentActiveMapId = useSelector(store => uiSelectors.getDialogMapId(store, uiTypes.DialogTypes.SoundingsSources));
|
|
1069
1616
|
const isSourcesOpenInStore = useSelector(store => uiSelectors.getisDialogOpen(store, uiTypes.DialogTypes.SoundingsSources));
|
|
1070
1617
|
const isLocationsOpenInStore = useSelector(store => uiSelectors.getisDialogOpen(store, uiTypes.DialogTypes.soundingsLocations));
|
|
1618
|
+
const sourcesAmount = useSelector(getSelectedSourcesAmount);
|
|
1071
1619
|
const dispatchActiveMapId = React.useCallback((dialogType, isOpen) => void dispatch(uiActions.setActiveMapIdForDialog({
|
|
1072
1620
|
type: dialogType,
|
|
1073
1621
|
mapId: viewId,
|
|
@@ -1090,39 +1638,276 @@ const MenuBarConnect = ({
|
|
|
1090
1638
|
};
|
|
1091
1639
|
return jsx(SoundingsMenuBar, {
|
|
1092
1640
|
isOpen: isOpen,
|
|
1093
|
-
handleClick: handleClick
|
|
1641
|
+
handleClick: handleClick,
|
|
1642
|
+
selectedSourcesAmount: sourcesAmount
|
|
1094
1643
|
});
|
|
1095
1644
|
};
|
|
1096
1645
|
|
|
1646
|
+
/* *
|
|
1647
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1648
|
+
* you may not use this file except in compliance with the License.
|
|
1649
|
+
* You may obtain a copy of the License at
|
|
1650
|
+
*
|
|
1651
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1652
|
+
*
|
|
1653
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1654
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1655
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1656
|
+
* See the License for the specific language governing permissions and
|
|
1657
|
+
* limitations under the License.
|
|
1658
|
+
*
|
|
1659
|
+
* Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
|
|
1660
|
+
* Copyright 2025 - Finnish Meteorological Institute (FMI)
|
|
1661
|
+
* Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
|
|
1662
|
+
* */
|
|
1663
|
+
const epsilon = 0.622; // Ratio of molecular weights of water vapour and dry air
|
|
1664
|
+
const e0 = 611.2; // Reference vapor pressure [Pa]
|
|
1665
|
+
const aConstant = 17.67;
|
|
1666
|
+
const bConstant = 243.5; // Constants for water, b in Celsius
|
|
1667
|
+
const tKelvinOffset = 273.15; // To convert Celsius to Kelvin
|
|
1668
|
+
const gConstant = 461.5; // Gas constant ratio between water vapour and air [J/(K*kg)]
|
|
1669
|
+
const hConstant = 2.5e6; // Latent heat for water vaporization [J/kg]
|
|
1670
|
+
// Formula to use when dewpoint is not available
|
|
1671
|
+
// Known as Magnus Formula
|
|
1672
|
+
// - Calculate vapor pressure
|
|
1673
|
+
// - Use inverse of the Clausius-Clapeyron equation
|
|
1674
|
+
function dewpointFromSpecificHumidity(specificHumidity,
|
|
1675
|
+
// specific humidity [kg/kg]
|
|
1676
|
+
pressure) {
|
|
1677
|
+
// Step 1: Calculate vapor pressure (Pa)
|
|
1678
|
+
const vaporPressure = specificHumidity * pressure / (epsilon + (1 - epsilon) * specificHumidity);
|
|
1679
|
+
// Step 2: Calculate dew point in Celsius
|
|
1680
|
+
const lnRatio = Math.log(vaporPressure / e0);
|
|
1681
|
+
const dewpoint = bConstant * lnRatio / (aConstant - lnRatio);
|
|
1682
|
+
return dewpoint; // dew point in Celsius
|
|
1683
|
+
}
|
|
1684
|
+
// Formula to use when dewpoint is not available
|
|
1685
|
+
function dewpointFromRelativeHumidity(tCelsius,
|
|
1686
|
+
// air temperature [°C]
|
|
1687
|
+
relHumidity) {
|
|
1688
|
+
// Convert to Kelvin
|
|
1689
|
+
const tKelvin = tCelsius + tKelvinOffset;
|
|
1690
|
+
// Calculate dewpoint
|
|
1691
|
+
const dewpoint = tKelvin / (1 - tKelvin * Math.log(relHumidity * 0.01) * gConstant / hConstant);
|
|
1692
|
+
return dewpoint;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
/* *
|
|
1696
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1697
|
+
* you may not use this file except in compliance with the License.
|
|
1698
|
+
* You may obtain a copy of the License at
|
|
1699
|
+
*
|
|
1700
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1701
|
+
*
|
|
1702
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1703
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1704
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1705
|
+
* See the License for the specific language governing permissions and
|
|
1706
|
+
* limitations under the License.
|
|
1707
|
+
*
|
|
1708
|
+
* Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
|
|
1709
|
+
* Copyright 2025 - Finnish Meteorological Institute (FMI)
|
|
1710
|
+
* Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
|
|
1711
|
+
* */
|
|
1712
|
+
// Parsing and conversion function
|
|
1713
|
+
function parseCoverageCollection(response) {
|
|
1714
|
+
const features = [];
|
|
1715
|
+
const {
|
|
1716
|
+
t: tAxis,
|
|
1717
|
+
x: xAxis,
|
|
1718
|
+
y: yAxis,
|
|
1719
|
+
z: zAxis
|
|
1720
|
+
} = response.coverages[0]?.domain.axes || {};
|
|
1721
|
+
if (!tAxis?.values || !xAxis?.values || !yAxis?.values || !zAxis?.values) {
|
|
1722
|
+
return features;
|
|
1723
|
+
}
|
|
1724
|
+
response.coverages.forEach(coverage => {
|
|
1725
|
+
const {
|
|
1726
|
+
ranges
|
|
1727
|
+
} = coverage;
|
|
1728
|
+
const time = coverage.domain.axes.t?.values[0];
|
|
1729
|
+
const xCoord = coverage.domain.axes.x.values[0];
|
|
1730
|
+
const yCoord = coverage.domain.axes.y.values[0];
|
|
1731
|
+
const zValues = coverage.domain.axes.z.values;
|
|
1732
|
+
zValues.forEach((_, zIndex) => {
|
|
1733
|
+
const temp = parseFloat(ranges.temperature?.values[zIndex]);
|
|
1734
|
+
const windums = parseFloat(ranges.windums?.values[zIndex]);
|
|
1735
|
+
const windvms = parseFloat(ranges.windvms?.values[zIndex]);
|
|
1736
|
+
let dewpoint = parseFloat(ranges.dewpoint?.values[zIndex]);
|
|
1737
|
+
const relativeHumidity = parseFloat(ranges.relativehumidity?.values[zIndex]);
|
|
1738
|
+
const specificHumidity = parseFloat(ranges.specifichumidity?.values[zIndex]);
|
|
1739
|
+
let windSpeed = parseFloat(ranges.windSpeed?.values[zIndex]);
|
|
1740
|
+
// Fallback calculations
|
|
1741
|
+
if (Number.isNaN(windSpeed) && !Number.isNaN(windums) && !Number.isNaN(windvms)) {
|
|
1742
|
+
// Fallback calculation
|
|
1743
|
+
windSpeed = Math.sqrt(windums ** 2 + windvms ** 2);
|
|
1744
|
+
}
|
|
1745
|
+
let windDirection = parseFloat(ranges.windDirection?.values[zIndex]);
|
|
1746
|
+
if (Number.isNaN(windDirection) && !Number.isNaN(windums) && !Number.isNaN(windvms)) {
|
|
1747
|
+
// Fallback calculation
|
|
1748
|
+
windDirection = (Math.atan2(-windums, -windvms) * (180 / Math.PI) + 360) % 360;
|
|
1749
|
+
}
|
|
1750
|
+
const pressureFromRange = parseFloat(ranges.pressure?.values[zIndex]);
|
|
1751
|
+
const pressure = !Number.isNaN(pressureFromRange) ? pressureFromRange : parseFloat(zValues[zIndex]);
|
|
1752
|
+
if (dewpoint === null || Number.isNaN(dewpoint)) {
|
|
1753
|
+
// Use dewpoint fallback calculations if not present
|
|
1754
|
+
if (!Number.isNaN(specificHumidity)) {
|
|
1755
|
+
dewpoint = dewpointFromSpecificHumidity(specificHumidity / 1000,
|
|
1756
|
+
// from g/kg to kg/kg
|
|
1757
|
+
pressure * 100);
|
|
1758
|
+
} else if (!Number.isNaN(relativeHumidity)) {
|
|
1759
|
+
dewpoint = dewpointFromRelativeHumidity(temp, relativeHumidity);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
features.push({
|
|
1763
|
+
type: 'Feature',
|
|
1764
|
+
geometry: {
|
|
1765
|
+
type: 'Point',
|
|
1766
|
+
coordinates: [xCoord, yCoord]
|
|
1767
|
+
},
|
|
1768
|
+
properties: {
|
|
1769
|
+
time,
|
|
1770
|
+
temp,
|
|
1771
|
+
dewpoint,
|
|
1772
|
+
pressure,
|
|
1773
|
+
windSpeed,
|
|
1774
|
+
windDirection
|
|
1775
|
+
}
|
|
1776
|
+
});
|
|
1777
|
+
});
|
|
1778
|
+
});
|
|
1779
|
+
return features;
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
/* *
|
|
1783
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1784
|
+
* you may not use this file except in compliance with the License.
|
|
1785
|
+
* You may obtain a copy of the License at
|
|
1786
|
+
*
|
|
1787
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1788
|
+
*
|
|
1789
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1790
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1791
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1792
|
+
* See the License for the specific language governing permissions and
|
|
1793
|
+
* limitations under the License.
|
|
1794
|
+
*
|
|
1795
|
+
* Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
|
|
1796
|
+
* Copyright 2025 - Finnish Meteorological Institute (FMI)
|
|
1797
|
+
* Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
|
|
1798
|
+
* */
|
|
1799
|
+
const sortSoundingFeatures = data => {
|
|
1800
|
+
return data.sort((featureA, featureB) => {
|
|
1801
|
+
// Sort by date (ascending)
|
|
1802
|
+
if (featureA.properties.time !== featureB.properties.time) {
|
|
1803
|
+
return featureA.properties.time > featureB.properties.time ? 1 : -1;
|
|
1804
|
+
}
|
|
1805
|
+
// Sort by pressure (descending)
|
|
1806
|
+
return featureB.properties.pressure - featureA.properties.pressure;
|
|
1807
|
+
});
|
|
1808
|
+
};
|
|
1809
|
+
const constructQueryParams = (collectionId, instanceId, location, datetime, parameters) => {
|
|
1810
|
+
if (typeof location === 'string') {
|
|
1811
|
+
// LOCATION call
|
|
1812
|
+
return {
|
|
1813
|
+
queryKey: ['edr-soundings-for-location', collectionId, instanceId || '', location, parameters.join(), datetime],
|
|
1814
|
+
queryFn: async () => {
|
|
1815
|
+
// No collection or location selected, don't do EDR call but return empty array
|
|
1816
|
+
if (collectionId === undefined || location === '') {
|
|
1817
|
+
return [];
|
|
1818
|
+
}
|
|
1819
|
+
try {
|
|
1820
|
+
const rawData = await getEdrCollectionForLocation(collectionId, instanceId, location, parameters, datetime);
|
|
1821
|
+
const parsedData = parseCoverageCollection(rawData);
|
|
1822
|
+
return sortSoundingFeatures(parsedData);
|
|
1823
|
+
} catch (err) {
|
|
1824
|
+
throw new Error('Error fetching EDR data collection by location');
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
};
|
|
1828
|
+
}
|
|
1829
|
+
// POSITION call
|
|
1830
|
+
return {
|
|
1831
|
+
queryKey: ['edr-soundings-for-position', collectionId, location.join(), parameters.join(), datetime],
|
|
1832
|
+
queryFn: async () => {
|
|
1833
|
+
// No collection or position selected, don't do EDR call but return empty array
|
|
1834
|
+
if (collectionId === undefined || location === undefined) {
|
|
1835
|
+
return [];
|
|
1836
|
+
}
|
|
1837
|
+
try {
|
|
1838
|
+
const rawData = await getEdrCollectionForPosition(collectionId, instanceId, location, parameters, datetime);
|
|
1839
|
+
const parsedData = parseCoverageCollection(rawData);
|
|
1840
|
+
return sortSoundingFeatures(parsedData);
|
|
1841
|
+
} catch (err) {
|
|
1842
|
+
throw new Error('Error fetching EDR data collection by location');
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
};
|
|
1846
|
+
};
|
|
1847
|
+
const useEdrCollectionForLocationPosition = (collectionId, instanceId, location, datetime, parameters = ['temperature', 'WindDirection', 'WindSpeedMS', 'windUMS', 'windVMS', 'Pressure', 'DewPoint', 'humidity', 'specifichumidity']) => {
|
|
1848
|
+
// TODO: Remove once we pass in the datetime coming from the timeslider!
|
|
1849
|
+
const {
|
|
1850
|
+
data
|
|
1851
|
+
} = useQuery({
|
|
1852
|
+
queryKey: ['temporary-instance-call', collectionId, instanceId],
|
|
1853
|
+
queryFn: async () => {
|
|
1854
|
+
return fetch(`https://opendata.fmi.fi/edr/collections/${collectionId}/instances/${instanceId}`, {
|
|
1855
|
+
headers: getHeaders()
|
|
1856
|
+
}).then(handleResponse);
|
|
1857
|
+
}
|
|
1858
|
+
});
|
|
1859
|
+
const datetime2 = collectionId === undefined || data === undefined ? '' : data.extent.temporal.interval[0][0];
|
|
1860
|
+
// Up until here can be removed
|
|
1861
|
+
// If position passed in, call positions hook
|
|
1862
|
+
const queryParams = constructQueryParams(collectionId, instanceId, location, datetime2, parameters);
|
|
1863
|
+
return useQuery(queryParams);
|
|
1864
|
+
};
|
|
1865
|
+
|
|
1097
1866
|
const SoundingsConnect = ({
|
|
1098
1867
|
viewId
|
|
1099
1868
|
}) => {
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1869
|
+
const sourceSelections = useSelector(getSelectedSources);
|
|
1870
|
+
const locationSelections = useSelector(getLocationSelections);
|
|
1871
|
+
// TODO https://gitlab.com/opengeoweb/opengeoweb/-/issues/5916: Allow multiple sources to be selected
|
|
1872
|
+
// For now, only select the first one
|
|
1873
|
+
const {
|
|
1874
|
+
error,
|
|
1875
|
+
data: soundingData,
|
|
1876
|
+
isPending
|
|
1877
|
+
} = useEdrCollectionForLocationPosition(sourceSelections[0]?.collectionId, sourceSelections[0]?.instanceId, locationSelections[sourceSelections[0]?.collectionId] || '');
|
|
1878
|
+
return jsxs(Fragment$1, {
|
|
1879
|
+
children: [error && jsx(AlertBanner, {
|
|
1880
|
+
title: error.message,
|
|
1881
|
+
shouldClose: true
|
|
1882
|
+
}), isPending && jsx(LinearProgress, {
|
|
1883
|
+
color: "secondary"
|
|
1884
|
+
}), jsxs(Box, {
|
|
1885
|
+
sx: {
|
|
1886
|
+
padding: 2
|
|
1115
1887
|
},
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1888
|
+
children: [jsx(Box, {
|
|
1889
|
+
paddingLeft: 5,
|
|
1890
|
+
children: jsx(MenuBarConnect, {
|
|
1891
|
+
viewId: viewId
|
|
1892
|
+
})
|
|
1893
|
+
}), jsx(Soundings
|
|
1894
|
+
// TODO: use real data
|
|
1895
|
+
, {
|
|
1896
|
+
// TODO: use real data
|
|
1897
|
+
data: {
|
|
1898
|
+
features: soundingData || []
|
|
1899
|
+
},
|
|
1900
|
+
svgDimensions: {
|
|
1901
|
+
width: 600,
|
|
1902
|
+
height: 600,
|
|
1903
|
+
margin: {
|
|
1904
|
+
bottom: 0,
|
|
1905
|
+
left: 0,
|
|
1906
|
+
right: 0,
|
|
1907
|
+
top: 0
|
|
1908
|
+
}
|
|
1124
1909
|
}
|
|
1125
|
-
}
|
|
1910
|
+
})]
|
|
1126
1911
|
})]
|
|
1127
1912
|
});
|
|
1128
1913
|
};
|
|
@@ -1137,4 +1922,33 @@ const soundingsLookUp = payload => {
|
|
|
1137
1922
|
return null;
|
|
1138
1923
|
};
|
|
1139
1924
|
|
|
1140
|
-
|
|
1925
|
+
/* *
|
|
1926
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1927
|
+
* you may not use this file except in compliance with the License.
|
|
1928
|
+
* You may obtain a copy of the License at
|
|
1929
|
+
*
|
|
1930
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1931
|
+
*
|
|
1932
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1933
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1934
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1935
|
+
* See the License for the specific language governing permissions and
|
|
1936
|
+
* limitations under the License.
|
|
1937
|
+
*
|
|
1938
|
+
* Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
|
|
1939
|
+
* Copyright 2025 - Finnish Meteorological Institute (FMI)
|
|
1940
|
+
* Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
|
|
1941
|
+
* */
|
|
1942
|
+
const soundingsReducersMap = {
|
|
1943
|
+
soundings: soundingsReducer,
|
|
1944
|
+
webmap: mapReducer,
|
|
1945
|
+
ui: uiReducer
|
|
1946
|
+
};
|
|
1947
|
+
const soundingsRootReuducer = combineReducers(soundingsReducersMap);
|
|
1948
|
+
const createMockStore = mockState => configureStore({
|
|
1949
|
+
reducer: soundingsRootReuducer,
|
|
1950
|
+
preloadedState: mockState,
|
|
1951
|
+
middleware: getDefaultMiddleware => getDefaultMiddleware()
|
|
1952
|
+
});
|
|
1953
|
+
|
|
1954
|
+
export { LocationsMenuConnect, SOUNDINGS_NAMESPACE, Soundings, SoundingsSourceDialogConnect, createMockStore, initSoundingsI18n, soundingsComponentType, soundingsLookUp, soundingsReducersMap, soundingsTranslations, translateKeyOutsideComponents, useSoundingsTranslation };
|