• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geosolutions-it / MapStore2 / 14359153796

09 Apr 2025 02:02PM UTC coverage: 76.955% (-0.03%) from 76.985%
14359153796

Pull #10950

github

web-flow
Merge c89fb6e0a into 3ad831e94
Pull Request #10950: Fix #10947 Updated dockerfile to static files for standard templates

31008 of 48285 branches covered (64.22%)

38489 of 50015 relevant lines covered (76.95%)

35.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.76
/web/client/api/catalog/CSW.js
1
/*
2
 * Copyright 2022, GeoSolutions Sas.
3
 * All rights reserved.
4
 *
5
 * This source code is licensed under the BSD-style license found in the
6
 * LICENSE file in the root directory of this source tree.
7
 */
8

9
import { head, isString, includes, castArray, sortBy, uniq, isEmpty } from 'lodash';
10
import { getLayerFromRecord as getLayerFromWMSRecord } from './WMS';
11
import { getMessageById } from '../../utils/LocaleUtils';
12
import { transformExtentToObj} from '../../utils/CoordinatesUtils';
13
import { extractEsriReferences, extractOGCServicesReferences } from '../../utils/CatalogUtils';
14
import CSW, { getLayerReferenceFromDc } from '../CSW';
15
import {
16
    validate as commonValidate,
17
    testService as commonTestService,
18
    preprocess as commonPreprocess
19
} from './common';
20
import { THREE_D_TILES } from '../ThreeDTiles';
21
import { MODEL } from '../Model';
22
const getBaseCatalogUrl = (url) => {
1✔
23
    return url && url.replace(/\/csw$/, "/");
7✔
24
};
25

26
// Try to find thumb from dc documents works both with geonode pycsw and geosolutions-csw
27
const getThumb = (dc) => head(castArray(dc.references).filter((ref) => {
11✔
28
    return ref.scheme === "WWW:LINK-1.0-http--image-thumbnail" || ref.scheme === "thumbnail" || (ref.scheme === "WWW:DOWNLOAD-1.0-http--download" && (ref.value || "").indexOf(`${dc.identifier || ""}-thumb`) !== -1) || (ref.scheme === "WWW:DOWNLOAD-REST_MAP" && (ref.value || "").indexOf(`${dc.identifier || ""}-thumb`) !== -1);
11!
29
}));
30

31
const getMetaDataDownloadFormat = (protocol) => {
1✔
32
    const formatsMap = [
8✔
33
        {
34
            protocol: 'https://registry.geodati.gov.it/metadata-codelist/ProtocolValue/www-download',
35
            displayValue: 'Download'
36
        },
37
        {
38
            protocol: 'http://www.opengis.net/def/serviceType/ogc/wms',
39
            displayValue: 'WMS'
40
        },
41
        {
42
            protocol: 'http://www.opengis.net/def/serviceType/ogc/wfs',
43
            displayValue: 'WFS'
44
        }
45
    ];
46
    return head(formatsMap.filter(item => item.protocol === protocol))?.displayValue ?? "Link";
24✔
47
};
48

49
const getURILinks = (metadata, locales, uriItem) => {
1✔
50
    let itemName = uriItem.name;
26✔
51
    if (itemName === undefined) {
26✔
52
        itemName = metadata.title ? metadata.title.join(' ') : getMessageById(locales, "catalog.notAvailable");
8✔
53
        const downloadFormat = getMetaDataDownloadFormat(uriItem.protocol, uriItem.value);
8✔
54
        itemName = `${downloadFormat ? `${itemName} - ${downloadFormat}` : itemName}`;
8!
55
    }
56
    return (`<li><a target="_blank" href="${uriItem.value}">${itemName}</a></li>`);
26✔
57
};
58

