Skip to content

Commit

Permalink
#10272: Choice of OGC service type for single layer in CSW catalog (#…
Browse files Browse the repository at this point in the history
…10304) (#10334)

(cherry picked from commit d033bc7)
  • Loading branch information
dsuren1 committed May 16, 2024
1 parent fac4616 commit 9126955
Show file tree
Hide file tree
Showing 12 changed files with 459 additions and 62 deletions.
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;
};

0 comments on commit 9126955

Please sign in to comment.