diff --git a/packages/react-components/src/components/cesium-map/helpers/utils.ts b/packages/react-components/src/components/cesium-map/helpers/utils.ts index 727cf84c..df95ae2a 100644 --- a/packages/react-components/src/components/cesium-map/helpers/utils.ts +++ b/packages/react-components/src/components/cesium-map/helpers/utils.ts @@ -175,6 +175,25 @@ export const rectangle2bbox = (bbox: Rectangle): BBox => [ CesiumMath.toDegrees(bbox.north), ]; +export const rectangle2Feature = (rect: Rectangle): Feature => { + return { + type: 'Feature', + properties: {}, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [CesiumMath.toDegrees(rect.west), CesiumMath.toDegrees(rect.north)], + [CesiumMath.toDegrees(rect.east), CesiumMath.toDegrees(rect.north)], + [CesiumMath.toDegrees(rect.east), CesiumMath.toDegrees(rect.south)], + [CesiumMath.toDegrees(rect.west), CesiumMath.toDegrees(rect.south)], + [CesiumMath.toDegrees(rect.west), CesiumMath.toDegrees(rect.north)], // close the ring + ], + ], + }, + }; +}; + export const customComputeViewRectangle = (mapViewer: CesiumViewer) => { const scene = mapViewer.scene; const camera = mapViewer.camera; @@ -330,7 +349,7 @@ export const computeLimitedViewRectangle = (mapViewer: CesiumViewer, maxDistance export const createRectangleAround = ( centerCartographic: { longitude: number; latitude: number }, widthMeters: number, - heightMeters: number, + heightMeters: number ): Polygon => { const ellipsoid = Ellipsoid.WGS84; const lat = centerCartographic.latitude; @@ -361,7 +380,14 @@ export const createRectangleAround = ( }; }; -export const defaultVisualizationHandler = (viewer: CesiumViewer, dataSource: GeoJsonDataSource, processEntityIds: string[], color: string, extent?: BBox, labeling?: ICesiumWFSLayerLabelingOptions): void => { +export const defaultVisualizationHandler = ( + viewer: CesiumViewer, + dataSource: GeoJsonDataSource, + processEntityIds: string[], + color: string, + extent?: BBox, + labeling?: ICesiumWFSLayerLabelingOptions +): void => { const is2D = viewer.scene.mode === SceneMode.SCENE2D; const getGeoJsonFromEntity = (entity: Entity): Polygon | undefined => { @@ -413,11 +439,7 @@ export const defaultVisualizationHandler = (viewer: CesiumViewer, dataSource: Ge return { widthMeters, heightMeters }; }; - const createRectangleAround = ( - centerCartographic: { longitude: number; latitude: number }, - widthMeters: number, - heightMeters: number - ): Polygon => { + const createRectangleAround = (centerCartographic: { longitude: number; latitude: number }, widthMeters: number, heightMeters: number): Polygon => { const ellipsoid = Ellipsoid.WGS84; const lat = centerCartographic.latitude; const lon = centerCartographic.longitude; @@ -557,13 +579,13 @@ export const defaultVisualizationHandler = (viewer: CesiumViewer, dataSource: Ge .then((dataSource) => { dataSource?.entities.values.forEach((entity: Entity) => { entity.billboard = new BillboardGraphics({ - image: entity.properties?.label.getValue(JulianDate.now()).dataURL, - heightReference: HeightReference.NONE, // Ensures it's not clamped and floats above - scale: 1.0, - disableDepthTestDistance: Number.POSITIVE_INFINITY, + image: entity.properties?.label.getValue(JulianDate.now()).dataURL, + heightReference: HeightReference.NONE, // Ensures it's not clamped and floats above + scale: 1.0, + disableDepthTestDistance: Number.POSITIVE_INFINITY, + }); }); }); - }); } }; @@ -571,7 +593,7 @@ const DEFAULT_RECTANGLE_FACTOR = 0.2; export const applyFactor = (rect: CesiumRectangle, factor = DEFAULT_RECTANGLE_FACTOR): CesiumRectangle => { if (rect.width === 0) { - rect.east = rect.east + 0.0001 * factor; + rect.east = rect.east + 0.0001 * factor; rect.west = rect.west - 0.0001 * factor; rect.south = rect.south - 0.0001 * factor; rect.north = rect.north + 0.0001 * factor; @@ -584,4 +606,4 @@ export const applyFactor = (rect: CesiumRectangle, factor = DEFAULT_RECTANGLE_FA rect.north = rect.north + rect.height * factor; return rect; -} \ No newline at end of file +}; diff --git a/packages/react-components/src/components/cesium-map/layers/wfs.layer.tsx b/packages/react-components/src/components/cesium-map/layers/wfs.layer.tsx index a9c9edb8..01210caf 100644 --- a/packages/react-components/src/components/cesium-map/layers/wfs.layer.tsx +++ b/packages/react-components/src/components/cesium-map/layers/wfs.layer.tsx @@ -18,7 +18,7 @@ import { get } from 'lodash'; import pMap from 'p-map'; import { v4 as uuidv4 } from 'uuid'; import booleanValid from '@turf/boolean-valid'; -import { distance, center, rectangle2bbox, computeLimitedViewRectangle, defaultVisualizationHandler } from '../helpers/utils'; +import { distance, center, rectangle2bbox, computeLimitedViewRectangle, defaultVisualizationHandler, rectangle2Feature } from '../helpers/utils'; import { CesiumViewer, useCesiumMap, useCesiumMapViewstate } from '../map'; export interface ICesiumWFSLayerLabelTextField { @@ -27,6 +27,7 @@ export interface ICesiumWFSLayerLabelTextField { format?: string; predicate?: (value: any) => any; } + export interface ICesiumWFSLayerLabelingOptions { dataSourcePrefix: string; text: { @@ -41,9 +42,38 @@ export interface ICesiumWFSLayerLabelingOptions { lineWidth: number; } +export interface IGqlServiceConfig { + query: string; + variablesDescriptor: { + featureProp: string; + startIndexProp: string; + countProp: string; + typeNameProp: string; + }; + variables: { + data: Record; + }; +} + +export interface IApiServiceConfig { + method?: 'GET' | 'POST'; + headers?: Record; +} + +export type AlternativePayload = + | { + type: 'gql'; + gql: IGqlServiceConfig; + } + | { + type: 'api'; + api: IApiServiceConfig; + }; + export interface ICesiumWFSLayerOptions { url: string; featureType: string; + alternativePayload?: AlternativePayload; style: { color: string; hover: string; @@ -72,7 +102,7 @@ interface IFetchMetadata { export const CesiumWFSLayer: React.FC = (props) => { const { options, meta, visualizationHandler, withGeometryValidation = false } = props; - const { url, featureType, style, pageSize, zoomLevel, maxCacheSize, keyField, labeling } = options; + const { url, featureType, alternativePayload, style, pageSize, zoomLevel, maxCacheSize, keyField, labeling } = options; const { color, hover } = style; const mapViewer = useCesiumMap(); const mapViewState = useCesiumMapViewstate(); @@ -269,11 +299,7 @@ export const CesiumWFSLayer: React.FC = (props) => { updateMetadata(0, 0); }; - const fetchWfsData = async (wfsDataUrl: string, method: string = 'GET', body?: string): Promise => { - const options: RequestInit = { method }; - if (body !== undefined) { - options.body = body; - } + const fetchWfsData = async (wfsDataUrl: string, options: RequestInit = { method: 'GET' }): Promise => { const response = await fetch(wfsDataUrl, options); if (response.status === 200) { return await response.json(); @@ -534,15 +560,38 @@ export const CesiumWFSLayer: React.FC = (props) => { const position: Feature = center(bbox); try { - const urlSeparator = url.includes('?') ? '&' : '?'; - let wfsDataUrl = `${url}${urlSeparator}service=WFS&version=2.0.0&request=GetFeature&typeNames=${featureType}&outputFormat=application/json&bbox=${extent.join( - ',' - )},EPSG:4326&startIndex=${offset}&count=${pageSize}`; - if (keyField) { - wfsDataUrl += `&sortBy=${keyField}%20ASC`; + let wfsResponse = null; + if (alternativePayload) { + if (alternativePayload.type === 'gql') { + const { variablesDescriptor, ...payload } = alternativePayload.gql; + payload.variables.data[variablesDescriptor.featureProp] = rectangle2Feature(bbox); + payload.variables.data[variablesDescriptor.startIndexProp] = offset; + payload.variables.data[variablesDescriptor.typeNameProp] = featureType; + payload.variables.data[variablesDescriptor.countProp] = pageSize; + + wfsResponse = await fetchWfsData(url, { + method: 'POST', + body: JSON.stringify(payload), + headers: { + 'Content-Type': 'application/json', + }, + }); + const queryName = payload.query.split('(')[0].replace('query ', ''); //strip queryName + await handleWfsResponse((wfsResponse as any)?.data[queryName], extent, offset, position); + } else { + throw 'API as alternative service still not supported'; + } + } else { + const urlSeparator = url.includes('?') ? '&' : '?'; + let wfsDataUrl = `${url}${urlSeparator}service=WFS&version=2.0.0&request=GetFeature&typeNames=${featureType}&outputFormat=application/json&bbox=${extent.join( + ',' + )},EPSG:4326&startIndex=${offset}&count=${pageSize}`; + if (keyField) { + wfsDataUrl += `&sortBy=${keyField}%20ASC`; + } + wfsResponse = await fetchWfsData(wfsDataUrl); + await handleWfsResponse(wfsResponse, extent, offset, position); } - const wfsResponse = await fetchWfsData(wfsDataUrl); - await handleWfsResponse(wfsResponse, extent, offset, position); } catch (error) { console.error('Error fetching WFS data:', error); updateMetadata(-1, -1);