59
const esriToLayer = (record, { layerBaseConfig = {} } = {}) => {
1!
60
    if (!record || !record.references) {
1!
61
        // we don't have a valid record so no buttons to add
62
        return null;
×
63
    }
64
    // let's extract the references we need
65
    const {esri} = extractEsriReferences(record);
1✔
66
    return {
1✔
67
        type: esri.type,
68
        url: esri.url,
69
        visibility: true,
70
        dimensions: record.dimensions || [],
2✔
71
        name: esri.params && esri.params.name,
2✔
72
        bbox: {
73
            crs: record.boundingBox.crs,
74
            bounds: {
75
                minx: record.boundingBox.extent[0],
76
                miny: record.boundingBox.extent[1],
77
                maxx: record.boundingBox.extent[2],
78
                maxy: record.boundingBox.extent[3]
79
            }
80
        },
81
        ...layerBaseConfig
82
    };
83

84
};
85

86
function getThumbnailFromDc(dc, options) {
87
    const URI = dc?.URI && castArray(dc.URI);
32✔
88
    let thumbURL;
89
    if (URI) {
32✔
90
        const thumb = head(URI.filter(uri => uri.name === 'thumbnail')) || head(URI.filter(uri => !uri.name && uri.protocol?.indexOf('image/') > -1));
23✔
91
        thumbURL = thumb ? thumb.value : null;
16✔
92
    }
93
    if (!thumbURL && dc && dc.references) {
32✔
94
        const thumb = getThumb(dc);
11✔
95
        if (thumb) {
11✔
96
            thumbURL = thumb.value;
3✔
97
        }
98
    }
99
    if (thumbURL) {
32✔
100
        const absolute = (thumbURL.indexOf("http") === 0);
12✔
101
        if (!absolute) {
12✔
102
            thumbURL = (getBaseCatalogUrl(options && options.url) || "") + thumbURL;
7✔
103
        }
104
    }
105
    return thumbURL;
32✔
106
}
107

108
/**
109
 * Extract bounding box object from the record
110
 * @param {Object} record from OGC service
111
 */
112
function getBoundingBox(record) {
113
    if (isEmpty(record.boundingBox?.crs) || isEmpty(record.boundingBox?.extent)) {
3!
114
        return null;
×
115
    }
116
    return {
3✔
117
        crs: record.boundingBox?.crs,
118
        bounds: transformExtentToObj(record.boundingBox?.extent)
119
    };
120
}
121
function getCatalogRecord3DTiles(record, metadata) {
122
    const dc = record.dc;
2✔
123
    // dc?.URI can be an array
124
    let dcURIs = castArray(dc?.URI);
2✔
125
    const firstValidURI = dcURIs?.find(uri => uri?.value?.endsWith('.json'));
2✔
126
    const url = firstValidURI?.value || "";
2!
127
    return {
2✔
128
        serviceType: '3dtiles',
129
        isValid: true,
130
        description: dc && isString(dc.abstract) && dc.abstract || '',
6!
131
        title: dc && isString(dc.title) && dc.title || '',
6!
132
        identifier: dc && isString(dc.identifier) && dc.identifier || '',
6!
133
        url,
134
        thumbnail: null,
135
        bbox: getBoundingBox(record),
136
        format: dc && dc.format || "",
4!
137
        references: [],
138
        catalogType: 'csw',
139
        metadata
140
    };
141
}
142

143
const recordToLayer = (record, options) => {
1✔
144
    switch (record.layerType) {
4!
145
    case 'wms':
146
        return getLayerFromWMSRecord(record, options);
3✔
147
    case 'esri':
148
        return esriToLayer(record, options);
1✔
149
    default:
150
        return null;
×
151
    }
152
};
153
const ADDITIONAL_OGC_SERVICES = ['wfs']; // Add services when support is provided
1✔
154
const getAdditionalOGCService = (record, references, parsedReferences = {}) => {
1!
155
    const hasAdditionalService = ADDITIONAL_OGC_SERVICES.some(serviceType => !isEmpty(parsedReferences[serviceType]));
32✔
156
    if (hasAdditionalService) {
32✔
157
        return {
1✔
158
            additionalOGCServices: {
159
                ...ADDITIONAL_OGC_SERVICES
160
                    .map(serviceType => {
161
                        const ogcReferences = parsedReferences[serviceType] ?? {};
1!
162
                        const {url, params: {name} = {}} = ogcReferences;
1!
163
                        return {[serviceType]: {
1✔
164
                            url, name, references, ogcReferences, fetchCapabilities: true,
165
                            boundingBox: getBoundingBox(record)
166
                        }};
167
                    })
168
                    .flat()
169
                    .reduce((a, c) => ({...c, ...a}), {})
1✔
170
            }
171
        };
172
    }
173
    return null;
31✔
174
};
175

