From 357e2e7b3345e39380d0da362b2c37f7c34b2605 Mon Sep 17 00:00:00 2001 From: Lorenzo Natali Date: Mon, 16 Jan 2017 14:26:57 +0100 Subject: [PATCH] Add search options from the catalog (#1104) (#1385) * 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 +- .../test-resources/wms/DescribeLayers.xml | 10 + .../wms/GetCapabilities-1.1.1.xml | 297 ++++++++++++++++++ .../wms/GetCapabilities-1.3.0.xml | 234 ++++++++++++++ web/client/utils/LayersUtils.js | 3 + 13 files changed, 706 insertions(+), 6 deletions(-) create mode 100644 web/client/api/__tests__/WMS-test.js create mode 100644 web/client/test-resources/wms/DescribeLayers.xml create mode 100644 web/client/test-resources/wms/GetCapabilities-1.1.1.xml create mode 100644 web/client/test-resources/wms/GetCapabilities-1.3.0.xml 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/test-resources/wms/DescribeLayers.xml b/web/client/test-resources/wms/DescribeLayers.xml new file mode 100644 index 0000000000..e072d7a9f1 --- /dev/null +++ b/web/client/test-resources/wms/DescribeLayers.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/web/client/test-resources/wms/GetCapabilities-1.1.1.xml b/web/client/test-resources/wms/GetCapabilities-1.1.1.xml new file mode 100644 index 0000000000..2076e92218 --- /dev/null +++ b/web/client/test-resources/wms/GetCapabilities-1.1.1.xml @@ -0,0 +1,297 @@ + + + + + OGC:WMS + GeoServer Web Map Service + A compliant implementation of WMS plus most of the SLD extension (dynamic styling). Can also generate PDF, SVG, KML, GeoRSS + + WFS + WMS + GEOSERVER + + + + + Claudius Ptolomaeus + The ancient geographes INC + + Chief geographer + + Work +
+ Alexandria + + + Egypt + + + + claudius.ptolomaeus@gmail.com + + NONE + NONE + + + + + application/vnd.ogc.wms_xml + + + + + + + + + + + + + image/png + application/atom xml + application/atom+xml + application/json;type=utfgrid + application/openlayers + 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/x-gpkg + application/x-sqlite3 + atom + geopackage + geopkg + gpkg + image/geotiff + image/geotiff8 + image/gif + image/gif;subtype=animated + image/jpeg + image/png8 + image/png; mode=8bit + image/svg + image/svg xml + image/svg+xml + image/tiff + image/tiff8 + image/vnd.jpeg-png + kml + kmz + mbtiles + openlayers + rss + text/html; subtype=openlayers + utfgrid + + + + + + + + + + text/plain + application/vnd.ogc.gml + text/xml + application/vnd.ogc.gml/3.1.1 + text/xml; subtype=gml/3.1.1 + text/html + application/json + + + + + + + + + + + + + application/vnd.ogc.wms_xml + + + + + + + + + + image/png + image/jpeg + image/gif + + + + + + + + + + application/vnd.ogc.sld+xml + + + + + + + + + + + application/vnd.ogc.se_xml + application/vnd.ogc.se_inimage + application/vnd.ogc.se_blank + application/json + + + + GeoServer Web Map Service + A compliant implementation of WMS plus most of the SLD extension (dynamic styling). Can also generate PDF, SVG, KML, GeoRSS + + EPSG:4326 + EPSG:3857 + + + ny-parent-group + Layer-Group type layer: ny-parent-group + EPSG:4326 + + + + spearfish + spearfish + Layer-Group type layer: spearfish + EPSG:26713 + + + + + tiger-ny + tiger-ny + Layer-Group type layer: tiger-ny + EPSG:4326 + + + + + + tasmania + tasmania + Layer-Group type layer: tasmania + EPSG:4326 + + + + + nurc:Arc_Sample + A sample ArcGrid file + + + WCS + arcGridSample + arcGridSample_Coverage + + EPSG:4326 + + + + + + + sde:GRAY_HR_SR_OB_DR + GRAY_HR_SR_OB_DR + Gray Earth with Shaded Relief, Hypsography, Ocean Bottom, and Drainages + + WCS + GeoTIFF + GRAY_HR_SR_OB_DR + + EPSG:4326 + + + + + + sde:HYP_HR_SR_OB_DR + HYP_HR_SR_OB_DR + Cross Blended Hypso with Relief, Water, Drains, and Ocean Bottom + + WCS + GeoTIFF + HYP_HR_SR_OB_DR + + EPSG:4326 + + + + + + nurc:Img_Sample + North America sample imagery + + + WCS + worldImageSample + worldImageSample_Coverage + + EPSG:4326 + + + + + + + + diff --git a/web/client/test-resources/wms/GetCapabilities-1.3.0.xml b/web/client/test-resources/wms/GetCapabilities-1.3.0.xml new file mode 100644 index 0000000000..7dde561bcd --- /dev/null +++ b/web/client/test-resources/wms/GetCapabilities-1.3.0.xml @@ -0,0 +1,234 @@ + + + + WMS + GeoServer Web Map Service + A compliant implementation of WMS plus most of the SLD extension (dynamic styling). Can also generate PDF, SVG, KML, GeoRSS + + WFS + WMS + GEOSERVER + + + + + Claudius Ptolomaeus + The ancient geographes INC + + Chief geographer + + Work +
+ Alexandria + + + Egypt + + + + claudius.ptolomaeus@gmail.com + + NONE + NONE + + + + + text/xml + + + + + + + + + + + + + image/png + application/atom+xml + application/json;type=utfgrid + application/pdf + application/rss+xml + application/vnd.google-earth.kml+xml + application/vnd.google-earth.kml+xml;mode=networklink + application/vnd.google-earth.kmz + application/x-gpkg + application/x-sqlite3 + image/geotiff + image/geotiff8 + image/gif + image/jpeg + image/png; mode=8bit + image/svg+xml + image/tiff + image/tiff8 + image/vnd.jpeg-png + text/html; subtype=openlayers + + + + + + + + + + text/plain + application/vnd.ogc.gml + text/xml + application/vnd.ogc.gml/3.1.1 + text/xml; subtype=gml/3.1.1 + text/html + application/json + + + + + + + + + + + XML + INIMAGE + BLANK + JSON + + + GeoServer Web Map Service + A compliant implementation of WMS plus most of the SLD extension (dynamic styling). Can also generate PDF, SVG, KML, GeoRSS + + EPSG:3857 + EPSG:4326 + CRS:84 + + -180.0 + 180.0 + -90.0 + 90.0 + + + + ny-parent-group + Layer-Group type layer: ny-parent-group + EPSG:4326 + + -180.0 + 180.0 + -90.0 + 90.0 + + + + spearfish + spearfish + Layer-Group type layer: spearfish + EPSG:26713 + + -103.87791475407893 + -103.62278893469492 + 44.37246687108142 + 44.50235105543566 + + + + + tiger-ny + tiger-ny + Layer-Group type layer: tiger-ny + EPSG:4326 + + -74.047185 + -73.907005 + 40.679648 + 40.882078 + + + + + + tasmania + tasmania + Layer-Group type layer: tasmania + EPSG:4326 + + 143.83482400000003 + 148.47914100000003 + -43.648056 + -39.573891 + + + + + nurc:Arc_Sample + A sample ArcGrid file + + + WCS + arcGridSample + arcGridSample_Coverage + + EPSG:4326 + CRS:84 + + -180.0 + 180.0 + -90.0 + 90.0 + + + + + + + + sde:GRAY_HR_SR_OB_DR + GRAY_HR_SR_OB_DR + Gray Earth with Shaded Relief, Hypsography, Ocean Bottom, and Drainages + + WCS + GeoTIFF + GRAY_HR_SR_OB_DR + + EPSG:4326 + CRS:84 + + -180.0 + 180.00000000007202 + -90.000000000036 + 90.00000000000001 + + + + + + + + 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++}));