Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport 2024.01.xx] #10272: Choice of OGC service type for single layer in CSW catalog (#10304) #10334

Merged
merged 1 commit into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 58 additions & 12 deletions web/client/api/CSW.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,28 @@ const extractWMSParamsFromURL = wms => {
return false;
};

// Extract the relevant information from the wfs URL for (RNDT / INSPIRE)
const extractWFSParamsFromURL = wfs => {
const lowerCaseParams = new Map(Array.from(new URLSearchParams(wfs.value)).map(([key, value]) => [key.toLowerCase(), value]));
const layerName = lowerCaseParams.get('typename');
if (layerName) {
return {
...wfs,
protocol: 'OGC:WFS',
name: layerName,
value: `${wfs.value.match(/[^\?]+[\?]+/g)}service=WFS`
};
}
return false;
};

const toReference = (layerType, data, options) => {
if (!data.name) {
return null;
}
switch (layerType) {
case 'wms':
case 'wfs':
const urlValue = !(data.value.indexOf("http") === 0)
? (options && options.catalogURL || "") + "/" + data.value
: data.value;
Expand All @@ -142,39 +158,69 @@ const toReference = (layerType, data, options) => {
};

const REGEX_WMS_EXPLICIT = [/^OGC:WMS-(.*)-http-get-map/g, /^OGC:WMS/g];
const REGEX_WFS_EXPLICIT = [/^OGC:WFS-(.*)-http-get-(capabilities|feature)/g, /^OGC:WFS/g];
const REGEX_WMS_EXTRACT = /serviceType\/ogc\/wms/g;
const REGEX_WFS_EXTRACT = /serviceType\/ogc\/wfs/g;
const REGEX_WMS_ALL = REGEX_WMS_EXPLICIT.concat(REGEX_WMS_EXTRACT);

export const getLayerReferenceFromDc = (dc, options, checkEsri = true) => {
const URI = dc?.URI && castArray(dc.URI);
const isWMS = isNil(options?.type) || options?.type === "wms";
const isWFS = options?.type === "wfs";
// look in URI objects for wms and thumbnail
if (URI) {
const wms = head(URI.map(uri => {
if (uri.protocol) {
if (REGEX_WMS_EXPLICIT.some(regex => uri.protocol.match(regex))) {
if (isWMS) {
const wms = head(URI.map(uri => {
if (uri.protocol) {
if (REGEX_WMS_EXPLICIT.some(regex => uri.protocol.match(regex))) {
/** wms protocol params are explicitly defined as attributes (INSPIRE)*/
return uri;
}
if (uri.protocol.match(REGEX_WMS_EXTRACT)) {
return uri;
}
if (uri.protocol.match(REGEX_WMS_EXTRACT)) {
/** wms protocol params must be extracted from the element text (RNDT / INSPIRE) */
return extractWMSParamsFromURL(uri);
return extractWMSParamsFromURL(uri);
}
}
return false;
}).filter(item => item));
if (wms) {
return toReference('wms', wms, options);
}
}
if (isWFS) {
const wfs = head(URI.map(uri => {
if (uri.protocol) {
if (REGEX_WFS_EXPLICIT.some(regex => uri.protocol.match(regex))) {
/** wfs protocol params are explicitly defined as attributes (INSPIRE)*/
return uri;
}
if (uri.protocol.match(REGEX_WFS_EXTRACT)) {
/** wfs protocol params must be extracted from the element text (RNDT / INSPIRE) */
return extractWFSParamsFromURL(uri);
}
}
return false;
}).filter(item => item));
if (wfs) {
return toReference('wfs', wfs, options);
}
return false;
}).filter(item => item));
if (wms) {
return toReference('wms', wms, options);
}
}
// look in references objects
if (dc?.references?.length) {
const refs = castArray(dc.references);
const wms = head(refs.filter((ref) => { return ref.scheme && REGEX_WMS_EXPLICIT.some(regex => ref.scheme.match(regex)); }));
if (wms) {
const wfs = head(refs.filter((ref) => { return ref.scheme && REGEX_WFS_EXPLICIT.some(regex => ref.scheme.match(regex)); }));
if (isWMS && wms) {
let urlObj = urlUtil.parse(getDefaultUrl(wms.value), true);
let layerName = urlObj.query && urlObj.query.layers || dc.alternative;
return toReference('wms', { ...wms, value: urlUtil.format(urlObj), name: layerName }, options);
}
if (isWFS && wfs) {
let urlObj = urlUtil.parse(getDefaultUrl(wfs.value), true);
let layerName = urlObj.query && urlObj.query.layers || dc.alternative;
return toReference('wfs', { ...wfs, value: urlUtil.format(urlObj), name: layerName }, options);
}
if (checkEsri) {
// checks for esri arcgis in geonode csw
const esri = head(refs.filter((ref) => {
Expand Down
37 changes: 37 additions & 0 deletions web/client/api/__tests__/CSW-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,34 @@ describe("getLayerReferenceFromDc", () => {
expect(layerRef.type).toBe('OGC:WMS');
expect(layerRef.url).toBe('catalog_url/wmsurl?SERVICE=WMS&VERSION=1.3.0');
});
it("test layer reference with dc.references of scheme OGC:WFS", () => {
const dc = {references: [{value: "http://wmsurl", scheme: 'OGC:WMS'}, {value: "http://wfsurl", scheme: 'OGC:WFS'}], alternative: "some_layer"};
const layerRef = getLayerReferenceFromDc(dc, {type: "wfs"});
expect(layerRef.params.name).toBe('some_layer');
expect(layerRef.type).toBe('OGC:WFS');
expect(layerRef.url).toBe('http://wfsurl/');
});
it("test layer reference with dc.references of scheme OGC:WMS-http-get-capabilities", () => {
const dc = {references: [{value: "http://wmsurl", scheme: 'OGC:WMS-http-get-map'}, {value: "http://wfsurl", scheme: 'OGC:WFS-http-get-capabilities'}], alternative: "some_layer"};
const layerRef = getLayerReferenceFromDc(dc, {type: "wfs"});
expect(layerRef.params.name).toBe('some_layer');
expect(layerRef.type).toBe('OGC:WFS-http-get-capabilities');
expect(layerRef.url).toBe('http://wfsurl/');
});
it("test layer reference with dc.references of scheme OGC:WMS-http-get-feature", () => {
const dc = {references: [{value: "http://wmsurl", scheme: 'OGC:WMS-http-get-map'}, {value: "http://wfsurl", scheme: 'OGC:WFS-http-get-feature'}], alternative: "some_layer"};
const layerRef = getLayerReferenceFromDc(dc, {type: "wfs"});
expect(layerRef.params.name).toBe('some_layer');
expect(layerRef.type).toBe('OGC:WFS-http-get-feature');
expect(layerRef.url).toBe('http://wfsurl/');
});
it("test layer reference with dc.URI of scheme serviceType/ogc/wfs and options", () => {
const dc = {URI: [{value: "wfsurl?service=wfs&typename=some_layer&version=1.3.0", protocol: 'serviceType/ogc/wfs'}, {value: "wmsurl", protocol: 'OGC:WMS'}]};
const layerRef = getLayerReferenceFromDc(dc, {type: "wfs", catalogURL: "catalog_url"});
expect(layerRef.params.name).toBe('some_layer');
expect(layerRef.type).toBe('OGC:WFS');
expect(layerRef.url).toBe('catalog_url/wfsurl?service=WFS');
});
it("test layer reference with multiple dc.URI of scheme OGC:WMS", () => {
const dc = {URI: [{value: "http://wmsurl", protocol: 'OGC:WMS', name: 'some_layer'}, {value: "wfsurl", protocol: 'OGC:WFS'}]};
const layerRef = getLayerReferenceFromDc(dc);
Expand Down Expand Up @@ -480,6 +508,15 @@ describe("getLayerReferenceFromDc", () => {

expect(getLayerReferenceFromDc(dc).url).toBe(_url[0]);
});
it('getLayerReferenceFromDc for service type WFS', () => {
const _url = [
'http://gs-stable.geosolutionsgroup.com:443/geoserver1',
'http://gs-stable.geosolutionsgroup.com:443/geoserver2',
'http://gs-stable.geosolutionsgroup.com:443/geoserver3'
];
const dc = {references: [{value: 'wmsurl', scheme: 'OGC:WMS'}, {value: _url, scheme: 'OGC:WFS'}], alternative: "some_layer"};
expect(getLayerReferenceFromDc(dc, {type: "wfs"}).url).toBe(_url[0]);
});
});


63 changes: 49 additions & 14 deletions web/client/api/catalog/CSW.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/

import { head, isString, includes, castArray, sortBy, uniq } from 'lodash';
import { head, isString, includes, castArray, sortBy, uniq, isEmpty } from 'lodash';
import { getLayerFromRecord as getLayerFromWMSRecord } from './WMS';
import { getMessageById } from '../../utils/LocaleUtils';
import { transformExtentToObj} from '../../utils/CoordinatesUtils';
Expand Down Expand Up @@ -104,12 +104,22 @@ function getThumbnailFromDc(dc, options) {
}
return thumbURL;
}

/**
* Extract bounding box object from the record
* @param {Object} record from OGC service
*/
function getBoundingBox(record) {
if (isEmpty(record.boundingBox?.crs) || isEmpty(record.boundingBox?.extent)) {
return null;
}
return {
crs: record.boundingBox?.crs,
bounds: transformExtentToObj(record.boundingBox?.extent)
};
}
function getCatalogRecord3DTiles(record, metadata) {
const dc = record.dc;
let bbox = {
crs: record.boundingBox.crs,
bounds: transformExtentToObj(record.boundingBox.extent)
};
return {
serviceType: '3dtiles',
isValid: true,
Expand All @@ -118,7 +128,7 @@ function getCatalogRecord3DTiles(record, metadata) {
identifier: dc && isString(dc.identifier) && dc.identifier || '',
url: dc?.URI?.value || "",
thumbnail: null,
bbox,
bbox: getBoundingBox(record),
format: dc && dc.format || "",
references: [],
catalogType: 'csw',
Expand All @@ -136,6 +146,28 @@ const recordToLayer = (record, options) => {
return null;
}
};
const ADDITIONAL_OGC_SERVICES = ['wfs']; // Add services when support is provided
const getAdditionalOGCService = (record, references, parsedReferences = {}) => {
const hasAdditionalService = ADDITIONAL_OGC_SERVICES.some(serviceType => !isEmpty(parsedReferences[serviceType]));
if (hasAdditionalService) {
return {
additionalOGCServices: {
...ADDITIONAL_OGC_SERVICES
.map(serviceType => {
const ogcReferences = parsedReferences[serviceType] ?? {};
const {url, params: {name} = {}} = ogcReferences;
return {[serviceType]: {
url, name, references, ogcReferences, fetchCapabilities: true,
boundingBox: getBoundingBox(record)
}};
})
.flat()
.reduce((a, c) => ({...c, ...a}), {})
}
};
}
return null;
};

export const preprocess = commonPreprocess;
export const validate = commonValidate;
Expand Down Expand Up @@ -168,9 +200,11 @@ export const getCatalogRecords = (records, options, locales) => {
});
}

const layerReference = getLayerReferenceFromDc(dc, options);
if (layerReference) {
references.push(layerReference);
const layerReferences = ['wms', ...ADDITIONAL_OGC_SERVICES].map(serviceType => {
return getLayerReferenceFromDc(dc, {...options, type: serviceType});
}).filter(ref => ref);
if (!isEmpty(layerReferences)) {
references = references.concat(layerReferences);
}

// create the references array (now only wms is supported)
Expand Down Expand Up @@ -229,16 +263,16 @@ export const getCatalogRecords = (records, options, locales) => {
...extractEsriReferences({ references })
};

const layerType = Object.keys(parsedReferences).find(key => parsedReferences[key]);
const ogcReferences = layerType && layerType !== 'esri'
? parsedReferences[layerType]
: undefined;
let catRecord;
if (dc && dc.format === THREE_D_TILES) {
catRecord = getCatalogRecord3DTiles(record, metadata);
} else if (dc && dc.format === MODEL) {
// todo: handle get catalog record for ifc
} else {
const layerType = Object.keys(parsedReferences).filter(key => !ADDITIONAL_OGC_SERVICES.includes(key)).find(key => parsedReferences[key]);
const ogcReferences = layerType && layerType !== 'esri'
? parsedReferences[layerType]
: undefined;
catRecord = {
serviceType: 'csw',
layerType,
Expand All @@ -253,7 +287,8 @@ export const getCatalogRecords = (records, options, locales) => {
tags: dc && dc.tags || '',
metadata,
capabilities: record.capabilities,
ogcReferences
ogcReferences,
...getAdditionalOGCService(record, references, parsedReferences)
};
}
return catRecord;
Expand Down
20 changes: 19 additions & 1 deletion web/client/api/catalog/WFS.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
testService as commonTestService,
preprocess as commonPreprocess
} from './common';
import { get, castArray } from 'lodash';
import { get, castArray, isEmpty } from 'lodash';

const capabilitiesCache = {};

Expand Down Expand Up @@ -142,7 +142,25 @@ export const getCatalogRecords = ({records} = {}) => {
return null;
};

/**
* Formulate WFS layer data from record
* and fetch capabilities if needed to add capibilities specific data
* @param {Object} record data obtained from catalog service
* @param {Object} options props specific to wfs
* @returns {Promise} promise that resolves to formulated layer data
*/
const getLayerData = (record, options) => {
const layer = recordToLayer(record, options);
return getRecords(record.url, 1, 1, record.name).then((result)=> {
const [newRecord] = result?.records ?? [];
return isEmpty(newRecord) ? layer : recordToLayer(newRecord, options);
}).catch(() => layer);
};

export const getLayerFromRecord = (record, options, asPromise) => {
if (options.fetchCapabilities && asPromise) {
return getLayerData(record, options);
}
const layer = recordToLayer(record, options);
return asPromise ? Promise.resolve(layer) : layer;
};
Loading
Loading