176
export const preprocess = commonPreprocess;
1✔
177
export const validate = commonValidate;
1✔
178
export const testService = commonTestService({ parseUrl: CSW.parseUrl });
1✔
179
export const textSearch = CSW.textSearch;
1✔
180
export const getCatalogRecords = (records, options, locales) => {
1✔
181
    let result = records;
33✔
182
    // let searchOptions = catalog.searchOptions;
183
    if (result && result.records) {
33!
184
        return result.records.map((record) => {
33✔
185
            const dc = record.dc;
34✔
186
            let references = [];
34✔
187

188
            // extract get capabilities references and add them to the final references
189
            if (dc && dc.references) {
34✔
190
                // make sure we have an array of references
191
                let rawReferences = Array.isArray(dc.references) ? dc.references : [dc.references];
17!
192
                rawReferences.filter((reference) => {
17✔
193
                    // filter all references that correspond to a get capabilities reference
194
                    return reference.scheme.indexOf("http-get-capabilities") > -1;
23✔
195
                }).forEach((reference) => {
196
                    // a get capabilities reference should be absolute and filter by the layer name
197
                    let referenceUrl = reference.value.indexOf("http") === 0 ? reference.value
1!
198
                        : (options && options.catalogURL || "") + "/" + reference.value;
×
199
                    // add the references to the final list
200
                    references.push({
1✔
201
                        type: reference.scheme,
202
                        url: referenceUrl
203
                    });
204
                });
205
            }
206

207
            const layerReferences = ['wms', ...ADDITIONAL_OGC_SERVICES].map(serviceType => {
34✔
208
                return getLayerReferenceFromDc(dc, {...options, type: serviceType});
68✔
209
            }).filter(ref => ref);
68✔
210
            if (!isEmpty(layerReferences)) {
34✔
211
                references = references.concat(layerReferences);
11✔
212
            }
213

214
            // create the references array (now only wms is supported)
215
            let metadata = {boundingBox: record.boundingBox && record.boundingBox.extent && castArray(record.boundingBox.extent.join(","))};
34✔
216
            if (dc) {
34✔
217
                // parsing all it comes from the csw service
218
                metadata = {...metadata, ...sortBy(Object.keys(dc)).reduce((p, c) => ({...p, [c]: uniq(castArray(dc[c]))}), {})};
95✔
219
            }
220
            // parsing URI
221
            if (dc && dc.URI && castArray(dc.URI) && castArray(dc.URI).length) {
34✔
222
                metadata = {...metadata, uri: ["<ul>" + castArray(dc.URI).map(getURILinks.bind(this, metadata, locales)).join("") + "</ul>"]};
17✔
223
            }
224
            if (dc && dc.subject && castArray(dc.subject) && castArray(dc.subject).length) {
34✔
225
                metadata = {...metadata, subject: ["<ul>" + castArray(dc.subject).map(s => `<li>${s}</li>`).join("") + "</ul>"]};
18✔
226
            }
227
            if (references && castArray(references).length ) {
34✔
228
                metadata = {...metadata, references: ["<ul>" + castArray(references).map(ref => `<li><a target="_blank" href="${ref.url}">${ref.params && ref.params.name || ref.url}</a></li>`).join("") + "</ul>"]
14✔
229
                };
230
            } else {
231
                // in order to use a default value
232
                // we need to not push undefined/empty matadata
233
                delete metadata.references;
22✔
234
            }
235

236
            if (dc && dc.temporal) {
34✔
237
                let elements = isString(dc.temporal) ? dc.temporal.split("; ") : [];
7!
238
                if (elements.length) {
7!
239
                    // finding scheme or using default
240
                    let scheme = elements.filter(e => e.indexOf("scheme=") !== -1).map(e => {
16✔
241
                        const equalIndex = e.indexOf("=");
2✔
242
                        const value = e.substr(equalIndex + 1, e.length - 1);
2✔
243
                        return value;
2✔
244
                    });
245
                    scheme = scheme.length ? scheme[0] : "W3C-DTF";
7✔
246
                    let temporal = elements
7✔
247
                        .filter(e => e.indexOf("start=") !== -1 || e.indexOf("end=") !== -1)
16✔
248
                        .map(e => {
249
                            const equalIndex = e.indexOf("=");
13✔
250
                            const prop = e.substr(0, equalIndex);
13✔
251
                            const value = e.substr(equalIndex + 1, e.length - 1);
13✔
252
                            const isOnlyDateFormat = e.length - equalIndex - 1 <= 10;
13✔
253
                            if (includes(["start", "end"], prop) && scheme === "W3C-DTF" && !isOnlyDateFormat) {
13✔
254
                                return getMessageById(locales, `catalog.${prop}`) + new Date(value).toLocaleString();
6✔
255
                            }
256
                            if (includes(["start", "end"], prop)) {
7!
257
                                return getMessageById(locales, `catalog.${prop}`) + value;
7✔
258
                            }
259
                            return "";
×
260
                        });
261
                    metadata = {...metadata, temporal: ["<ul>" + temporal.map(date => `<li>${date}</li>`).join("") + "</ul>"]};
13✔
262
                }
263
            }
264

265
            const parsedReferences = {
34✔
266
                ...extractOGCServicesReferences({ references }),
267
                ...extractEsriReferences({ references })
268
            };
269

270
            let catRecord;
271
            if (dc && dc.format === THREE_D_TILES) {
34✔
272
                catRecord = getCatalogRecord3DTiles(record, metadata);
2✔
273
            } else if (dc && dc.format === MODEL) {
32!
274
                // todo: handle get catalog record for ifc
275
            } else {
276
                const layerType = Object.keys(parsedReferences).filter(key => !ADDITIONAL_OGC_SERVICES.includes(key)).find(key => parsedReferences[key]);
160✔
277
                const ogcReferences = layerType && layerType !== 'esri'
32✔
278
                    ? parsedReferences[layerType]
279
                    : undefined;
280
                catRecord = {
32✔
281
                    serviceType: 'csw',
282
                    layerType,
283
                    isValid: !!layerType,
284
                    boundingBox: record.boundingBox,
285
                    description: dc && isString(dc.abstract) && dc.abstract || '',
95!
286
                    layerOptions: options && options.layerOptions || {},
95✔
287
                    identifier: dc && isString(dc.identifier) && dc.identifier || '',
95✔
288
                    references: references,
289
                    thumbnail: getThumbnailFromDc(dc, options),
290
                    title: dc && isString(dc.title) && dc.title || '',
95✔
291
                    tags: dc && dc.tags || '',
95✔
292
                    metadata,
293
                    capabilities: record.capabilities,
294
                    ogcReferences,
295
                    ...getAdditionalOGCService(record, references, parsedReferences)
296
                };
297
            }
298
            return catRecord;
34✔
299
        });
300
    }
301
    return null;
×
302
};
303

304
export const getLayerFromRecord = (record, options, asPromise) => {
1✔
305
    const layer = recordToLayer(record, options);
4✔
306
    return asPromise ? Promise.resolve(layer) : layer;
4✔
307
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc