@opengeoweb/soundings 15.0.0 → 15.1.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 CHANGED
@@ -1,8 +1,9 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { Menu, ListSubheader, Typography, MenuItem, Box, Card, Stack, IconButton, Tooltip, LinearProgress, List, ListItem, ListItemIcon, Checkbox, ListItemText, Grid, ListItemButton } from '@mui/material';
3
+ import { range, minBy, isEqual } from 'lodash';
3
4
  import React, { useRef, useEffect, useMemo, useState, Fragment as Fragment$1 } from 'react';
4
- import { useDispatch, useSelector } from 'react-redux';
5
- import { safelyInitI18nForTestsAndStories, sharedTranslations, SHARED_NAMESPACE, CustomToggleButton, dateUtils, CustomSlider, AlertBanner, ToolContainerDraggable, StyledSelect, SearchField, CustomAccordion } from '@opengeoweb/shared';
5
+ import { useDispatch, useSelector, shallowEqual } from 'react-redux';
6
+ import { safelyInitI18nForTestsAndStories, sharedTranslations, SHARED_NAMESPACE, CustomToggleButton, dateUtils, SwitchButton, CustomSlider, AlertBanner, ToolContainerDraggable, StyledSelect, SearchField, CustomAccordion } from '@opengeoweb/shared';
6
7
  import { selectorMemoizationOptions, uiSelectors, uiTypes, uiActions, genericSelectors, mapSelectors, genericActions, syncGroupsActions, mapActions, useSetupDialog, storeReducerMap, storeMiddlewares } from '@opengeoweb/store';
7
8
  import { ParseISOTimeRangeDuration, parseISO8601IntervalToDateInterval, webmapUtils } from '@opengeoweb/webmap';
8
9
  import { MapFeatureClass } from '@opengeoweb/webmap-react';
