From 6f26fa9d632003cc9fc7efd5ae78c0c88e4a0b27 Mon Sep 17 00:00:00 2001 From: Lorenzo Natali Date: Mon, 16 Jan 2017 12:53:23 +0100 Subject: [PATCH] Add search options from the catalog When adding a WMS layer from the catalog, MapStore will try to get a describeLayer for it, to find out if the layer can be searched via WFS. If possible, the search object is added to the layer. * DescribeLayers is implemented to avoid ogc-schemas lib (old functionality has been mantained for backward compatibility, as describeLayer with require-ensure pattern. * Add tests for the JavaScript WMS API. --- web/client/actions/__tests__/catalog-test.js | 28 +++++++- web/client/actions/catalog.js | 31 +++++++++ web/client/api/WMS.js | 26 +++++++ web/client/api/__tests__/WMS-test.js | 73 ++++++++++++++++++++ web/client/plugins/MetadataExplorer.jsx | 3 +- web/client/plugins/Save.jsx | 1 + web/client/plugins/SaveAs.jsx | 1 + web/client/reducers/layers.js | 2 +- web/client/reducers/query.js | 3 +- web/client/utils/LayersUtils.js | 3 + 10 files changed, 165 insertions(+), 6 deletions(-) create mode 100644 web/client/api/__tests__/WMS-test.js diff --git a/web/client/actions/__tests__/catalog-test.js b/web/client/actions/__tests__/catalog-test.js index 360225a39a..85fe92feae 100644 --- a/web/client/actions/__tests__/catalog-test.js +++ b/web/client/actions/__tests__/catalog-test.js @@ -7,7 +7,9 @@ */ const expect = require('expect'); -const {getRecords, addLayerError, ADD_LAYER_ERROR} = require('../catalog'); +const LayersUtils = require('../../utils/LayersUtils'); +const {getRecords, addLayerError, addLayer, ADD_LAYER_ERROR} = require('../catalog'); +const {CHANGE_LAYER_PROPERTIES, ADD_LAYER} = require('../layers'); describe('Test correctness of the catalog actions', () => { it('getRecords ISO Metadata Profile', (done) => { @@ -55,7 +57,29 @@ describe('Test correctness of the catalog actions', () => { } }); }); - + it('add layer and describe it', (done) => { + const verify = (action) => { + if (action.type === ADD_LAYER) { + expect(action.layer).toExist(); + const layer = action.layer; + expect(layer.id).toExist(); + expect(layer.id).toBe(LayersUtils.getLayerId(action.layer, [])); + } else if (action.type === CHANGE_LAYER_PROPERTIES) { + expect(action.layer).toExist(); + expect(action.newProperties).toExist(); + expect(action.newProperties.search).toExist(); + expect(action.newProperties.search.type ).toBe('wfs'); + expect(action.newProperties.search.url).toBe("http://some.geoserver.org:80/geoserver/wfs?"); + done(); + } + }; + const callback = addLayer({ + url: 'base/web/client/test-resources/wms/DescribeLayers.xml', + type: 'wms', + name: 'workspace:vector_layer' + }); + callback(verify, () => ({ layers: []})); + }); it('sets an error on addLayerError action', () => { const action = addLayerError('myerror'); diff --git a/web/client/actions/catalog.js b/web/client/actions/catalog.js index ce7a49045d..52f45e7ff0 100644 --- a/web/client/actions/catalog.js +++ b/web/client/actions/catalog.js @@ -11,6 +11,11 @@ var API = { wms: require('../api/WMS') }; +const {addLayer, changeLayerProperties} = require('./layers'); + +const LayersUtils = require('../utils/LayersUtils'); +const {find} = require('lodash'); + const RECORD_LIST_LOADED = 'RECORD_LIST_LOADED'; const RECORD_LIST_LOAD_ERROR = 'RECORD_LIST_LOAD_ERROR'; const CHANGE_CATALOG_FORMAT = 'CHANGE_CATALOG_FORMAT'; @@ -75,7 +80,32 @@ function textSearch(format, url, startPosition, maxRecords, text, options) { }); }; } +function addLayerAndDescribe(layer) { + return (dispatch, getState) => { + const state = getState(); + const layers = state && state.layers; + const id = LayersUtils.getLayerId(layer, layers || []); + dispatch(addLayer({...layer, id})); + if (layer.type === 'wms') { + // try to describe layer + return API.wms.describeLayers(layer.url, layer.name).then((results) => { + if (results) { + let description = find(results, (desc) => desc.name === layer.name ); + if (description && description.owsType === 'WFS') { + dispatch(changeLayerProperties(id, { + search: { + url: description.owsURL, + type: 'wfs' + } + })); + } + } + + }); + } + }; +} function addLayerError(error) { return { type: ADD_LAYER_ERROR, @@ -98,6 +128,7 @@ module.exports = { getRecords, textSearch, changeCatalogFormat, + addLayer: addLayerAndDescribe, addLayerError, catalogReset }; diff --git a/web/client/api/WMS.js b/web/client/api/WMS.js index 095fb91c93..b71a133c8f 100644 --- a/web/client/api/WMS.js +++ b/web/client/api/WMS.js @@ -111,6 +111,32 @@ const Api = { return searchAndPaginate(json, startPosition, maxRecords, text); }); }, + describeLayers: function(url, layers) { + const parsed = urlUtil.parse(url, true); + const describeLayerUrl = urlUtil.format(assign({}, parsed, { + query: assign({ + service: "WMS", + version: "1.1.1", + layers: layers, + request: "DescribeLayer" + }, parsed.query) + })); + return axios.get(parseUrl(describeLayerUrl)).then((response) => { + let decriptions; + xml2js.parseString(response.data, {explicitArray: false}, (ignore, result) => { + decriptions = result && result.WMS_DescribeLayerResponse && result.WMS_DescribeLayerResponse.LayerDescription; + }); + decriptions = Array.isArray(decriptions) ? decriptions : [decriptions]; + // make it compatible with json format of describe layer + return decriptions.map(desc => ({ + ...(desc && desc.$ || {}), + layerName: desc.$ && desc.$.name, + query: { + ...(desc && desc.query && desc.query.$ || {}) + } + })); + }); + }, textSearch: function(url, startPosition, maxRecords, text) { return Api.getRecords(url, startPosition, maxRecords, text); } diff --git a/web/client/api/__tests__/WMS-test.js b/web/client/api/__tests__/WMS-test.js new file mode 100644 index 0000000000..eceed9330e --- /dev/null +++ b/web/client/api/__tests__/WMS-test.js @@ -0,0 +1,73 @@ +/** + * Copyright 2016, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +const expect = require('expect'); +const API = require('../WMS'); + +describe('Test correctness of the WMS APIs', () => { + it('describeLayers', (done) => { + API.describeLayers('base/web/client/test-resources/wms/DescribeLayers.xml', "workspace:vector_layer").then((result) => { + try { + expect(result).toExist(); + expect(result.length).toBe(2); + expect(result[0].owsType).toBe("WFS"); + done(); + } catch(ex) { + done(ex); + } + }); + }); + it('describeLayer with OGC-SCHEMAS', (done) => { + API.describeLayer('base/web/client/test-resources/wms/DescribeLayers.xml', "workspace:vector_layer").then((result) => { + try { + expect(result).toExist(); + expect(result.owsType).toBe("WFS"); + done(); + } catch(ex) { + done(ex); + } + }); + }); + it('GetCapabilities 1.3.0', (done) => { + API.getCapabilities('base/web/client/test-resources/wms/GetCapabilities-1.3.0.xml').then((result) => { + try { + expect(result).toExist(); + expect(result.capability).toExist(); + expect(result.version).toBe("1.3.0"); + expect(result.capability.layer).toExist(); + done(); + } catch(ex) { + done(ex); + } + }); + }); + it('GetCapabilities 1.1.1', (done) => { + API.getCapabilities('base/web/client/test-resources/wms/GetCapabilities-1.1.1.xml').then((result) => { + try { + expect(result).toExist(); + expect(result.capability).toExist(); + expect(result.version).toBe("1.1.1"); + expect(result.capability.layer).toExist(); + done(); + } catch(ex) { + done(ex); + } + }); + }); + it('GetRecords', (done) => { + API.getRecords('base/web/client/test-resources/wms/GetCapabilities-1.3.0.xml', 0, 1, '').then((result) => { + try { + expect(result).toExist(); + expect(result.numberOfRecordsMatched).toBe(5); + done(); + } catch(ex) { + done(ex); + } + }); + }); +}); diff --git a/web/client/plugins/MetadataExplorer.jsx b/web/client/plugins/MetadataExplorer.jsx index 1e786725cc..92f3e11457 100644 --- a/web/client/plugins/MetadataExplorer.jsx +++ b/web/client/plugins/MetadataExplorer.jsx @@ -11,8 +11,7 @@ const {connect} = require('react-redux'); const assign = require('object-assign'); const {createSelector} = require("reselect"); const {Glyphicon, Panel} = require('react-bootstrap'); -const {textSearch, changeCatalogFormat, addLayerError, catalogReset} = require("../actions/catalog"); -const {addLayer} = require("../actions/layers"); +const {textSearch, changeCatalogFormat, addLayer, addLayerError, catalogReset} = require("../actions/catalog"); const {zoomToExtent} = require("../actions/map"); const {toggleControl} = require("../actions/controls"); const Message = require("../components/I18N/Message"); diff --git a/web/client/plugins/Save.jsx b/web/client/plugins/Save.jsx index 93186e7e7c..467fc156a2 100644 --- a/web/client/plugins/Save.jsx +++ b/web/client/plugins/Save.jsx @@ -87,6 +87,7 @@ const Save = React.createClass({ features: layer.features, format: layer.format, group: layer.group, + search: layer.search, source: layer.source, name: layer.name, opacity: layer.opacity, diff --git a/web/client/plugins/SaveAs.jsx b/web/client/plugins/SaveAs.jsx index fe84e42f58..e806900aaf 100644 --- a/web/client/plugins/SaveAs.jsx +++ b/web/client/plugins/SaveAs.jsx @@ -119,6 +119,7 @@ const SaveAs = React.createClass({ features: layer.features, format: layer.format, group: layer.group, + search: layer.search, source: layer.source, name: layer.name, opacity: layer.opacity, diff --git a/web/client/reducers/layers.js b/web/client/reducers/layers.js index 01ea66f450..a3a2c9a0f5 100644 --- a/web/client/reducers/layers.js +++ b/web/client/reducers/layers.js @@ -246,7 +246,7 @@ function layers(state = [], action) { case ADD_LAYER: { let newLayers = (state.flat || []).concat(); let newGroups = (state.groups || []).concat(); - const newLayer = (action.layer.id) ? action.layer : assign({}, action.layer, {id: action.layer.name + "__" + newLayers.length}); + const newLayer = (action.layer.id) ? action.layer : assign({}, action.layer, {id: LayersUtils.getLayerId(action.layer, newLayers)}); newLayers.push(newLayer); const groupName = newLayer.group || 'Default'; if (groupName !== "background") { diff --git a/web/client/reducers/query.js b/web/client/reducers/query.js index 18ab40f77f..98a5770d71 100644 --- a/web/client/reducers/query.js +++ b/web/client/reducers/query.js @@ -26,7 +26,8 @@ const assign = require('object-assign'); const types = { 'xsd:string': 'string', 'xsd:dateTime': 'date', - 'xsd:number': 'number' + 'xsd:number': 'number', + 'xsd:int': 'number' }; const fieldConfig = {}; const extractInfo = (featureType) => { diff --git a/web/client/utils/LayersUtils.js b/web/client/utils/LayersUtils.js index b86ae53061..be655e5ca6 100644 --- a/web/client/utils/LayersUtils.js +++ b/web/client/utils/LayersUtils.js @@ -25,6 +25,9 @@ const reorderLayers = (groups, allLayers) => { }; var LayersUtils = { + getLayerId: (layerObj, layers) => { + return layerObj && layerObj.id || (layerObj.name + "__" + layers.length); + }, getLayersByGroup: (configLayers) => { let i = 0; let mapLayers = configLayers.map((layer) => assign({}, layer, {storeIndex: i++}));