@opengeoweb/soundings 12.7.0 → 12.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.esm.js CHANGED
@@ -1670,20 +1670,22 @@ const getCollectionLocations = (collectionId, baseUrl) => fetch(`${baseUrl}/coll
1670
1670
  const getCollectionInstances = (collectionId, baseUrl) => fetch(`${baseUrl}/collections/${collectionId}/instances`, {
1671
1671
  headers: getHeaders()
1672
1672
  }).then(handleResponse);
1673
- const getEdrCollectionForLocation = (collectionId, baseUrl, instanceId, locationId, parameters, datetime) => {
1673
+ const getEdrCollectionForLocation = (collectionId, baseUrl, instanceId, locationId, parameters, datetime, verticalRange) => {
1674
1674
  const url = new URL(`${baseUrl}/collections/${collectionId}${instanceId ? `/instances/${instanceId}` : ''}/locations/${locationId}`);
1675
1675
  url.searchParams.append('parameter-name', parameters.join(','));
1676
1676
  url.searchParams.append('datetime', datetime);
1677
+ url.searchParams.append('z', verticalRange);
1677
1678
  url.searchParams.append('f', 'CoverageJSON');
1678
1679
  return fetch(url.toString(), {
1679
1680
  headers: getHeaders()
1680
1681
  }).then(handleResponse);
1681
1682
  };
1682
- const getEdrCollectionForPosition = (collectionId, baseUrl, instanceId, coordinates, parameters, datetime) => {
1683
+ const getEdrCollectionForPosition = (collectionId, baseUrl, instanceId, coordinates, parameters, datetime, verticalRange) => {
1683
1684
  const url = new URL(`${baseUrl}/collections/${collectionId}${instanceId ? `/instances/${instanceId}` : ''}/position`);
1684
1685
  url.searchParams.append('coords', `POINT(${coordinates[0]} ${coordinates[1]})`);
1685
1686
  url.searchParams.append('parameter-name', parameters.join(','));
1686
1687
  url.searchParams.append('datetime', datetime);
1688
+ url.searchParams.append('z', verticalRange);
1687
1689
  url.searchParams.append('f', 'CoverageJSON');
1688
1690
  return fetch(url.toString(), {
1689
1691
  headers: getHeaders()
@@ -1893,27 +1895,29 @@ const getTimestepsFromInstance = instance => {
1893
1895
  if (!timeExtentValues) {
1894
1896
  return [];
1895
1897
  }
1896
- try {
1897
- // parse the time extent, if needed
1898
- if (timeExtentValues.length === 1) {
1899
- const timeExtent = timeExtentValues[0];
1900
- // duration with repeats
1901
- if (timeExtent.match(/^R\d+\/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z\/\S+$/)) {
1902
- return parseDurationWithRepeats(timeExtent);
1898
+ const allParsedTimesteps = timeExtentValues.reduce((parsedSteps, currentValue) => {
1899
+ try {
1900
+ // Try to parse EDR time extent value in different possible formats
1901
+ // R<repeats>/<start>/P<period>
1902
+ if (currentValue.match(/^R\d+\/.*\/P.*$/)) {
1903
+ return [...parsedSteps, ...parseDurationWithRepeats(currentValue)];
1903
1904
  }
1904
- // format without repeats
1905
- const parseObject = new ParseISOTimeRangeDuration(timeExtent);
1906
- const timeValues = range(parseObject.getTimeSteps()).map(timestep => parseObject.getDateAtTimeStep(timestep).toISOString());
1907
- return timeValues;
1905
+ // <start>/<end>[/P<period>]
1906
+ if (currentValue.match(/.*\/.*P?.*/)) {
1907
+ const parseObject = new ParseISOTimeRangeDuration(currentValue);
1908
+ const timeValues = range(parseObject.getTimeSteps()).map(timestep => parseObject.getDateAtTimeStep(timestep).toISOString());
1909
+ return [...parsedSteps, ...timeValues];
1910
+ }
1911
+ // individual datetime string
1912
+ const isoFormattedValue = new Date(currentValue).toISOString();
1913
+ return [...parsedSteps, isoFormattedValue];
1914
+ } catch (error) {
1915
+ console.warn(error);
1916
+ // skip unsupported format
1917
+ return [...parsedSteps];
1908
1918
  }
1909
- // If more than one value, assume time extent is given as individual timesteps.
1910
- // Pass through toISOString to ensure consistent timestamp format.
1911
- const isoFormattedValues = timeExtentValues.map(value => new Date(value).toISOString());
1912
- return isoFormattedValues;
1913
- } catch (error) {
1914
- console.warn(error);
1915
- }
1916
- return [];
1919
+ }, []);
1920
+ return allParsedTimesteps;
1917
1921
  };
1918
1922
 
1919
1923
  /* *
@@ -1943,19 +1947,19 @@ const sortSoundingFeatures = data => {
1943
1947
  return featureB.properties.pressure - featureA.properties.pressure;
1944
1948
  });
1945
1949
  };
1946
- const constructQueryParams = (collectionId, baseUrl, instanceId, location, datetime, parameterMappings) => {
1950
+ const constructQueryParams = (collectionId, baseUrl, instanceId, location, datetime, parameterMappings, verticalRange) => {
1947
1951
  const parameters = getParameterMappings(parameterMappings);
1948
1952
  if (typeof location === 'string') {
1949
1953
  // LOCATION call
1950
1954
  return {
1951
- queryKey: ['edr-soundings-for-location', collectionId, instanceId || '', location, parameters.join(), datetime],
1955
+ queryKey: ['edr-soundings-for-location', collectionId, instanceId || '', location, parameters.join(), datetime, verticalRange],
1952
1956
  queryFn: async () => {
1953
1957
  // No collection, location or time selected, don't do EDR call but return empty array
1954
1958
  if (collectionId === undefined || location === '' || datetime === '') {
1955
1959
  return [];
1956
1960
  }
1957
1961
  try {
1958
- const rawData = await getEdrCollectionForLocation(collectionId, baseUrl, instanceId, location, parameters, datetime);
1962
+ const rawData = await getEdrCollectionForLocation(collectionId, baseUrl, instanceId, location, parameters, datetime, verticalRange);
1959
1963
  const parsedData = parseCoverageCollection(rawData, parameterMappings);
1960
1964
  return sortSoundingFeatures(parsedData);
1961
1965
  } catch (err) {
@@ -1966,14 +1970,14 @@ const constructQueryParams = (collectionId, baseUrl, instanceId, location, datet
1966
1970
  }
1967
1971
  // POSITION call
1968
1972
  return {
1969
- queryKey: ['edr-soundings-for-position', collectionId, location.join(), parameters.join(), datetime],
1973
+ queryKey: ['edr-soundings-for-position', collectionId, location.join(), parameters.join(), datetime, verticalRange],
1970
1974
  queryFn: async () => {
1971
1975
  // No collection, position or time selected, don't do EDR call but return empty array
1972
1976
  if (collectionId === undefined || location === undefined || datetime === '') {
1973
1977
  return [];
1974
1978
  }
1975
1979
  try {
1976
- const rawData = await getEdrCollectionForPosition(collectionId, baseUrl, instanceId, location, parameters, datetime);
1980
+ const rawData = await getEdrCollectionForPosition(collectionId, baseUrl, instanceId, location, parameters, datetime, verticalRange);
1977
1981
  const parsedData = parseCoverageCollection(rawData, parameterMappings);
1978
1982
  return sortSoundingFeatures(parsedData);
1979
1983
  } catch (err) {
@@ -2008,13 +2012,14 @@ const useMultipleSources = (sources, locations, datetime) => {
2008
2012
  collectionId,
2009
2013
  url,
2010
2014
  instanceId,
2011
- parameterMappings
2015
+ parameterMappings,
2016
+ verticalRange
2012
2017
  }, index) => {
2013
2018
  // If acceptance time logic is needed, it could come here.
2014
2019
  const dateToUse = datetime &&
2015
2020
  // Only use selected time if exact match if found
2016
2021
  timesteps[index].includes(new Date(datetime).toISOString()) ? datetime : '';
2017
- const queryParams = constructQueryParams(collectionId, url, instanceId, locations[collectionId] || '', dateToUse || '', parameterMappings || defaultParameterMappings);
2022
+ const queryParams = constructQueryParams(collectionId, url, instanceId, locations[collectionId] || '', dateToUse || '', parameterMappings || defaultParameterMappings, verticalRange || '');
2018
2023
  return queryParams;
2019
2024
  });
2020
2025
  }, [datetime, locations, sources, timesteps]);
@@ -2367,6 +2372,7 @@ const getSelectedSources = createSelector(getSoundingsStore, store => {
2367
2372
  collectionId: key,
2368
2373
  url: value.url,
2369
2374
  instanceId: value.instanceId,
2375
+ verticalRange: value.verticalRange,
2370
2376
  parameterMappings: value.parameterMappings
2371
2377
  });
2372
2378
  }
@@ -2643,6 +2649,26 @@ const SoundingsConnect = ({
2643
2649
  });
2644
2650
  };
2645
2651
 
2652
+ const extractVerticalRange = (collection, selectedInstanceId) => {
2653
+ const instance = collection.instances.find(instance => instance.id === selectedInstanceId);
2654
+ const interval = instance?.extent?.vertical?.interval;
2655
+ if (interval) {
2656
+ // Fallback for FMI server while it sends a string array instead of string[][] as specified in documentation
2657
+ if (typeof interval[0] === 'string') {
2658
+ const verticalDims = interval.toSorted((a, b) => a.localeCompare(b, undefined, {
2659
+ numeric: true
2660
+ })) ?? [];
2661
+ return verticalDims.join('/');
2662
+ }
2663
+ const firstEl = interval[0][0];
2664
+ const lastEl = interval[interval.length - 1][1];
2665
+ const verticalDims = [firstEl, lastEl].toSorted((a, b) => a.localeCompare(b, undefined, {
2666
+ numeric: true
2667
+ })) ?? [];
2668
+ return verticalDims.join('/');
2669
+ }
2670
+ return '';
2671
+ };
2646
2672
  const SoundingsSourceMenu = ({
2647
2673
  soundingsCollections,
2648
2674
  isOpen = false,
@@ -2705,11 +2731,15 @@ const SoundingsSourceMenu = ({
2705
2731
  },
2706
2732
  children: jsx(Checkbox, {
2707
2733
  checked: !!isChecked,
2708
- onChange: event => updateCollection(collection.id, {
2709
- url,
2710
- isChecked: event.target.checked,
2711
- instanceId: selectedInstanceId
2712
- }),
2734
+ onChange: event => {
2735
+ const verticalRange = selectedInstanceId !== undefined ? extractVerticalRange(collection, selectedInstanceId) : '';
2736
+ updateCollection(collection.id, {
2737
+ url,
2738
+ isChecked: event.target.checked,
2739
+ instanceId: selectedInstanceId,
2740
+ verticalRange
2741
+ });
2742
+ },
2713
2743
  disableRipple: true
2714
2744
  })
2715
2745
  }), jsxs(StyledSelect, {
@@ -2719,10 +2749,15 @@ const SoundingsSourceMenu = ({
2719
2749
  },
2720
2750
  skipLocalStyling: false,
2721
2751
  value: selectedInstanceId || t('noInstancesAvailable'),
2722
- onChange: event => updateCollection(collection.id, {
2723
- url,
2724
- instanceId: event.target.value
2725
- }),
2752
+ onChange: event => {
2753
+ const instanceId = event.target.value;
2754
+ const verticalRange = extractVerticalRange(collection, instanceId);
2755
+ updateCollection(collection.id, {
2756
+ url,
2757
+ instanceId,
2758
+ verticalRange
2759
+ });
2760
+ },
2726
2761
  renderValue: value => jsx(Box, {
2727
2762
  sx: {
2728
2763
  textOverflow: 'ellipsis',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeoweb/soundings",
3
- "version": "12.7.0",
3
+ "version": "12.8.0",
4
4
  "description": "GeoWeb soundings library for the opengeoweb project",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -8,25 +8,26 @@
8
8
  "url": "git@gitlab.com:opengeoweb/opengeoweb.git"
9
9
  },
10
10
  "dependencies": {
11
- "@opengeoweb/api": "12.7.0",
11
+ "@opengeoweb/api": "12.8.0",
12
12
  "@tanstack/react-query": "^5.69.2",
13
13
  "react": "^18.3.1",
14
14
  "d3": "^7.9.0",
15
15
  "react-i18next": "^15.1.1",
16
16
  "i18next": "^25.0.1",
17
- "@opengeoweb/shared": "12.7.0",
17
+ "@opengeoweb/shared": "12.8.0",
18
18
  "@mui/material": "^7.0.1",
19
- "@opengeoweb/store": "12.7.0",
19
+ "@opengeoweb/store": "12.8.0",
20
20
  "react-redux": "^9.2.0",
21
- "@opengeoweb/theme": "12.7.0",
21
+ "@opengeoweb/theme": "12.8.0",
22
22
  "@reduxjs/toolkit": "^2.6.1",
23
- "@opengeoweb/timeslider": "12.7.0",
23
+ "@opengeoweb/timeslider": "12.8.0",
24
24
  "react-window": "^1.8.10",
25
- "@opengeoweb/webmap": "12.7.0",
26
- "lodash": "^4.17.21"
25
+ "@opengeoweb/webmap": "12.8.0",
26
+ "lodash": "^4.17.21",
27
+ "@types/geojson": "^7946.0.14"
27
28
  },
28
29
  "module": "./index.esm.js",
29
30
  "type": "module",
30
31
  "main": "./index.esm.js",
31
- "types": "./index.esm.d.ts"
32
+ "types": "./index.d.ts"
32
33
  }
@@ -2,5 +2,5 @@ import { CoverageCollection } from '@opengeoweb/shared';
2
2
  import { LocationsResponseFromBE, SoundingsInstances } from './types';
3
3
  export declare const getCollectionLocations: (collectionId: string, baseUrl: string) => Promise<LocationsResponseFromBE>;
4
4
  export declare const getCollectionInstances: (collectionId: string, baseUrl: string) => Promise<SoundingsInstances>;
5
- export declare const getEdrCollectionForLocation: (collectionId: string, baseUrl: string, instanceId: string | undefined, locationId: string, parameters: string[], datetime: string) => Promise<CoverageCollection>;
6
- export declare const getEdrCollectionForPosition: (collectionId: string, baseUrl: string, instanceId: string | undefined, coordinates: [number, number], parameters: string[], datetime: string) => Promise<CoverageCollection>;
5
+ export declare const getEdrCollectionForLocation: (collectionId: string, baseUrl: string, instanceId: string | undefined, locationId: string, parameters: string[], datetime: string, verticalRange: string) => Promise<CoverageCollection>;
6
+ export declare const getEdrCollectionForPosition: (collectionId: string, baseUrl: string, instanceId: string | undefined, coordinates: [number, number], parameters: string[], datetime: string, verticalRange: string) => Promise<CoverageCollection>;
@@ -3,6 +3,7 @@ import { SoundingFeature } from './types';
3
3
  import { SourceType } from '../../store/types';
4
4
  import { SoundingsCollectionsWithInstances } from '../../components/Soundings/types';
5
5
  import { ServiceWithLocations } from '../../components/MenuBar/LocationsMenu';
6
+ export declare const sortSoundingFeatures: (data: SoundingFeature[]) => SoundingFeature[];
6
7
  type Coordinates = [number, number];
7
8
  export declare const defaultParameterMappings: {
8
9
  temperature: string;
@@ -2,6 +2,7 @@ export interface SourceType {
2
2
  collectionId: string;
3
3
  url: string;
4
4
  instanceId?: string;
5
+ verticalRange?: string;
5
6
  parameterMappings?: Record<string, string>;
6
7
  }
7
8
  export interface UpdateSourcePayload {
@@ -12,6 +13,7 @@ export interface SourceSelection {
12
13
  url: string;
13
14
  instanceId?: string;
14
15
  isChecked?: boolean;
16
+ verticalRange?: string;
15
17
  parameterMappings?: Record<string, string>;
16
18
  }
17
19
  export interface UpdateLocationPayload {
File without changes