@@ -16,7 +17,6 @@ import { timesliderTranslations, TIME_SLIDER_NAMESPACE, BackwardForwardStepButto
16
17
  import { useResizable } from 'react-resizable-layout';
17
18
  import { useQueries } from '@tanstack/react-query';
18
19
  import { getHeaders, handleResponse } from '@opengeoweb/api';
19
- import { range, minBy } from 'lodash';
20
20
  import { createSlice, createSelector, combineReducers, configureStore } from '@reduxjs/toolkit';
21
21
  import { FixedSizeList } from 'react-window';
22
22
 
@@ -30,7 +30,9 @@ var en = {
30
30
  options: "Options",
31
31
  "diagram-SKEWT": "Skew-T",
32
32
  "diagram-BIJVOET": "Bijvoet",
33
- "diagram-EMAGRAM": "Emagram"
33
+ "diagram-EMAGRAM": "Emagram",
34
+ "soundings-toggle-location-markers-label": "Markers",
35
+ "soundings-toggle-location-markers-tooltip": "Show/hide location markers"
34
36
  };
35
37
  var no = {
36
38
  sources: "Kilder",
@@ -42,7 +44,9 @@ var no = {
42
44
  options: "Valg",
43
45
  "diagram-SKEWT": "Skew-T",
44
46
  "diagram-BIJVOET": "Bijvoet",
45
- "diagram-EMAGRAM": "Emagram"
47
+ "diagram-EMAGRAM": "Emagram",
48
+ "soundings-toggle-location-markers-label": "ikke oversatt",
49
+ "soundings-toggle-location-markers-tooltip": "ikke oversatt"
46
50
  };
47
51
  var nl = {
48
52
  sources: "Bronnen",
@@ -54,7 +58,9 @@ var nl = {
54
58
  options: "Opties",
55
59
  "diagram-SKEWT": "Skew-T",
56
60
  "diagram-BIJVOET": "Bijvoet",
57
- "diagram-EMAGRAM": "Emagram"
61
+ "diagram-EMAGRAM": "Emagram",
62
+ "soundings-toggle-location-markers-label": "Kaart locaties",
63
+ "soundings-toggle-location-markers-tooltip": "Toon/Verberg locatie punten"
58
64
  };
59
65
  var fi = {
60
66
  sources: "Lähteet",
@@ -66,7 +72,9 @@ var fi = {
66
72
  options: "Valinnat",
67
73
  "diagram-SKEWT": "Skew-T",
68
74
  "diagram-BIJVOET": "Bijvoet",
69
- "diagram-EMAGRAM": "Emagram"
75
+ "diagram-EMAGRAM": "Emagram",
76
+ "soundings-toggle-location-markers-label": "Merkit",
77
+ "soundings-toggle-location-markers-tooltip": "Näytä/piilota kohdemerkit"
70
78
  };
71
79
  var soundingsTranslations = {
72
80
  en: en,
@@ -2850,10 +2858,28 @@ const useMultipleSources = (sources, locations, datetime) => {
2850
2858
  parameterMappings,
2851
2859
  verticalRange
2852
2860
  }, index) => {
2853
- // If acceptance time logic is needed, it could come here.
2854
- const dateToUse = datetime &&
2855
- // Only use selected time if exact match if found
2856
- timesteps[index].includes(new Date(datetime).toISOString()) ? datetime : '';
2861
+ // Find the best matching timestep for this source
2862
+ let dateToUse = '';
2863
+ if (datetime && timesteps[index] && timesteps[index].length > 0) {
2864
+ const requestedTime = new Date(datetime).getTime();
2865
+ const availableTimesteps = timesteps[index];
2866
+ // Check for exact match first
2867
+ if (availableTimesteps.includes(new Date(datetime).toISOString())) {
2868
+ dateToUse = datetime;
2869
+ } else {
2870
+ // Find nearest timestep (prefer earlier timesteps if equidistant)
2871
+ let nearestTimestep = availableTimesteps[0];
2872
+ let minDiff = Math.abs(new Date(availableTimesteps[0]).getTime() - requestedTime);
2873
+ for (const timestep of availableTimesteps) {
2874
+ const diff = Math.abs(new Date(timestep).getTime() - requestedTime);
2875
+ if (diff < minDiff) {
2876
+ minDiff = diff;
2877
+ nearestTimestep = timestep;
2878
+ }
2879
+ }
2880
+ dateToUse = nearestTimestep;
2881
+ }
2882
+ }
2857
2883
  const queryParams = constructQueryParams(collectionId, url, instanceId, locations[collectionId] || '', dateToUse || '', parameterMappings || defaultParameterMappings, verticalRange || '');
2858
2884
  return queryParams;
2859
2885
  });
@@ -3056,15 +3082,39 @@ const initialSoundingsState = {
3056
3082
  selectedSources: {},
3057
3083
  selectedLocations: {},
3058
3084
  selectedTime: new Date().toISOString(),
3085
+ diagramType: 'SKEWT',
3059
3086
  soundingPanels: {
3060
- order: [],
3061
- entities: {}
3062
- }
3087
+ order: ['soundings', 'windbarbs'],
3088
+ entities: {
3089
+ soundings: {
3090
+ id: 'soundings',
3091
+ name: 'Soundings',
3092
+ isDisplayed: true
3093
+ },
3094
+ windbarbs: {
3095
+ id: 'windbarbs',
3096
+ name: 'Wind Barbs',
3097
+ isDisplayed: true
3098
+ }
3099
+ }
3100
+ },
3101
+ shouldHideLocationMarkers: false
3063
3102
  };
3064
3103
  const slice = createSlice({
3065
3104
  initialState: initialSoundingsState,
3066
3105
  name: 'soundings',
3067
3106
  reducers: {
3107
+ registerSoundingsPreset: (draft, payloadAction) => {
3108
+ const preset = payloadAction.payload;
3109
+ if (preset) {
3110
+ draft.selectedSources = preset.selectedSources ?? {};
3111
+ draft.selectedLocations = preset.selectedLocations ?? {};
3112
+ draft.selectedTime = preset.selectedTime ?? new Date().toISOString();
3113
+ draft.diagramType = preset.diagramType ?? 'SKEWT';
3114
+ draft.soundingPanels = preset.soundingPanels ?? initialSoundingsState.soundingPanels;
3115
+ draft.shouldHideLocationMarkers = preset.shouldHideLocationMarkers ?? false;
3116
+ }
3117
+ },
3068
3118
  updateSelectedSource: (draft, payloadAction) => {
3069
3119
  const {
3070
3120
  sourceId,
@@ -3088,6 +3138,15 @@ const slice = createSlice({
3088
3138
  } = payloadAction.payload;
3089
3139
  draft.selectedTime = selectedTime;
3090
3140
  },
3141
+ updateDiagramType: (draft, payloadAction) => {
3142
+ draft.diagramType = payloadAction.payload.diagramType;
3143
+ },
3144
+ toggleLocationMarkers: (draft, payloadAction) => {
3145
+ const {
3146
+ shouldHideLocationMarkers
3147
+ } = payloadAction.payload;
3148
+ draft.shouldHideLocationMarkers = shouldHideLocationMarkers;
3149
+ },
3091
3150
  addSoundingPanelsFromPreset: (draft, payloadAction) => {
3092
3151
  const {
3093
3152
  order,
@@ -3131,7 +3190,9 @@ const getSourceSelections = createSelector(getSoundingsStore, store => {
3131
3190
  return store.selectedSources;
3132
3191
  }, selectorMemoizationOptions);
3133
3192
  const getLocationSelections = createSelector(getSoundingsStore, store => store.selectedLocations, selectorMemoizationOptions);
3134
- const getSelectedTime = createSelector(getSoundingsStore, store => store.selectedTime, selectorMemoizationOptions);
3193
+ const getSelectedTime = createSelector(getSoundingsStore, store => store.selectedTime ?? new Date().toISOString(), selectorMemoizationOptions);
3194
+ const getDiagramType = createSelector(getSoundingsStore, store => store.diagramType ?? 'SKEWT', selectorMemoizationOptions);
3195
+ const getShouldHideLocationMarkers = createSelector(getSoundingsStore, store => store.shouldHideLocationMarkers ?? false, selectorMemoizationOptions);
3135
3196
  const getSelectedSources = createSelector(getSoundingsStore, store => {
3136
3197
  const sources = store.selectedSources;
3137
3198
  const activeSources = Object.keys(sources).reduce((list, key) => {
@@ -3139,11 +3200,11 @@ const getSelectedSources = createSelector(getSoundingsStore, store => {
3139
3200
  if (value.isChecked === true) {
3140
3201
  return list.concat({
3141
3202
  collectionId: key,
3142
- url: value.url,
3203
+ url: value.url ?? '',
3143
3204
  instanceId: value.instanceId,
3144
3205
  verticalRange: value.verticalRange,
3145
3206
  parameterMappings: value.parameterMappings,
3146
- name: value.name || ''
3207
+ name: value.name ?? ''
3147
3208
  });
3148
3209
  }
3149
3210
  return list;
@@ -3156,6 +3217,27 @@ const getSelectedSourcesAmount = createSelector(getSelectedSources, activeSource
3156
3217
  const getSelectedSourcesIds = createSelector(getSelectedSources, activeSources => {
3157
3218
  return activeSources.map(source => source.collectionId);
3158
3219
  }, selectorMemoizationOptions);
3220
+ const getSoundingsPreset = createSelector(getSoundingsStore, soundingsStore => {
3221
+ const {
3222
+ selectedTime,
3223
+ selectedSources,
3224
+ ...rest
3225
+ } = soundingsStore;
3226
+ const filteredSources = Object.entries(selectedSources).reduce((acc, [key, value]) => {
3227
+ const {
3228
+ instanceId,
3229
+ ...sourceWithoutInstance
3230
+ } = value;
3231
+ return {
3232
+ ...acc,
3233
+ [key]: sourceWithoutInstance
3234
+ };
3235
+ }, {});
3236
+ return {
3237
+ ...rest,
3238
+ selectedSources: filteredSources
3239
+ };
3240
+ }, selectorMemoizationOptions);
3159
3241
  const getSoundingPanelsInOrder = createSelector(getSoundingsStore, store => {
3160
3242
  const {
3161
3243
  order = [],
@@ -3167,6 +3249,104 @@ const getSoundingPanelsInOrder = createSelector(getSoundingsStore, store => {
3167
3249
  return order.map(id => entities[id]);
3168
3250
  }, selectorMemoizationOptions);
3169
3251
 
3252
+ var selectors = /*#__PURE__*/Object.freeze({
3253
+ __proto__: null,
3254
+ getDiagramType: getDiagramType,
3255
+ getLocationSelections: getLocationSelections,
3256
+ getSelectedSources: getSelectedSources,
3257
+ getSelectedSourcesAmount: getSelectedSourcesAmount,
3258
+ getSelectedSourcesIds: getSelectedSourcesIds,
3259
+ getSelectedTime: getSelectedTime,
3260
+ getShouldHideLocationMarkers: getShouldHideLocationMarkers,
3261
+ getSoundingPanelsInOrder: getSoundingPanelsInOrder,
3262
+ getSoundingsPreset: getSoundingsPreset,
3263
+ getSourceSelections: getSourceSelections
3264
+ });
3265
+
3266
+ /* *
3267
+ * Licensed under the Apache License, Version 2.0 (the "License");
3268
+ * you may not use this file except in compliance with the License.
3269
+ * You may obtain a copy of the License at
3270
+ *
3271
+ * http://www.apache.org/licenses/LICENSE-2.0
3272
+ *
3273
+ * Unless required by applicable law or agreed to in writing, software
3274
+ * distributed under the License is distributed on an "AS IS" BASIS,
3275
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3276
+ * See the License for the specific language governing permissions and
3277
+ * limitations under the License.
3278
+ *
3279
+ * Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
3280
+ * Copyright 2025 - Finnish Meteorological Institute (FMI)
3281
+ * Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
3282
+ * */
3283
+ var SoundingsActionOrigin;
3284
+ (function (SoundingsActionOrigin) {
3285
+ SoundingsActionOrigin["soundingsManager"] = "soundingsManager";
3286
+ SoundingsActionOrigin["soundingsComponent"] = "soundingsComponent";
3287
+ })(SoundingsActionOrigin || (SoundingsActionOrigin = {}));
3288
+
3289
+ var types = /*#__PURE__*/Object.freeze({
3290
+ __proto__: null,
3291
+ get SoundingsActionOrigin () { return SoundingsActionOrigin; }
3292
+ });
3293
+
3294
+ const SoundingsMarkersSwitch = ({
3295
+ shouldHideMarkers,
3296
+ toggleMarkers
3297
+ }) => {
3298
+ const {
3299
+ t
3300
+ } = useSoundingsTranslation();
3301
+ return jsxs(Box, {
3302
+ sx: {
3303
+ display: 'flex',
3304
+ flexWrap: 'wrap',
3305
+ alignItems: 'flex-start',
3306
+ 'label:first-of-type': {
3307
+ marginLeft: '5px',
3308
+ marginRight: '-5px'
3309
+ },
3310
+ 'label:last-of-type': {
3311
+ marginLeft: '-5px'
3312
+ },
3313
+ '.switch-button label': {
3314
+ display: 'none'
3315
+ },
3316
+ '.markers-label': {
3317
+ display: 'none'
3318
+ },
3319
+ '@media (min-width: 720px)': {
3320
+ minWidth: '146px',
3321
+ '.markers-label, .switch-button label': {
3322
+ display: 'initial'
3323
+ }
3324
+ }
3325
+ },
3326
+ children: [jsx(Typography, {
3327
+ className: "markers-label",
3328
+ sx: {
3329
+ marginTop: '4px'
3330
+ },
3331
+ variant: "body2",
3332
+ children: t('soundings-toggle-location-markers-label')
3333
+ }), jsx(Box, {
3334
+ sx: {
3335
+ marginTop: '-5px'
3336
+ },
3337
+ children: jsx(SwitchButton, {
3338
+ activeLabel: "ON",
3339
+ checked: !shouldHideMarkers,
3340
+ inActiveLabel: "OFF",
3341
+ onChange: event => {
3342
+ toggleMarkers(!event.target.checked);
3343
+ },
3344
+ tooltip: t('soundings-toggle-location-markers-tooltip')
3345
+ })
3346
+ })]
3347
+ });
3348
+ };
3349
+
3170
3350
  var MenuOptions;
3171
3351
  (function (MenuOptions) {
3172
3352
  MenuOptions["soundingsManager"] = "Sd Manager";
@@ -3180,7 +3360,9 @@ const buttonStyle = {
3180
3360
  const SoundingsMenuBar = ({
3181
3361
  handleClick,
3182
3362
  isOpen,
3183
- selectedSourcesAmount = 0
3363
+ selectedSourcesAmount = 0,
3364
+ shouldHideMarkers,
3365
+ toggleMarkers
3184
3366
  }) => {
3185
3367
  const {
3186
3368
  t
@@ -3219,6 +3401,9 @@ const SoundingsMenuBar = ({
3219
3401
  sx: buttonStyle,
3220
3402
  variant: "tool",
3221
3403
  children: t('locations')
3404
+ }), jsx(SoundingsMarkersSwitch, {
3405
+ shouldHideMarkers: shouldHideMarkers,
3406
+ toggleMarkers: toggleMarkers
3222
3407
  })]
3223
3408
  });
3224
3409
  };
@@ -3235,6 +3420,14 @@ const MenuBarConnect = ({
3235
3420
  const isLocationsOpenInStore = useSelector(store => uiSelectors.getisDialogOpen(store, uiTypes.DialogTypes.SoundingsLocations));
3236
3421
  const isSoundingsManagerOpenInStore = useSelector(store => uiSelectors.getisDialogOpen(store, uiTypes.DialogTypes.SoundingsManager));
3237
3422
  const sourcesAmount = useSelector(getSelectedSourcesAmount);
3423
+ const shouldHideLocationMarkers = useSelector(getShouldHideLocationMarkers);
3424
+ const toggleLocationMarkers = React.useCallback(shouldHide => {
3425
+ dispatch(soundingsActions.toggleLocationMarkers({
3426
+ shouldHideLocationMarkers: shouldHide,
3427
+ panelId: viewId,
3428
+ origin: SoundingsActionOrigin.soundingsComponent
3429
+ }));
3430
+ }, [dispatch, viewId]);
3238
3431
  const dispatchActiveMapId = React.useCallback((dialogType, isOpen, activeMapId) => void dispatch(uiActions.setActiveMapIdForDialog({
3239
3432
  type: dialogType,
3240
3433
  mapId: viewId,
@@ -3262,7 +3455,9 @@ const MenuBarConnect = ({
3262
3455
  return jsx(SoundingsMenuBar, {
3263
3456
  handleClick: handleClick,
3264
3457
  isOpen: isOpen,
3265
- selectedSourcesAmount: sourcesAmount
3458
+ selectedSourcesAmount: sourcesAmount,
3459
+ shouldHideMarkers: shouldHideLocationMarkers,
3460
+ toggleMarkers: toggleLocationMarkers
3266
3461
  });
3267
3462
  };
3268
3463
 
@@ -3402,24 +3597,33 @@ const roundToClosestTimestep = (timeToRound, availableSteps) => {
3402
3597
  }
3403
3598
  return timeToRound;
3404
3599
  };
3405
- const SoundingsTimeSliderConnect = () => {
3600
+ const SoundingsTimeSliderConnect = ({
3601
+ viewId
3602
+ }) => {
3406
3603
  const selectedTime = useSelector(getSelectedTime);
3407
3604
  const sourceSelections = useSelector(getSelectedSources);
3408
3605
  const dispatch = useDispatch();
3409
- const updateSelectedTime = React.useCallback(selectedTime => {
3606
+ const updateSelectedTime = React.useCallback((selectedTime, isInitialCorrection = false) => {
3410
3607
  dispatch(soundingsActions.updateSelectedTime({
3411
- selectedTime
3608
+ selectedTime,
3609
+ panelId: viewId,
3610
+ origin: isInitialCorrection ? SoundingsActionOrigin.soundingsManager : SoundingsActionOrigin.soundingsComponent
3412
3611
  }));
3413
- }, [dispatch]);
3612
+ }, [dispatch, viewId]);
3414
3613
  const {
3415
3614
  data: timesteps
3416
3615
  } = useTimesteps(sourceSelections);
3417
3616
  const mergedTimesteps = [...new Set(timesteps.flat())].sort();
3617
+ const hasInitializedTime = React.useRef(false);
3418
3618
  // Ensure that the selected time is always one of the available timesteps
3419
3619
  const validSelectedTime = roundToClosestTimestep(selectedTime, mergedTimesteps);
3420
3620
  useEffect(() => {
3421
3621
  if (selectedTime !== validSelectedTime) {
3422
- updateSelectedTime(validSelectedTime);
3622
+ const isInitial = !hasInitializedTime.current;
3623
+ updateSelectedTime(validSelectedTime, isInitial);
3624
+ if (isInitial) {
3625
+ hasInitializedTime.current = true;
3626
+ }
3423
3627
  }
3424
3628
  }, [selectedTime, updateSelectedTime, validSelectedTime]);
3425
3629
  return jsx(SoundingsTimeSlider, {
@@ -3458,33 +3662,80 @@ const useGetSize = viewRef => {
3458
3662
  };
3459
3663
  const SoundingsConnect = ({
3460
3664
  viewId,
3461
- diagramType = 'SKEWT'
3665
+ soundingsPreset
3462
3666
  }) => {
3463
3667
  const viewRef = React.useRef(null);
3464
3668
  const size = useGetSize(viewRef);
3465
- const [chosenDiagramType, setChosenDiagramType] = React.useState(diagramType);
3466
3669
  const geojsonLayerId = React.useRef(webmapUtils.generateLayerId()).current;
3467
3670
  const dispatch = useDispatch();
3671
+ const soundingsPresetRef = React.useRef();
3672
+ const presetJustLoadedRef = React.useRef(false);
3673
+ const initialLocationsRef = React.useRef({});
3674
+ const previousLocationsRef = React.useRef({});
3675
+ React.useLayoutEffect(() => {
3676
+ if (soundingsPreset && !isEqual(soundingsPreset, soundingsPresetRef.current)) {
3677
+ soundingsPresetRef.current = soundingsPreset;
3678
+ presetJustLoadedRef.current = true;
3679
+ initialLocationsRef.current = soundingsPreset.selectedLocations || {};
3680
+ previousLocationsRef.current = {};
3681
+ dispatch(soundingsActions.registerSoundingsPreset(soundingsPreset));
3682
+ setTimeout(() => {
3683
+ presetJustLoadedRef.current = false;
3684
+ initialLocationsRef.current = {};
3685
+ }, 500);
3686
+ } else if (!soundingsPreset) {
3687
+ soundingsPresetRef.current = undefined;
3688
+ initialLocationsRef.current = {};
3689
+ previousLocationsRef.current = {};
3690
+ }
3691
+ }, [dispatch, soundingsPreset, viewId]);
3468
3692
  const selectedTime = useSelector(getSelectedTime);
3469
- const sourceSelections = useSelector(getSelectedSources);
3470
- const locationSelections = useSelector(getLocationSelections);
3471
- const selectedFeature = useSelector(state => genericSelectors.getSelectedFeature(state.syncGroups, viewId));
3472
- const selectedMapIds = useSelector(state => genericSelectors.getLinkedMaps(state, viewId));
3693
+ const sourceSelections = useSelector(getSelectedSources, shallowEqual);
3694
+ const locationSelections = useSelector(getLocationSelections, shallowEqual);
3695
+ const diagramType = useSelector(getDiagramType);
3696
+ const shouldHideLocationMarkers = useSelector(getShouldHideLocationMarkers);
3697
+ const setChosenDiagramType = React.useCallback(diagramType => {
3698
+ dispatch(soundingsActions.updateDiagramType({
3699
+ diagramType,
3700
+ panelId: viewId,
3701
+ origin: SoundingsActionOrigin.soundingsComponent
3702
+ }));
3703
+ }, [dispatch, viewId]);
3704
+ const selectedFeature = useSelector(state => genericSelectors.getSelectedFeature(state.syncGroups, viewId), shallowEqual);
3705
+ const selectedMapIds = useSelector(state => genericSelectors.getLinkedMaps(state, viewId), shallowEqual);
3706
+ const firstMapId = selectedMapIds[0];
3473
3707
  const selectedMapPin = useSelector(state =>
3474
3708
  // mapPins are synced so we can arbitrarily choose the first linked map
3475
- mapSelectors.getPinLocation(state, selectedMapIds[0]));
3476
- const collections = useMemo(() => sourceSelections.map(({
3477
- collectionId,
3478
- url,
3479
- name
3480
- }) => ({
3481
- id: collectionId,
3482
- url,
3483
- name: name || collectionId
3484
- })), [sourceSelections]);
3709
+ mapSelectors.getPinLocation(state, firstMapId), shallowEqual);
3710
+ const collectionsRef = React.useRef();
3711
+ const collections = useMemo(() => {
3712
+ const newCollections = sourceSelections.map(({
3713
+ collectionId,
3714
+ url,
3715
+ name
3716
+ }) => ({
3717
+ id: collectionId,
3718
+ url,
3719
+ name: name || collectionId
3720
+ }));
3721
+ if (isEqual(newCollections, collectionsRef.current)) {
3722
+ return collectionsRef.current;
3723
+ }
3724
+ collectionsRef.current = newCollections;
3725
+ return newCollections;
3726
+ }, [sourceSelections]);
3485
3727
  const {
3486
- data: locationData
3728
+ data: rawLocationData
3487
3729
  } = useGetCollectionLocations(collections, true);
3730
+ // Stabilize locationData to prevent infinite loops caused by new array references
3731
+ const locationDataRef = React.useRef();
3732
+ const locationData = useMemo(() => {
3733
+ if (isEqual(rawLocationData, locationDataRef.current)) {
3734
+ return locationDataRef.current;
3735
+ }
3736
+ locationDataRef.current = rawLocationData;
3737
+ return rawLocationData;
3738
+ }, [rawLocationData]);
3488
3739
  /* Make sure the map pin is visible when linking new maps */
3489
3740
  useEffect(() => {
3490
3741
  const toggleMapPinVisibility = displayMapPin => {
@@ -3502,25 +3753,26 @@ const SoundingsConnect = ({
3502
3753
  };
3503
3754
  }, [dispatch, selectedMapIds]);
3504
3755
  // construct and send geojson to map for drawing locations
3505
- useEffect(() => {
3506
- const geoJsonForMap = {
3507
- type: 'FeatureCollection',
3508
- features: (locationData || []).flatMap(({
3509
- id: serviceId,
3510
- features
3511
- }) => features.map(feature => ({
3512
- type: 'Feature',
3513
- geometry: feature.geometry,
3756
+ const geoJsonForMap = useMemo(() => ({
3757
+ type: 'FeatureCollection',
3758
+ features: (locationData || []).flatMap(({
3759
+ id: serviceId,
3760
+ features
3761
+ }) => features.map(feature => ({
3762
+ type: 'Feature',
3763
+ geometry: feature.geometry,
3764
+ id: feature.id,
3765
+ properties: {
3766
+ name: feature.properties.name,
3767
+ mapFeatureClass: MapFeatureClass.ObservationStation,
3768
+ highlightSelectedFeature: selectedFeature?.id === feature.id,
3514
3769
  id: feature.id,
3515
- properties: {
3516
- name: feature.properties.name,
3517
- mapFeatureClass: MapFeatureClass.ObservationStation,
3518
- highlightSelectedFeature: selectedFeature?.id === feature.id,
3519
- id: feature.id,
3520
- serviceId
3521
- }
3522
- })))
3523
- };
3770
+ serviceId,
3771
+ hidden: shouldHideLocationMarkers
3772
+ }
3773
+ })))
3774
+ }), [locationData, selectedFeature?.id, shouldHideLocationMarkers]);
3775
+ useEffect(() => {
3524
3776
  dispatch(genericActions.addSharedData({
3525
3777
  panelId: viewId,
3526
3778
  data: {
@@ -3530,7 +3782,7 @@ const SoundingsConnect = ({
3530
3782
  }]
3531
3783
  }
3532
3784
  }));
3533
- }, [dispatch, geojsonLayerId, locationData, selectedFeature?.id, viewId]);
3785
+ }, [dispatch, geojsonLayerId, geoJsonForMap, viewId]);
3534
3786
  useEffect(() => () => {
3535
3787
  if (viewId) {
3536
3788
  dispatch(syncGroupsActions.deleteSharedData({
@@ -3541,26 +3793,133 @@ const SoundingsConnect = ({
3541
3793
  // Map location selection has changed. Update soundings location
3542
3794
  // since the location menu may be out of sync
3543
3795
  useEffect(() => {
3796
+ if (presetJustLoadedRef.current) {
3797
+ return;
3798
+ }
3544
3799
  const serviceId = selectedFeature?.serviceId;
3545
- if (serviceId) {
3546
- dispatch(soundingsActions.updateSelectedLocation({
3547
- sourceId: serviceId,
3548
- location: selectedFeature?.id
3549
- }));
3800
+ const featureId = selectedFeature?.id;
3801
+ if (serviceId && featureId) {
3802
+ const currentLocation = locationSelectionsRef.current[serviceId];
3803
+ const initialLocation = initialLocationsRef.current[serviceId];
3804
+ if (currentLocation !== featureId && initialLocation !== featureId) {
3805
+ dispatch(soundingsActions.updateSelectedLocation({
3806
+ sourceId: serviceId,
3807
+ location: featureId,
3808
+ panelId: viewId,
3809
+ origin: SoundingsActionOrigin.soundingsComponent
3810
+ }));
3811
+ }
3550
3812
  }
3551
- }, [dispatch, selectedFeature?.id, selectedFeature?.serviceId]);
3552
- // Map pin change on its own implies position selection
3813
+ }, [dispatch, selectedFeature?.id, selectedFeature?.serviceId, viewId]);
3814
+ // Use ref to avoid circular dependency when reading locationSelections
3815
+ const locationSelectionsRef = React.useRef(locationSelections);
3816
+ React.useEffect(() => {
3817
+ locationSelectionsRef.current = locationSelections;
3818
+ }, [locationSelections]);
3819
+ // Makes position requests for all user-selected sources for the current map pin position.
3820
+ // Map pin change on its own implies a new position selection.
3553
3821
  useEffect(() => {
3554
- // Use first source. Multiple sources will be implemented later.
3555
- // https://gitlab.com/opengeoweb/geoweb-assets/-/issues/4254
3556
- const source = sourceSelections[0];
3557
- if (source?.collectionId && !selectedFeature?.id && selectedMapPin?.lon && selectedMapPin.lat) {
3558
- dispatch(soundingsActions.updateSelectedLocation({
3559
- sourceId: source.collectionId,
3560
- location: [selectedMapPin.lon, selectedMapPin.lat]
3561
- }));
3822
+ if (presetJustLoadedRef.current) {
3823
+ return;
3824
+ }
3825
+ if (!selectedFeature?.id && !selectedMapPin?.id && selectedMapPin?.lon && selectedMapPin.lat) {
3826
+ const newLocation = [selectedMapPin.lon, selectedMapPin.lat];
3827
+ // Update location for all selected sources
3828
+ sourceSelections.forEach(source => {
3829
+ if (source?.collectionId) {
3830
+ const currentLocation = locationSelectionsRef.current[source.collectionId];
3831
+ const isSameLocation = Array.isArray(currentLocation) && currentLocation[0] === newLocation[0] && currentLocation[1] === newLocation[1];
3832
+ if (!isSameLocation) {
3833
+ dispatch(soundingsActions.updateSelectedLocation({
3834
+ sourceId: source.collectionId,
3835
+ location: newLocation,
3836
+ panelId: viewId,
3837
+ origin: SoundingsActionOrigin.soundingsComponent
3838
+ }));
3839
+ }
3840
+ }
3841
+ });
3842
+ }
3843
+ }, [selectedMapPin, selectedFeature?.id, sourceSelections, dispatch, viewId]);
3844
+ useEffect(() => {
3845
+ if (!locationData || locationData.length === 0) {
3846
+ return;
3847
+ }
3848
+ if (selectedMapIds.length === 0) {
3849
+ return;
3850
+ }
3851
+ let changedSourceId;
3852
+ for (const source of sourceSelections) {
3853
+ if (source?.collectionId) {
3854
+ const currentLocation = locationSelections[source.collectionId];
3855
+ const previousLocation = previousLocationsRef.current[source.collectionId];
3856
+ if (currentLocation !== previousLocation) {
3857
+ changedSourceId = source.collectionId;
3858
+ if (!presetJustLoadedRef.current) {
3859
+ break;
3860
+ }
3861
+ }
3862
+ }
3863
+ }
3864
+ if (!changedSourceId) {
3865
+ return;
3866
+ }
3867
+ const selectedLocation = locationSelections[changedSourceId];
3868
+ if (!selectedLocation) {
3869
+ return;
3870
+ }
3871
+ previousLocationsRef.current = {
3872
+ ...previousLocationsRef.current,
3873
+ [changedSourceId]: selectedLocation
3874
+ };
3875
+ const serviceData = locationData.find(service => service.id === changedSourceId);
3876
+ if (!serviceData) {
3877
+ return;
3878
+ }
3879
+ if (typeof selectedLocation === 'string') {
3880
+ const foundFeature = serviceData.features.find(feature => feature.id === selectedLocation);
3881
+ if (foundFeature?.geometry.type === 'Point') {
3882
+ const [lon, lat] = foundFeature.geometry.coordinates;
3883
+ const mapPinNeedsUpdate = selectedMapPin?.id !== selectedLocation || selectedMapPin?.lon !== lon || selectedMapPin?.lat !== lat;
3884
+ const featureNeedsUpdate = selectedFeature?.id !== selectedLocation;
3885
+ if (mapPinNeedsUpdate) {
3886
+ selectedMapIds.forEach(mapId => {
3887
+ dispatch(mapActions.setMapPinLocation({
3888
+ mapId,
3889
+ mapPinLocation: {
3890
+ lon,
3891
+ lat,
3892
+ id: selectedLocation
3893
+ }
3894
+ }));
3895
+ });
3896
+ }
3897
+ if (featureNeedsUpdate) {
3898
+ dispatch(genericActions.addSharedData({
3899
+ panelId: viewId,
3900
+ data: {
3901
+ selectedFeatureId: selectedLocation
3902
+ }
3903
+ }));
3904
+ }
3905
+ }
3906
+ } else if (Array.isArray(selectedLocation)) {
3907
+ const [lon, lat] = selectedLocation;
3908
+ const needsUpdate = selectedMapPin?.lon !== lon || selectedMapPin?.lat !== lat;
3909
+ if (needsUpdate) {
3910
+ selectedMapIds.forEach(mapId => {
3911
+ dispatch(mapActions.setMapPinLocation({
3912
+ mapId,
3913
+ mapPinLocation: {
3914
+ lon,
3915
+ lat
3916
+ }
3917
+ }));
3918
+ });
3919
+ }
3562
3920
  }
3563
- }, [selectedMapPin, selectedFeature?.id, sourceSelections, dispatch]);
3921
+ // eslint-disable-next-line react-hooks/exhaustive-deps
3922
+ }, [locationData, locationSelections, sourceSelections, selectedMapIds, dispatch, viewId]);
3564
3923
  const {
3565
3924
  error,
3566
3925
  data,
@@ -3627,7 +3986,7 @@ const SoundingsConnect = ({
3627
3986
  },
3628
3987
  children: jsx(SharedMouseYProvider, {
3629
3988
  children: jsx(SortableAccordions, {
3630
- chosenDiagramType: chosenDiagramType,
3989
+ chosenDiagramType: diagramType,
3631
3990
  data: data,
3632
3991
  onOrderChange: changeOrder,
3633
3992
  setChosenDiagramType: setChosenDiagramType,
@@ -3642,7 +4001,9 @@ const SoundingsConnect = ({
3642
4001
  flex: '0 0 auto',
3643
4002
  height: `${TIMESLIDER_HEIGHT}px`
3644
4003
  },
3645
- children: jsx(SoundingsTimeSliderConnect, {})
4004
+ children: jsx(SoundingsTimeSliderConnect, {
4005
+ viewId: viewId
4006
+ })
3646
4007
  })]
3647
4008
  })
3648
4009
  })]
@@ -3718,6 +4079,7 @@ const SoundingsSourceMenu = ({
3718
4079
  instanceId,
3719
4080
  url
3720
4081
  } = sourceSelections[collection.id] || {};
4082
+ const sourceUrl = url || collection.url;
3721
4083
  const hasInstances = collection.instances.length > 0;
3722
4084
  const selectedInstanceId = instanceId || collection.instances[0]?.id || undefined;
3723
4085
  return jsxs(ListItem, {
@@ -3732,7 +4094,7 @@ const SoundingsSourceMenu = ({
3732
4094
  onChange: event => {
3733
4095
  const verticalRange = selectedInstanceId !== undefined ? extractVerticalRange(collection, selectedInstanceId) : '';
3734
4096
  updateCollection(collection.id, {
3735
- url,
4097
+ url: sourceUrl,
3736
4098
  isChecked: event.target.checked,
3737
4099
  instanceId: selectedInstanceId,
3738
4100
  verticalRange
@@ -3748,7 +4110,7 @@ const SoundingsSourceMenu = ({
3748
4110
  const instanceId = event.target.value;
3749
4111
  const verticalRange = extractVerticalRange(collection, instanceId);
3750
4112
  updateCollection(collection.id, {
3751
- url,
4113
+ url: sourceUrl,
3752
4114
  instanceId,
3753
4115
  verticalRange
3754
4116
  });
@@ -3811,30 +4173,39 @@ const SoundingsSourceDialogConnect = ({
3811
4173
  dialogOrder,
3812
4174
  setDialogOrder
3813
4175
  } = useSetupDialog(uiTypes.DialogTypes.SoundingsSources);
4176
+ const panelId = useSelector(store => uiSelectors.getDialogMapId(store, uiTypes.DialogTypes.SoundingsSources));
3814
4177
  const updateCollection = React.useCallback((sourceId, value) => {
3815
4178
  dispatch(soundingsActions.updateSelectedSource({
3816
4179
  sourceId,
3817
- value
4180
+ value,
4181
+ panelId,
4182
+ origin: SoundingsActionOrigin.soundingsComponent
3818
4183
  }));
3819
- }, [dispatch]);
4184
+ }, [dispatch, panelId]);
3820
4185
  const sourceSelections = useSelector(getSourceSelections);
3821
4186
  // Set mappings for each collection
3822
4187
  React.useEffect(() => {
3823
4188
  soundingsCollections?.forEach(collection => {
4189
+ const currentSelection = sourceSelections[collection.id];
4190
+ if (currentSelection?.url && currentSelection?.parameterMappings) {
4191
+ return;
4192
+ }
4193
+ const newValue = {
4194
+ url: collection.url,
4195
+ isChecked: currentSelection?.isChecked || false,
4196
+ parameterMappings: collection.parameterMappings,
4197
+ ...(collection.name && collection.name !== '' ? {
4198
+ name: collection.name
4199
+ } : {})
4200
+ };
3824
4201
  dispatch(soundingsActions.updateSelectedSource({
3825
4202
  sourceId: collection.id,
3826
- value: {
3827
- url: collection.url,
3828
- isChecked: sourceSelections[collection.id]?.isChecked || false,
3829
- parameterMappings: collection.parameterMappings,
3830
- ...(collection.name && collection.name !== '' ? {
3831
- name: collection.name
3832
- } : {})
3833
- }
4203
+ value: newValue,
4204
+ origin: SoundingsActionOrigin.soundingsManager
3834
4205
  }));
3835
4206
  });
3836
4207
  // eslint-disable-next-line react-hooks/exhaustive-deps
3837
- }, [dispatch, soundingsCollections]);
4208
+ }, [dispatch, soundingsCollections, panelId]);
3838
4209
  const {
3839
4210
  data,
3840
4211
  isPending,
@@ -4004,8 +4375,6 @@ const LocationsMenuConnect = ({
4004
4375
  const locationSelections = useSelector(getLocationSelections);
4005
4376
  const selectedSourceIds = useSelector(getSelectedSourcesIds);
4006
4377
  const sourcesAmount = useSelector(getSelectedSourcesAmount);
4007
- const panelId = useSelector(store => uiSelectors.getDialogMapId(store, uiTypes.DialogTypes.SoundingsLocations));
4008
- const selectedMapIds = useSelector(state => genericSelectors.getLinkedMaps(state, panelId));
4009
4378
  const {
4010
4379
  data,
4011
4380
  isPending,
@@ -4014,22 +4383,6 @@ const LocationsMenuConnect = ({
4014
4383
  const services = useMemo(() => {
4015
4384
  return data?.filter(service => selectedSourceIds.includes(service.id)) || [];
4016
4385
  }, [data, selectedSourceIds]);
4017
- const handleFeatureSelect = (panelId, featureId) => {
4018
- dispatch(genericActions.addSharedData({
4019
- panelId,
4020
- data: {
4021
- selectedFeatureId: featureId
4022
- }
4023
- }));
4024
- };
4025
- const setMapPinLocation = mapPinLocation => {
4026
- selectedMapIds.forEach(mapId => {
4027
- dispatch(mapActions.setMapPinLocation({
4028
- mapId,
4029
- mapPinLocation
4030
- }));
4031
- });
4032
- };
4033
4386
  return jsx(LocationsMenu, {
4034
4387
  error: error,
4035
4388
  isLoading: isPending,
@@ -4045,25 +4398,6 @@ const LocationsMenuConnect = ({
4045
4398
  sourceId,
4046
4399
  location: locationId
4047
4400
  }));
4048
- // Use first source. Multiple sources will be implemented later.
4049
- // https://gitlab.com/opengeoweb/geoweb-assets/-/issues/4254
4050
- const isFirstSource = sourceId === selectedSourceIds[0];
4051
- if (!isFirstSource) {
4052
- return;
4053
- }
4054
- if (locationId) {
4055
- handleFeatureSelect(panelId, locationId);
4056
- }
4057
- const foundFeature = services[0].features.find(feature => feature.id === locationId);
4058
- // check type for typescript
4059
- if (foundFeature?.geometry.type === 'Point') {
4060
- const [lon, lat] = foundFeature.geometry.coordinates;
4061
- setMapPinLocation({
4062
- lon,
4063
- lat,
4064
- id: locationId
4065
- });
4066
- }
4067
4401
  }
4068
4402
  });
4069
4403
  };
@@ -4211,7 +4545,7 @@ const SoundingsManagerConnect = () => {
4211
4545
  const soundingsLookUp = payload => {
4212
4546
  if (payload.componentType === 'Soundings') {
4213
4547
  return jsx(SoundingsConnect, {
4214
- diagramType: payload.diagramType ? payload.diagramType : 'SKEWT',
4548
+ soundingsPreset: payload.initialProps,
4215
4549
  viewId: payload.id
4216
4550
  });
4217
4551
  }
@@ -4246,4 +4580,4 @@ const createMockStore = mockState => configureStore({
4246
4580
  middleware: getDefaultMiddleware => getDefaultMiddleware().concat(storeMiddlewares)
4247
4581
  });
4248
4582
 
4249
- export { LocationsMenuConnect, SOUNDINGS_NAMESPACE, SoundingsConnect, SoundingsManagerConnect, SoundingsSourceDialogConnect, createMockStore, initSoundingsI18n, soundingDiagrams, soundingsLookUp, soundingsReducersMap, soundingsTranslations, translateInTestsAndStories, useSoundingsTranslation };
4583
+ export { LocationsMenuConnect, SOUNDINGS_NAMESPACE, SoundingsActionOrigin, SoundingsConnect, SoundingsManagerConnect, SoundingsSourceDialogConnect, types as SoundingsTypes, createMockStore, initSoundingsI18n, soundingDiagrams, soundingsActions, soundingsLookUp, soundingsReducer, soundingsReducersMap, selectors as soundingsSelectors, soundingsTranslations, translateInTestsAndStories, useSoundingsTranslation };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeoweb/soundings",
3
- "version": "15.0.0",
3
+ "version": "15.1.0",
4
4
  "description": "GeoWeb soundings library for the opengeoweb project",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -8,24 +8,24 @@
8
8
  "url": "git@gitlab.com:opengeoweb/opengeoweb.git"
9
9
  },
10
10
  "dependencies": {
11
- "@opengeoweb/api": "15.0.0",
11
+ "@opengeoweb/api": "15.1.0",
12
12
  "@tanstack/react-query": "^5.85.5",
13
13
  "react": "^18.3.1",
14
14
  "d3": "^7.9.0",
15
15
  "react-i18next": "^15.1.1",
16
16
  "i18next": "^25.0.1",
17
- "@opengeoweb/shared": "15.0.0",
17
+ "@opengeoweb/shared": "15.1.0",
18
18
  "@mui/material": "^7.0.1",
19
- "@opengeoweb/store": "15.0.0",
19
+ "@opengeoweb/store": "15.1.0",
20
20
  "react-redux": "^9.2.0",
21
- "@opengeoweb/theme": "15.0.0",
21
+ "@opengeoweb/theme": "15.1.0",
22
22
  "@reduxjs/toolkit": "^2.6.1",
23
- "@opengeoweb/time-slider": "15.0.0",
23
+ "@opengeoweb/time-slider": "15.1.0",
24
24
  "react-window": "^1.8.10",
25
- "@opengeoweb/webmap": "15.0.0",
25
+ "@opengeoweb/webmap": "15.1.0",
26
26
  "lodash": "^4.17.21",
27
27
  "@types/geojson": "^7946.0.14",
28
- "@opengeoweb/webmap-react": "15.0.0",
28
+ "@opengeoweb/webmap-react": "15.1.0",
29
29
  "react-resizable-layout": "^0.7.3",
30
30
  "react-sortablejs": "^6.1.4"
31
31
  },
package/src/index.d.ts CHANGED
@@ -5,3 +5,5 @@ export * from './lib/utils/soundingsLookUp';
5
5
  export * from './lib/api/edr-api/types';
6
6
  export * from './lib/store/store';
7
7
  export * from './lib/utils/i18n';
8
+ export * as SoundingsTypes from './lib/store/types';
9
+ export { SoundingsActionOrigin } from './lib/store/types';
@@ -1,8 +1,8 @@
1
- import type { SoundingsCollection } from '@opengeoweb/shared';
1
+ import type { Coordinates, SoundingsCollection } from '@opengeoweb/shared';
2
2
  import type { SoundingFeature } from './types';
3
3
  import type { ServiceWithLocations } from '../../components/MenuBar/LocationsMenu';
4
4
  import type { SoundingsCollectionsWithInstances } from '../../components/Soundings/types';
5
- import type { Coordinates, SourceType } from '../../store/types';
5
+ import type { SourceType } from '../../store/types';
6
6
  export declare const sortSoundingFeatures: (data: SoundingFeature[]) => SoundingFeature[];
7
7
  export declare const defaultParameterMappings: {
8
8
  temperature: string;
@@ -1,5 +1,5 @@
1
+ import type { Coordinates } from '@opengeoweb/shared';
1
2
  import type { LocationsResponseFromBE } from '../../api/edr-api/types';
2
- import type { Coordinates } from '../../store/types';
3
3
  export type ServiceWithLocations = LocationsResponseFromBE & {
4
4
  id: string;
5
5
  name: string;
@@ -8,6 +8,8 @@ interface SoundingsMenuBarProps {
8
8
  handleClick: (menuType: MenuOptions) => void;
9
9
  isOpen: Record<MenuOptions, boolean>;
10
10
  selectedSourcesAmount?: number;
11
+ shouldHideMarkers: boolean;
12
+ toggleMarkers: (shouldHideMarkers: boolean) => void;
11
13
  }
12
- export declare const SoundingsMenuBar: ({ handleClick, isOpen, selectedSourcesAmount, }: SoundingsMenuBarProps) => React.ReactElement;
14
+ export declare const SoundingsMenuBar: ({ handleClick, isOpen, selectedSourcesAmount, shouldHideMarkers, toggleMarkers, }: SoundingsMenuBarProps) => React.ReactElement;
13
15
  export {};
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ export interface SoundingsMarkersSwitchProps {
3
+ shouldHideMarkers: boolean;
4
+ toggleMarkers: (shouldHideMarkers: boolean) => void;
5
+ }
6
+ export declare const SoundingsMarkersSwitch: React.FC<SoundingsMarkersSwitchProps>;
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import type { SourceSelection } from '../../store/types';
2
+ import type { SourceSelection } from '@opengeoweb/shared';
3
3
  import type { SoundingsCollectionsWithInstances } from '../Soundings/types';
4
4
  interface SoundingsSourceMenuProps {
5
5
  soundingsCollections: SoundingsCollectionsWithInstances[] | undefined;
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
- import type { SoundingDiagramType } from '../../api/edr-api/types';
2
+ import type { SoundingsStore } from '../../store/types';
3
3
  interface SoundingsConnectProps {
4
4
  viewId: string;
5
- diagramType?: SoundingDiagramType;
5
+ soundingsPreset?: SoundingsStore;
6
6
  }
7
- export declare const SoundingsConnect: ({ viewId, diagramType, }: SoundingsConnectProps) => React.ReactElement;
7
+ export declare const SoundingsConnect: ({ viewId, soundingsPreset, }: SoundingsConnectProps) => React.ReactElement;
8
8
  export {};
@@ -1,2 +1,6 @@
1
1
  import React from 'react';
2
- export declare const SoundingsTimeSliderConnect: () => React.ReactElement;
2
+ interface SoundingsTimeSliderConnectProps {
3
+ viewId: string;
4
+ }
5
+ export declare const SoundingsTimeSliderConnect: ({ viewId, }: SoundingsTimeSliderConnectProps) => React.ReactElement;
6
+ export {};
@@ -1,10 +1,13 @@
1
1
  import type { Draft, PayloadAction } from '@reduxjs/toolkit';
2
- import type { SoundingPanels, SoundingsStore, UpdateLocationPayload, UpdateSourcePayload, UpdateTimePayload } from './types';
2
+ import type { SoundingPanels, SoundingsStore, UpdateLocationPayload, UpdateSourcePayload, UpdateTimePayload, UpdateDiagramTypePayload, ToggleLocationMarkersPayload } from './types';
3
3
  export declare const initialSoundingsState: SoundingsStore;
4
4
  export declare const soundingsReducer: import("redux").Reducer<SoundingsStore>, soundingsActions: import("@reduxjs/toolkit").CaseReducerActions<{
5
+ registerSoundingsPreset: (draft: Draft<SoundingsStore>, payloadAction: PayloadAction<SoundingsStore>) => void;
5
6
  updateSelectedSource: (draft: Draft<SoundingsStore>, payloadAction: PayloadAction<UpdateSourcePayload>) => void;
6
7
  updateSelectedLocation: (draft: Draft<SoundingsStore>, payloadAction: PayloadAction<UpdateLocationPayload>) => void;
7
8
  updateSelectedTime: (draft: Draft<SoundingsStore>, payloadAction: PayloadAction<UpdateTimePayload>) => void;
9
+ updateDiagramType: (draft: Draft<SoundingsStore>, payloadAction: PayloadAction<UpdateDiagramTypePayload>) => void;
10
+ toggleLocationMarkers: (draft: Draft<SoundingsStore>, payloadAction: PayloadAction<ToggleLocationMarkersPayload>) => void;
8
11
  addSoundingPanelsFromPreset: (draft: Draft<SoundingsStore>, payloadAction: PayloadAction<SoundingPanels>) => void;
9
12
  changeSoundingPanelOrder: (draft: Draft<SoundingsStore>, payloadAction: PayloadAction<{
10
13
  newOrderOfIds: string[];
@@ -1,4 +1,5 @@
1
- import type { SoundingsModuleState, SoundingsStore, SourceSelection, SourceType } from './types';
1
+ import type { SourceSelection } from '@opengeoweb/shared';
2
+ import type { SoundingsModuleState, SoundingsStore, SourceType } from './types';
2
3
  export declare const getSourceSelections: ((state: SoundingsModuleState) => Record<string, SourceSelection>) & {
3
4
  clearCache: () => void;
4
5
  resultsCount: () => number;
@@ -20,18 +21,18 @@ export declare const getSourceSelections: ((state: SoundingsModuleState) => Reco
20
21
  memoize: typeof import("reselect").weakMapMemoize;
21
22
  argsMemoize: typeof import("reselect").weakMapMemoize;
22
23
  };
23
- export declare const getLocationSelections: ((state: SoundingsModuleState) => Record<string, string | import("./types").Coordinates | undefined>) & {
24
+ export declare const getLocationSelections: ((state: SoundingsModuleState) => Record<string, string | import("@opengeoweb/shared").Coordinates | undefined>) & {
24
25
  clearCache: () => void;
25
26
  resultsCount: () => number;
26
27
  resetResultsCount: () => void;
27
28
  } & {
28
- resultFunc: (resultFuncArgs_0: SoundingsStore) => Record<string, string | import("./types").Coordinates | undefined>;
29
- memoizedResultFunc: ((resultFuncArgs_0: SoundingsStore) => Record<string, string | import("./types").Coordinates | undefined>) & {
29
+ resultFunc: (resultFuncArgs_0: SoundingsStore) => Record<string, string | import("@opengeoweb/shared").Coordinates | undefined>;
30
+ memoizedResultFunc: ((resultFuncArgs_0: SoundingsStore) => Record<string, string | import("@opengeoweb/shared").Coordinates | undefined>) & {
30
31
  clearCache: () => void;
31
32
  resultsCount: () => number;
32
33
  resetResultsCount: () => void;
33
34
  };
34
- lastResult: () => Record<string, string | import("./types").Coordinates | undefined>;
35
+ lastResult: () => Record<string, string | import("@opengeoweb/shared").Coordinates | undefined>;
35
36
  dependencies: [(store: SoundingsModuleState) => SoundingsStore];
36
37
  recomputations: () => number;
37
38
  resetRecomputations: () => void;
@@ -62,6 +63,48 @@ export declare const getSelectedTime: ((state: SoundingsModuleState) => string)
62
63
  memoize: typeof import("reselect").weakMapMemoize;
63
64
  argsMemoize: typeof import("reselect").weakMapMemoize;
64
65
  };
66
+ export declare const getDiagramType: ((state: SoundingsModuleState) => "SKEWT" | "BIJVOET" | "EMAGRAM") & {
67
+ clearCache: () => void;
68
+ resultsCount: () => number;
69
+ resetResultsCount: () => void;
70
+ } & {
71
+ resultFunc: (resultFuncArgs_0: SoundingsStore) => "SKEWT" | "BIJVOET" | "EMAGRAM";
72
+ memoizedResultFunc: ((resultFuncArgs_0: SoundingsStore) => "SKEWT" | "BIJVOET" | "EMAGRAM") & {
73
+ clearCache: () => void;
74
+ resultsCount: () => number;
75
+ resetResultsCount: () => void;
76
+ };
77
+ lastResult: () => "SKEWT" | "BIJVOET" | "EMAGRAM";
78
+ dependencies: [(store: SoundingsModuleState) => SoundingsStore];
79
+ recomputations: () => number;
80
+ resetRecomputations: () => void;
81
+ dependencyRecomputations: () => number;
82
+ resetDependencyRecomputations: () => void;
83
+ } & {
84
+ memoize: typeof import("reselect").weakMapMemoize;
85
+ argsMemoize: typeof import("reselect").weakMapMemoize;
86
+ };
87
+ export declare const getShouldHideLocationMarkers: ((state: SoundingsModuleState) => boolean) & {
88
+ clearCache: () => void;
89
+ resultsCount: () => number;
90
+ resetResultsCount: () => void;
91
+ } & {
92
+ resultFunc: (resultFuncArgs_0: SoundingsStore) => boolean;
93
+ memoizedResultFunc: ((resultFuncArgs_0: SoundingsStore) => boolean) & {
94
+ clearCache: () => void;
95
+ resultsCount: () => number;
96
+ resetResultsCount: () => void;
97
+ };
98
+ lastResult: () => boolean;
99
+ dependencies: [(store: SoundingsModuleState) => SoundingsStore];
100
+ recomputations: () => number;
101
+ resetRecomputations: () => void;
102
+ dependencyRecomputations: () => number;
103
+ resetDependencyRecomputations: () => void;
104
+ } & {
105
+ memoize: typeof import("reselect").weakMapMemoize;
106
+ argsMemoize: typeof import("reselect").weakMapMemoize;
107
+ };
65
108
  export declare const getSelectedSources: ((state: SoundingsModuleState) => SourceType[]) & {
66
109
  clearCache: () => void;
67
110
  resultsCount: () => number;
@@ -165,6 +208,27 @@ export declare const getSelectedSourcesIds: ((state: SoundingsModuleState) => st
165
208
  memoize: typeof import("reselect").weakMapMemoize;
166
209
  argsMemoize: typeof import("reselect").weakMapMemoize;
167
210
  };
211
+ export declare const getSoundingsPreset: ((state: SoundingsModuleState) => SoundingsStore) & {
212
+ clearCache: () => void;
213
+ resultsCount: () => number;
214
+ resetResultsCount: () => void;
215
+ } & {
216
+ resultFunc: (resultFuncArgs_0: SoundingsStore) => SoundingsStore;
217
+ memoizedResultFunc: ((resultFuncArgs_0: SoundingsStore) => SoundingsStore) & {
218
+ clearCache: () => void;
219
+ resultsCount: () => number;
220
+ resetResultsCount: () => void;
221
+ };
222
+ lastResult: () => SoundingsStore;
223
+ dependencies: [(store: SoundingsModuleState) => SoundingsStore];
224
+ recomputations: () => number;
225
+ resetRecomputations: () => void;
226
+ dependencyRecomputations: () => number;
227
+ resetDependencyRecomputations: () => void;
228
+ } & {
229
+ memoize: typeof import("reselect").weakMapMemoize;
230
+ argsMemoize: typeof import("reselect").weakMapMemoize;
231
+ };
168
232
  export declare const getSoundingPanelsInOrder: ((state: SoundingsModuleState) => import("./types").SoundingPanel[]) & {
169
233
  clearCache: () => void;
170
234
  resultsCount: () => number;
@@ -1,6 +1,9 @@
1
1
  import type { Store } from '@reduxjs/toolkit';
2
2
  import type { UIModuleState, WebMapStateModuleState } from '@opengeoweb/store';
3
3
  import type { SoundingsModuleState } from './types';
4
+ export * as soundingsSelectors from './selectors';
5
+ export { soundingsReducer, soundingsActions } from './reducer';
6
+ export type { SoundingsModuleState, SoundingsStore } from './types';
4
7
  export declare const soundingsReducersMap: {
5
8
  webmap: import("redux").Reducer<import("dist/libs/store/src/store/map/types").WebMapState>;
6
9
  services: import("redux").Reducer<import("dist/libs/store/src/store/map/types").ServiceState>;
@@ -1,3 +1,8 @@
1
+ import type { Coordinates, SourceSelection } from '@opengeoweb/shared';
2
+ export declare enum SoundingsActionOrigin {
3
+ soundingsManager = "soundingsManager",
4
+ soundingsComponent = "soundingsComponent"
5
+ }
1
6
  export interface SourceType {
2
7
  collectionId: string;
3
8
  url: string;
@@ -6,27 +11,27 @@ export interface SourceType {
6
11
  parameterMappings?: Record<string, string>;
7
12
  name?: string;
8
13
  }
9
- export interface UpdateSourcePayload {
14
+ export interface SoundingsPayload {
15
+ panelId?: string;
16
+ origin?: string;
17
+ }
18
+ export interface UpdateSourcePayload extends SoundingsPayload {
10
19
  sourceId: string;
11
20
  value: SourceSelection;
12
21
  }
13
- export interface SourceSelection {
14
- url: string;
15
- instanceId?: string;
16
- isChecked?: boolean;
17
- verticalRange?: string;
18
- parameterMappings?: Record<string, string>;
19
- name?: string;
20
- }
21
- export interface UpdateLocationPayload {
22
+ export interface UpdateLocationPayload extends SoundingsPayload {
22
23
  sourceId: string;
23
24
  location: string | Coordinates | undefined;
24
25
  }
25
- export interface UpdateTimePayload {
26
+ export interface UpdateTimePayload extends SoundingsPayload {
26
27
  selectedTime: string;
27
28
  }
28
- /** [lon, lat] */
29
- export type Coordinates = [number, number];
29
+ export interface UpdateDiagramTypePayload extends SoundingsPayload {
30
+ diagramType: 'SKEWT' | 'BIJVOET' | 'EMAGRAM';
31
+ }
32
+ export interface ToggleLocationMarkersPayload extends SoundingsPayload {
33
+ shouldHideLocationMarkers: boolean;
34
+ }
30
35
  export interface SoundingPanel {
31
36
  id: string;
32
37
  name: string;
@@ -42,9 +47,13 @@ export interface SoundingsStore {
42
47
  /** key: sourceId, value: locationId or position coordinates */
43
48
  selectedLocations: Record<string, string | Coordinates | undefined>;
44
49
  /** ISO date, e.g., 2025-01-02T12:00:00.000Z */
45
- selectedTime: string;
50
+ selectedTime?: string;
51
+ /** Diagram type: SKEWT, BIJVOET, or EMAGRAM */
52
+ diagramType?: 'SKEWT' | 'BIJVOET' | 'EMAGRAM';
46
53
  soundingPanels: SoundingPanels;
54
+ /** Whether to hide location markers on the map */
55
+ shouldHideLocationMarkers?: boolean;
47
56
  }
48
57
  export interface SoundingsModuleState {
49
- soundings: SoundingsStore;
58
+ soundings?: SoundingsStore;
50
59
  }
@@ -1,10 +1,9 @@
1
- import type { SoundingDiagramType } from '../api/edr-api/types';
1
+ import type { SoundingsStore } from '../store/types';
2
2
  export type SoundingsComponentType = 'Soundings';
3
3
  export interface SoundingsLookUpPayload {
4
4
  componentType: SoundingsComponentType;
5
5
  id: string;
6
6
  title?: string;
7
- diagramType?: SoundingDiagramType;
8
- initialProps: Record<string, unknown>;
7
+ initialProps: SoundingsStore;
9
8
  }
10
9
  export declare const soundingsLookUp: (payload: SoundingsLookUpPayload) => React.JSX.Element | null;