@opengeoweb/soundings 13.2.1 → 14.0.1

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
@@ -3,7 +3,7 @@ import React, { useRef, useEffect, useMemo, useState, Fragment as Fragment$1 } f
3
3
  import { useSelector, useDispatch } from 'react-redux';
4
4
  import { Box, Stack, Menu, ListSubheader, Typography, MenuItem, Tooltip, LinearProgress, Card, List, ListItem, ListItemIcon, Checkbox, ListItemText, Grid, ListItemButton } from '@mui/material';
5
5
  import { safelyInitI18nForTestsAndStories, sharedTranslations, SHARED_NAMESPACE, dateUtils, CustomSlider, CustomToggleButton, AlertBanner, ToolContainerDraggable, StyledSelect, SearchField, CustomAccordion } from '@opengeoweb/shared';
6
- import { selectorMemoizationOptions, uiSelectors, uiTypes, uiActions, genericActions, useSetupDialog, storeReducerMap, storeMiddlewares } from '@opengeoweb/store';
6
+ import { selectorMemoizationOptions, uiSelectors, uiTypes, uiActions, genericSelectors, mapSelectors, genericActions, syncGroupsActions, mapActions, useSetupDialog, storeReducerMap, storeMiddlewares } from '@opengeoweb/store';
7
7
  import { ParseISOTimeRangeDuration, parseISO8601IntervalToDateInterval, webmapUtils } from '@opengeoweb/webmap';
8
8
  import { MapFeatureClass } from '@opengeoweb/webmap-react';
9
9
  import * as d3 from 'd3';
@@ -246,7 +246,7 @@ const drawIsobars = ({
246
246
  * Copyright 2025 - Finnish Meteorological Institute (FMI)
247
247
  * Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
248
248
  * */
249
- const SOUNDING_COLORS = ['#4487f3', '#c3213c', '#509a00', '#ED6C06'];
249
+ const SOUNDING_COLORS = ['#4487f3', '#c3213c', '#509a00', '#ED6C06', '#9810FA', '#FFDF20', '#312C85'];
250
250
  const getDiagramName = (t, diagramType) => {
251
251
  switch (diagramType) {
252
252
  case 'BIJVOET':
@@ -2012,7 +2012,7 @@ const constructQueryParams = (collectionId, baseUrl, instanceId, location, datet
2012
2012
  const rawData = await getEdrCollectionForLocation(collectionId, baseUrl, instanceId, location, parameters, datetime, verticalRange);
2013
2013
  const parsedData = parseCoverageCollection(rawData, parameterMappings);
2014
2014
  return sortSoundingFeatures(parsedData);
2015
- } catch (err) {
2015
+ } catch (_err) {
2016
2016
  throw new Error('Error fetching EDR data collection by location');
2017
2017
  }
2018
2018
  }
@@ -2030,7 +2030,7 @@ const constructQueryParams = (collectionId, baseUrl, instanceId, location, datet
2030
2030
  const rawData = await getEdrCollectionForPosition(collectionId, baseUrl, instanceId, location, parameters, datetime, verticalRange);
2031
2031
  const parsedData = parseCoverageCollection(rawData, parameterMappings);
2032
2032
  return sortSoundingFeatures(parsedData);
2033
- } catch (err) {
2033
+ } catch (_err) {
2034
2034
  throw new Error('Error fetching EDR data collection by position');
2035
2035
  }
2036
2036
  }
@@ -2452,9 +2452,9 @@ const slice = createSlice({
2452
2452
  updateSelectedLocation: (draft, payloadAction) => {
2453
2453
  const {
2454
2454
  sourceId,
2455
- locationId
2455
+ location
2456
2456
  } = payloadAction.payload;
2457
- draft.selectedLocations[sourceId] = locationId;
2457
+ draft.selectedLocations[sourceId] = location;
2458
2458
  },
2459
2459
  updateSelectedTime: (draft, payloadAction) => {
2460
2460
  const {
@@ -2539,9 +2539,7 @@ const SoundingsTimeSliderConnect = () => {
2539
2539
  const {
2540
2540
  data: timesteps
2541
2541
  } = useTimesteps(sourceSelections);
2542
- const mergedTimesteps = Array.from(new Set(timesteps.flat())).sort();
2543
- // TODO: next line should work in Storybook but does not: https://gitlab.com/opengeoweb/geoweb-assets/-/issues/4025
2544
- // const mergedTimesteps = [...new Set(timestepArrays.flat())].sort();
2542
+ const mergedTimesteps = [...new Set(timesteps.flat())].sort();
2545
2543
  // Ensure that the selected time is always one of the available timesteps
2546
2544
  const validSelectedTime = roundToClosestTimestep(selectedTime, mergedTimesteps);
2547
2545
  useEffect(() => {
@@ -2821,7 +2819,12 @@ const SoundingsConnect = ({
2821
2819
  const selectedTime = useSelector(getSelectedTime);
2822
2820
  const sourceSelections = useSelector(getSelectedSources);
2823
2821
  const locationSelections = useSelector(getLocationSelections);
2824
- const collections = sourceSelections.map(({
2822
+ const selectedFeature = useSelector(state => genericSelectors.getSelectedFeature(state.syncGroups, viewId));
2823
+ const selectedMapIds = useSelector(state => genericSelectors.getLinkedMaps(state, viewId));
2824
+ const selectedMapPin = useSelector(state =>
2825
+ // mapPins are synced so we can arbitrarily choose the first linked map
2826
+ mapSelectors.getPinLocation(state, selectedMapIds[0]));
2827
+ const collections = useMemo(() => sourceSelections.map(({
2825
2828
  collectionId,
2826
2829
  url,
2827
2830
  name
@@ -2829,12 +2832,29 @@ const SoundingsConnect = ({
2829
2832
  id: collectionId,
2830
2833
  url,
2831
2834
  name: name || collectionId
2832
- }));
2835
+ })), [sourceSelections]);
2833
2836
  const {
2834
2837
  data: locationData
2835
2838
  } = useGetCollectionLocations(collections, true);
2839
+ /* Make sure the map pin is visible when linking new maps */
2836
2840
  useEffect(() => {
2837
- const simpleGeoJson = {
2841
+ const toggleMapPinVisibility = displayMapPin => {
2842
+ selectedMapIds.forEach(mapId => {
2843
+ dispatch(mapActions.toggleMapPinIsVisible({
2844
+ mapId,
2845
+ displayMapPin
2846
+ }));
2847
+ });
2848
+ };
2849
+ toggleMapPinVisibility(true);
2850
+ return () => {
2851
+ // Hide the map pin when the component is unmounted
2852
+ toggleMapPinVisibility(false);
2853
+ };
2854
+ }, [dispatch, selectedMapIds]);
2855
+ // construct and send geojson to map for drawing locations
2856
+ useEffect(() => {
2857
+ const geoJsonForMap = {
2838
2858
  type: 'FeatureCollection',
2839
2859
  features: (locationData || []).flatMap(({
2840
2860
  id: serviceId,
@@ -2842,12 +2862,14 @@ const SoundingsConnect = ({
2842
2862
  }) => features.map(feature => ({
2843
2863
  type: 'Feature',
2844
2864
  geometry: feature.geometry,
2845
- properties: {
2846
- ...feature.properties,
2847
- mapFeatureClass: MapFeatureClass.ObservationStation
2848
- },
2849
2865
  id: feature.id,
2850
- serviceId // TODO: remove in follow-up if not needed https://gitlab.com/opengeoweb/opengeoweb/-/issues/6312
2866
+ properties: {
2867
+ name: feature.properties.name,
2868
+ mapFeatureClass: MapFeatureClass.ObservationStation,
2869
+ highlightSelectedFeature: selectedFeature?.id === feature.id,
2870
+ id: feature.id,
2871
+ serviceId
2872
+ }
2851
2873
  })))
2852
2874
  };
2853
2875
  dispatch(genericActions.addSharedData({
@@ -2855,11 +2877,41 @@ const SoundingsConnect = ({
2855
2877
  data: {
2856
2878
  features: [{
2857
2879
  id: geojsonLayerId,
2858
- geoJSON: simpleGeoJson
2880
+ geoJSON: geoJsonForMap
2859
2881
  }]
2860
2882
  }
2861
2883
  }));
2862
- }, [collections, dispatch, geojsonLayerId, locationData, sourceSelections, viewId]);
2884
+ }, [dispatch, geojsonLayerId, locationData, selectedFeature?.id, viewId]);
2885
+ useEffect(() => () => {
2886
+ if (viewId) {
2887
+ dispatch(syncGroupsActions.deleteSharedData({
2888
+ panelId: viewId
2889
+ }));
2890
+ }
2891
+ }, [dispatch, viewId]);
2892
+ // Map location selection has changed. Update soundings location
2893
+ // since the location menu may be out of sync
2894
+ useEffect(() => {
2895
+ const serviceId = selectedFeature?.serviceId;
2896
+ if (serviceId) {
2897
+ dispatch(soundingsActions.updateSelectedLocation({
2898
+ sourceId: serviceId,
2899
+ location: selectedFeature?.id
2900
+ }));
2901
+ }
2902
+ }, [dispatch, selectedFeature?.id, selectedFeature?.serviceId]);
2903
+ // Map pin change on its own implies position selection
2904
+ useEffect(() => {
2905
+ // Use first source. Multiple sources will be implemented later.
2906
+ // https://gitlab.com/opengeoweb/geoweb-assets/-/issues/4254
2907
+ const source = sourceSelections[0];
2908
+ if (source?.collectionId && !selectedFeature?.id && selectedMapPin?.lon && selectedMapPin.lat) {
2909
+ dispatch(soundingsActions.updateSelectedLocation({
2910
+ sourceId: source.collectionId,
2911
+ location: [selectedMapPin.lon, selectedMapPin.lat]
2912
+ }));
2913
+ }
2914
+ }, [selectedMapPin, selectedFeature?.id, sourceSelections, dispatch]);
2863
2915
  const {
2864
2916
  error,
2865
2917
  data,
@@ -3298,6 +3350,8 @@ const LocationsMenuConnect = ({
3298
3350
  const locationSelections = useSelector(getLocationSelections);
3299
3351
  const selectedSourceIds = useSelector(getSelectedSourcesIds);
3300
3352
  const sourcesAmount = useSelector(getSelectedSourcesAmount);
3353
+ const panelId = useSelector(store => uiSelectors.getDialogMapId(store, uiTypes.DialogTypes.SoundingsLocations));
3354
+ const selectedMapIds = useSelector(state => genericSelectors.getLinkedMaps(state, panelId));
3301
3355
  const {
3302
3356
  data,
3303
3357
  isPending,
@@ -3306,16 +3360,53 @@ const LocationsMenuConnect = ({
3306
3360
  const services = useMemo(() => {
3307
3361
  return data?.filter(service => selectedSourceIds.includes(service.id)) || [];
3308
3362
  }, [data, selectedSourceIds]);
3363
+ const handleFeatureSelect = (panelId, featureId) => {
3364
+ dispatch(genericActions.addSharedData({
3365
+ panelId,
3366
+ data: {
3367
+ selectedFeatureId: featureId
3368
+ }
3369
+ }));
3370
+ };
3371
+ const setMapPinLocation = mapPinLocation => {
3372
+ selectedMapIds.forEach(mapId => {
3373
+ dispatch(mapActions.setMapPinLocation({
3374
+ mapId,
3375
+ mapPinLocation
3376
+ }));
3377
+ });
3378
+ };
3309
3379
  return jsx(LocationsMenu, {
3310
3380
  isOpen: isDialogOpen,
3311
3381
  onClose: onCloseDialog,
3312
3382
  services: services,
3313
3383
  selectedSourcesAmount: sourcesAmount,
3314
3384
  selectedLocations: locationSelections,
3315
- updateSelectedLocation: (sourceId, locationId) => void dispatch(soundingsActions.updateSelectedLocation({
3316
- sourceId,
3317
- locationId
3318
- })),
3385
+ updateSelectedLocation: (sourceId, locationId) => {
3386
+ dispatch(soundingsActions.updateSelectedLocation({
3387
+ sourceId,
3388
+ location: locationId
3389
+ }));
3390
+ // Use first source. Multiple sources will be implemented later.
3391
+ // https://gitlab.com/opengeoweb/geoweb-assets/-/issues/4254
3392
+ const isFirstSource = sourceId === selectedSourceIds[0];
3393
+ if (!isFirstSource) {
3394
+ return;
3395
+ }
3396
+ if (locationId) {
3397
+ handleFeatureSelect(panelId, locationId);
3398
+ }
3399
+ const foundFeature = services[0].features.find(feature => feature.id === locationId);
3400
+ // check type for typescript
3401
+ if (foundFeature?.geometry.type === 'Point') {
3402
+ const [lon, lat] = foundFeature.geometry.coordinates;
3403
+ setMapPinLocation({
3404
+ lon,
3405
+ lat,
3406
+ id: locationId
3407
+ });
3408
+ }
3409
+ },
3319
3410
  onMouseDown: setDialogOrder,
3320
3411
  order: dialogOrder,
3321
3412
  isLoading: isPending,
@@ -3323,9 +3414,8 @@ const LocationsMenuConnect = ({
3323
3414
  });
3324
3415
  };
3325
3416
 
3326
- const soundingsComponentType = 'Soundings';
3327
3417
  const soundingsLookUp = payload => {
3328
- if (payload.componentType === soundingsComponentType) {
3418
+ if (payload.componentType === 'Soundings') {
3329
3419
  return jsx(SoundingsConnect, {
3330
3420
  viewId: payload.id,
3331
3421
  diagramType: payload.diagramType ? payload.diagramType : 'SKEWT'
@@ -3362,4 +3452,4 @@ const createMockStore = mockState => configureStore({
3362
3452
  middleware: getDefaultMiddleware => getDefaultMiddleware().concat(storeMiddlewares)
3363
3453
  });
3364
3454
 
3365
- export { LocationsMenuConnect, SOUNDINGS_NAMESPACE, SoundingsConnect, SoundingsSourceDialogConnect, createMockStore, initSoundingsI18n, soundingDiagrams, soundingsComponentType, soundingsLookUp, soundingsReducersMap, soundingsTranslations, translateKeyOutsideComponents, useSoundingsTranslation };
3455
+ export { LocationsMenuConnect, SOUNDINGS_NAMESPACE, SoundingsConnect, SoundingsSourceDialogConnect, createMockStore, initSoundingsI18n, soundingDiagrams, soundingsLookUp, soundingsReducersMap, soundingsTranslations, translateKeyOutsideComponents, useSoundingsTranslation };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeoweb/soundings",
3
- "version": "13.2.1",
3
+ "version": "14.0.1",
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": "13.2.1",
11
+ "@opengeoweb/api": "14.0.1",
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": "13.2.1",
17
+ "@opengeoweb/shared": "14.0.1",
18
18
  "@mui/material": "^7.0.1",
19
- "@opengeoweb/store": "13.2.1",
19
+ "@opengeoweb/store": "14.0.1",
20
20
  "react-redux": "^9.2.0",
21
- "@opengeoweb/theme": "13.2.1",
21
+ "@opengeoweb/theme": "14.0.1",
22
22
  "@reduxjs/toolkit": "^2.6.1",
23
- "@opengeoweb/time-slider": "13.2.1",
23
+ "@opengeoweb/time-slider": "14.0.1",
24
24
  "react-window": "^1.8.10",
25
- "@opengeoweb/webmap": "13.2.1",
25
+ "@opengeoweb/webmap": "14.0.1",
26
26
  "lodash": "^4.17.21",
27
27
  "@types/geojson": "^7946.0.14",
28
- "@opengeoweb/webmap-react": "13.2.1"
28
+ "@opengeoweb/webmap-react": "14.0.1"
29
29
  },
30
30
  "devDependencies": {
31
31
  "eslint-plugin-storybook": "9.0.17"
@@ -1,10 +1,9 @@
1
1
  import { SoundingsCollection } from '@opengeoweb/shared';
2
2
  import { SoundingFeature } from './types';
3
- import { SourceType } from '../../store/types';
3
+ import { Coordinates, SourceType } from '../../store/types';
4
4
  import { SoundingsCollectionsWithInstances } from '../../components/Soundings/types';
5
5
  import { ServiceWithLocations } from '../../components/MenuBar/LocationsMenu';
6
6
  export declare const sortSoundingFeatures: (data: SoundingFeature[]) => SoundingFeature[];
7
- type Coordinates = [number, number];
8
7
  export declare const defaultParameterMappings: {
9
8
  temperature: string;
10
9
  windDirection: string;
@@ -42,4 +41,3 @@ export declare const useGetCollectionLocations: <SoundingsCollectionLike extends
42
41
  isPending: boolean;
43
42
  error: Error | null | undefined;
44
43
  };
45
- export {};
@@ -1,4 +1,5 @@
1
1
  import { LocationsResponseFromBE } from '../../api/edr-api/types';
2
+ import { Coordinates } from '../../store/types';
2
3
  export type ServiceWithLocations = LocationsResponseFromBE & {
3
4
  id: string;
4
5
  name: string;
@@ -6,7 +7,7 @@ export type ServiceWithLocations = LocationsResponseFromBE & {
6
7
  interface Props {
7
8
  services: ServiceWithLocations[];
8
9
  selectedSourcesAmount: number;
9
- selectedLocations: Record<string, string | undefined>;
10
+ selectedLocations: Record<string, string | Coordinates | undefined>;
10
11
  isOpen: boolean;
11
12
  onClose: () => void;
12
13
  updateSelectedLocation: (sourceId: string, locationId: string | undefined) => void;
@@ -20,18 +20,18 @@ export declare const getSourceSelections: ((state: SoundingsModuleState) => Reco
20
20
  memoize: typeof import("reselect").weakMapMemoize;
21
21
  argsMemoize: typeof import("reselect").weakMapMemoize;
22
22
  };
23
- export declare const getLocationSelections: ((state: SoundingsModuleState) => Record<string, string | undefined>) & {
23
+ export declare const getLocationSelections: ((state: SoundingsModuleState) => Record<string, string | import("./types").Coordinates | undefined>) & {
24
24
  clearCache: () => void;
25
25
  resultsCount: () => number;
26
26
  resetResultsCount: () => void;
27
27
  } & {
28
- resultFunc: (resultFuncArgs_0: SoundingsStore) => Record<string, string | undefined>;
29
- memoizedResultFunc: ((resultFuncArgs_0: SoundingsStore) => Record<string, string | undefined>) & {
28
+ resultFunc: (resultFuncArgs_0: SoundingsStore) => Record<string, string | import("./types").Coordinates | undefined>;
29
+ memoizedResultFunc: ((resultFuncArgs_0: SoundingsStore) => Record<string, string | import("./types").Coordinates | undefined>) & {
30
30
  clearCache: () => void;
31
31
  resultsCount: () => number;
32
32
  resetResultsCount: () => void;
33
33
  };
34
- lastResult: () => Record<string, string | undefined>;
34
+ lastResult: () => Record<string, string | import("./types").Coordinates | undefined>;
35
35
  dependencies: [(store: SoundingsModuleState) => SoundingsStore];
36
36
  recomputations: () => number;
37
37
  resetRecomputations: () => void;
@@ -20,16 +20,18 @@ export interface SourceSelection {
20
20
  }
21
21
  export interface UpdateLocationPayload {
22
22
  sourceId: string;
23
- locationId: string | undefined;
23
+ location: string | Coordinates | undefined;
24
24
  }
25
25
  export interface UpdateTimePayload {
26
26
  selectedTime: string;
27
27
  }
28
+ /** [lon, lat] */
29
+ export type Coordinates = [number, number];
28
30
  export interface SoundingsStore {
29
31
  /** key: sourceId */
30
32
  selectedSources: Record<string, SourceSelection>;
31
- /** key: sourceId, value: locationId */
32
- selectedLocations: Record<string, string | undefined>;
33
+ /** key: sourceId, value: locationId or position coordinates */
34
+ selectedLocations: Record<string, string | Coordinates | undefined>;
33
35
  /** ISO date, e.g., 2025-01-02T12:00:00.000Z */
34
36
  selectedTime: string;
35
37
  }
@@ -1,7 +1,7 @@
1
1
  import { SoundingDiagramType } from '../api/edr-api/types';
2
- export declare const soundingsComponentType = "Soundings";
2
+ export type SoundingsComponentType = 'Soundings';
3
3
  export interface SoundingsLookUpPayload {
4
- componentType: typeof soundingsComponentType;
4
+ componentType: SoundingsComponentType;
5
5
  id: string;
6
6
  title?: string;
7
7
  diagramType?: SoundingDiagramType;