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

#9031 GetMap format list in GetCapabilities not respected #9054

Merged
merged 9 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from 6 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
3 changes: 3 additions & 0 deletions docs/developer-guide/local-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ This is the main structure:
}
}
},
// allow to extend the default GetMap supported formats
// is an array of mime type string eg. ["image/format"]
"supportedGetMapFormats": [],
"plugins": {
// plugins to load for the mobile mode
"mobile": [...]
Expand Down
5 changes: 3 additions & 2 deletions web/client/actions/layerCapabilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

import { updateNode } from './layers';

import WMS, { formatCapabilitiesOptions } from '../api/WMS';
import WMS from '../api/WMS';
import { getLayerOptions } from '../utils/WMSUtils';
import * as WFS from '../api/WFS';
import WCS from '../api/WCS';
import {getCapabilitiesUrl} from '../utils/LayersUtils';
Expand Down Expand Up @@ -63,7 +64,7 @@ export function getLayerCapabilities(layer) {
const layerCapability = WMS.parseLayerCapabilities(capabilities, layer);

if (layerCapability) {
dispatch(updateNode(layer.id, "id", formatCapabilitiesOptions(layerCapability)));
dispatch(updateNode(layer.id, "id", { ...getLayerOptions(layerCapability), capabilitiesLoading: null }));
} else {
dispatch(updateNode(layer.id, "id", { capabilitiesLoading: null, capabilities: { error: "error getting capabilities", details: "no layer info" }, description: null }));
}
Expand Down
2 changes: 1 addition & 1 deletion web/client/api/CSW.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ const addCapabilitiesToRecords = (_dcRef, result, options) => {
isCached
? cached
: WMS.getCapabilities(parsedUrl + '?version=')
.then((caps)=> WMS.flatLayers(WMS.getCapabilityRoot(caps)?.Capability))
.then((caps)=> WMS.flatLayers(caps.Capability))
.catch(()=> []))
.then((layers) => {
if (!isCached) {
Expand Down
173 changes: 25 additions & 148 deletions web/client/api/WMS.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/**
/*
* Copyright 2016, GeoSolutions Sas.
* All rights reserved.
*
Expand All @@ -7,20 +7,19 @@
*/

import urlUtil from 'url';

import { isObject, isArray, castArray, get, uniq } from 'lodash';
import { isArray, castArray, get } from 'lodash';
import xml2js from 'xml2js';
import axios from '../libs/ajax';
import { getConfigProp } from '../utils/ConfigUtils';
import { getWMSBoundingBox } from '../utils/CoordinatesUtils';
import { getAvailableInfoFormat } from "../utils/MapInfoUtils";
import { isValidGetMapFormat, isValidGetFeatureInfoFormat } from '../utils/WMSUtils';
const capabilitiesCache = {};

export const WMS_GET_CAPABILITIES_VERSION = '1.3.0';
// The describe layer request is used to detect the OWS type, WFS or WCS type (eg: get additional information for the styling)
// The default version is 1.1.1 because GeoServer is not fully supporting the version 1.3.0
export const WMS_DESCRIBE_LAYER_VERSION = '1.1.1';

export const getCapabilityRoot = (json) => (json.WMS_Capabilities || json.WMT_MS_Capabilities || {});

export const parseUrl = (
urls,
query = {
Expand Down Expand Up @@ -76,90 +75,6 @@ export const extractCredits = attribution => {
};
};

/**
* Get unique array of supported info formats
* @return {array} info formats
*/
export const getUniqueInfoFormats = () => {
return uniq(Object.values(getAvailableInfoFormat()));
};

export const DEFAULT_GET_MAP_FORMAT_WMS = [
'image/png',
'image/png8',
'image/jpeg',
'image/vnd.jpeg-png',
'image/vnd.jpeg-png8',
'image/gif'
];

const UNSUPPORTED_GET_MAP_FORMATS_WMS = [
"application/atom xml",
"application/atom+xml",
"application/json;type=geojson",
"application/json;type=topojson",
"application/json;type=utfgrid",
"application/openlayers",
"application/openlayers2",
"application/openlayers3",
"application/pdf",
"application/rss xml",
"application/rss+xml",
"application/vnd.google-earth.kml",
"application/vnd.google-earth.kml xml",
"application/vnd.google-earth.kml+xml",
"application/vnd.google-earth.kml+xml;mode=networklink",
"application/vnd.google-earth.kmz",
"application/vnd.google-earth.kmz xml",
"application/vnd.google-earth.kmz+xml",
"application/vnd.google-earth.kmz;mode=networklink",
"application/vnd.mapbox-vector-tile",
"application/x-protobuf;type=mapbox-vector",
"atom",
"geojson",
"image/geotiff",
"image/geotiff8",
"image/svg",
"image/svg xml",
"image/svg+xml",
"image/tiff",
"image/tiff8",
"kml",
"kmz",
"openlayers",
"pbf",
"rss",
"text/html; subtype=openlayers",
"text/html; subtype=openlayers2",
"text/html; subtype=openlayers3",
"topojson",
"utfgrid"
];
/**
* Validate GetMap format from WMS capabilities
* @param {string} format GetMap format
* @return {boolean}
*/
export const isValidGetMapFormat = (format) => {
if (UNSUPPORTED_GET_MAP_FORMATS_WMS.includes(format)) {
return false;
}
if (DEFAULT_GET_MAP_FORMAT_WMS.includes(format)) {
return true;
}
// check if mime type is image
const parts = format.split('/');
return parts[0] === 'image';
};
/**
* Validate GetFeatureInfo format from WMS capabilities
* @param {string} format GetFeatureInfo format
* @return {boolean}
*/
export const isValidGetInfoFormat = (format) => {
return getUniqueInfoFormats().includes(format);
};

export const flatLayers = (root) => {
const rootLayer = root?.Layer ?? root?.layer;
const rootName = root?.Name ?? root?.name;
Expand All @@ -181,13 +96,13 @@ export const getOnlineResource = (c) => {
return c.Request && c.Request.GetMap && c.Request.GetMap.DCPType && c.Request.GetMap.DCPType.HTTP && c.Request.GetMap.DCPType.HTTP.Get && c.Request.GetMap.DCPType.HTTP.Get.OnlineResource && c.Request.GetMap.DCPType.HTTP.Get.OnlineResource.$ || undefined;
};
export const searchAndPaginate = (json = {}, startPosition, maxRecords, text) => {
allyoucanmap marked this conversation as resolved.
Show resolved Hide resolved
const root = getCapabilityRoot(json).Capability;
const service = getCapabilityRoot(json).Service;
const root = json.Capability;
const service = json.Service;
const onlineResource = getOnlineResource(root);
const SRSList = root.Layer && (root.Layer.SRS || root.Layer.CRS)?.map((crs) => crs.toUpperCase()) || [];
const credits = root.Layer && root.Layer.Attribution && extractCredits(root.Layer.Attribution);
const supportedGetMapFormats = castArray(root?.Request?.GetMap?.Format || []).filter(isValidGetMapFormat);
const supportedGetFeatureInfoFormats = castArray(root?.Request?.GetFeatureInfo?.Format || []).filter(isValidGetInfoFormat);
const getMapFormats = castArray(root?.Request?.GetMap?.Format || []);
const getFeatureInfoFormats = castArray(root?.Request?.GetFeatureInfo?.Format || []);
const layersObj = flatLayers(root);
const layers = isArray(layersObj) ? layersObj : [layersObj];
const filteredLayers = layers
Expand All @@ -198,14 +113,14 @@ export const searchAndPaginate = (json = {}, startPosition, maxRecords, text) =>
nextRecord: startPosition + Math.min(maxRecords, filteredLayers.length) + 1,
service,
layerOptions: {
version: getCapabilityRoot(json)?.$?.version || WMS_GET_CAPABILITIES_VERSION
version: json?.$?.version || WMS_GET_CAPABILITIES_VERSION
},
records: filteredLayers
.filter((layer, index) => index >= startPosition - 1 && index < startPosition - 1 + maxRecords)
.map((layer) => ({
...layer,
supportedGetMapFormats,
supportedGetFeatureInfoFormats,
getMapFormats,
getFeatureInfoFormats,
onlineResource,
SRS: SRSList,
credits: layer.Attribution ? extractCredits(layer.Attribution) : credits
Expand All @@ -225,7 +140,14 @@ export const getDimensions = (layer) => {
};
});
};

/**
* Get the WMS capabilities given a valid url endpoint
* @param {string} url WMS endpoint
* @return {object} root object of the capabilities
* - `$`: object with additional information of the capability (eg: $.version)
* - `Capability`: capability object that contains layers and requests formats
* - `Service`: service information object
*/
export const getCapabilities = (url) => {
return axios.get(parseUrl(url, {
service: "WMS",
Expand All @@ -236,52 +158,10 @@ export const getCapabilities = (url) => {
xml2js.parseString(response.data, {explicitArray: false}, (ignore, result) => {
json = result;
});
return json;
return (json.WMS_Capabilities || json.WMT_MS_Capabilities || {});
});
};

/**
* Return capabilities valid for the layer object
*/
export const formatCapabilitiesOptions = function(capabilities) {
return isObject(capabilities)
? {
capabilities,
capabilitiesLoading: null,
description: capabilities.Abstract,
boundingBox: capabilities?.EX_GeographicBoundingBox
? {
minx: capabilities.EX_GeographicBoundingBox?.westBoundLongitude,
miny: capabilities.EX_GeographicBoundingBox?.southBoundLatitude,
maxx: capabilities.EX_GeographicBoundingBox?.eastBoundLongitude,
maxy: capabilities.EX_GeographicBoundingBox?.northBoundLatitude
}
: capabilities?.LatLonBoundingBox?.$,
availableStyles: capabilities?.Style && castArray(capabilities.Style)
.map((capStyle) => ({
name: capStyle.Name,
...(capStyle.Title && { title: capStyle.Title }),
...(capStyle.Abstract && { _abstract: capStyle.Abstract }),
...(capStyle.LegendURL && {
legendURL: castArray(capStyle.LegendURL)
.map((capLegendURL) => ({
width: capLegendURL?.$?.width ? parseFloat(capLegendURL.$.width) : undefined,
height: capLegendURL?.$?.height ? parseFloat(capLegendURL.$.height) : undefined,
format: capLegendURL?.Format,
...(capLegendURL?.OnlineResource?.$?.['xlink:type'] &&
capLegendURL?.OnlineResource?.$?.['xlink:href'] && {
onlineResource: {
type: capLegendURL.OnlineResource.$['xlink:type'],
href: capLegendURL.OnlineResource.$['xlink:href']
}
})
}))
})
}))
}
: {};
};

export const describeLayer = (url, layer, options = {}) => {
return axios.get(parseUrl(url, {
service: "WMS",
Expand Down Expand Up @@ -346,7 +226,7 @@ export const textSearch = (url, startPosition, maxRecords, text) => {
return getRecords(url, startPosition, maxRecords, text);
};
export const parseLayerCapabilities = (json, layer) => {
const root = getCapabilityRoot(json).Capability;
const root = json.Capability;
const layersCapabilities = flatLayers(root);
return layersCapabilities.find((layerCapability) => {
const capabilityName = layerCapability.Name;
Expand Down Expand Up @@ -414,10 +294,10 @@ export const reset = () => {
export const getSupportedFormat = (url, includeGFIFormats = false) => {
return getCapabilities(url)
.then((response) => {
const root = getCapabilityRoot(response).Capability;
const root = response.Capability;
const imageFormats = castArray(root?.Request?.GetMap?.Format || []).filter(isValidGetMapFormat);
if (includeGFIFormats) {
const infoFormats = castArray(root?.Request?.GetFeatureInfo?.Format || []).filter(isValidGetInfoFormat);
const infoFormats = castArray(root?.Request?.GetFeatureInfo?.Format || []).filter(isValidGetFeatureInfoFormat);
return { imageFormats, infoFormats };
}
return imageFormats;
Expand All @@ -426,7 +306,6 @@ export const getSupportedFormat = (url, includeGFIFormats = false) => {
};

const Api = {
getCapabilityRoot,
flatLayers,
parseUrl,
getDimensions,
Expand All @@ -438,9 +317,7 @@ const Api = {
parseLayerCapabilities,
getBBox,
reset,
getUniqueInfoFormats,
getSupportedFormat,
formatCapabilitiesOptions
getSupportedFormat
};

export default Api;
Loading