diff --git a/src/util/ArcGISRest.js b/src/util/ArcGISRest.js index 9c82bf308..a03229011 100644 --- a/src/util/ArcGISRest.js +++ b/src/util/ArcGISRest.js @@ -62,22 +62,27 @@ Ext.define('BasiGX.util.ArcGISRest', { }, /** - * Creates the URL for a FeatureServer request. + * Creates the URL for a FeatureServer/MapServer/GPServer. * * @param {string} serviceUrl The URL of the service. - * @param {string} serverName The name of the FeatureServer. + * @param {string} serverName The name of the Server. * @param {string} format The output format. - * @return {string} The URL to the FeatureServer. + * @param {string} serverType FeatureServer/MapServer/GPServer. + * @return {string} The URL to the FeatureServer/MapServer/GPServer. */ - createFeatureServerUrl: function(serviceUrl, serverName, format) { + createServerUrl: function(serviceUrl, serverName, format, serverType) { if (!BasiGX.util.ArcGISRest.isArcGISRestUrl(serviceUrl)) { return; } - var urlObj = new URL(serviceUrl); + + // Working with the root Url ensures that this code works + // with both root and folder level services + var rootUrl = BasiGX.util.ArcGISRest.getArcGISRestRootUrl( + serviceUrl + ); + var urlObj = new URL(rootUrl); var parts = urlObj.pathname.split('/'); - parts.push(serverName); - parts.push('FeatureServer'); - var path = parts.join('/'); + var path = parts.concat([serverName, serverType]).join('/'); var url = urlObj.origin + path; if (format) { @@ -85,7 +90,40 @@ Ext.define('BasiGX.util.ArcGISRest', { } return url; + }, + + /** + * Creates the URL for a FeatureServer request. + * + * @param {string} serviceUrl The URL of the service. + * @param {string} serverName The name of the FeatureServer. + * @param {string} format The output format. + * @return {string} The URL to the FeatureServer. + */ + createFeatureServerUrl: function(serviceUrl, serverName, format) { + return BasiGX.util.ArcGISRest.createServerUrl( + serviceUrl, + serverName, + format, + 'FeatureServer' + ); + }, + /** + * Creates the URL for a MapServer request. + * + * @param {string} serviceUrl The URL of the service. + * @param {string} serverName The name of the MapServer. + * @param {string} format The output format. + * @return {string} The URL to the MapServer. + */ + createMapServerUrl: function(serviceUrl, serverName, format) { + return BasiGX.util.ArcGISRest.createServerUrl( + serviceUrl, + serverName, + format, + 'MapServer' + ); }, /** @@ -139,8 +177,10 @@ Ext.define('BasiGX.util.ArcGISRest', { * @param {number} layerConfig.layer.id The id of a FeatureServer layer. * @param {string} layerConfig.layer.name The name of a FeatureServer * layer. - * @param {boolean} useDefaultHeader Whether to use the default - * Xhr header. + * @param {Ext.data.TreeStore} layerConfig.subLayerStore The tree + * store containing the sublayers. + * @param {boolean} useDefaultHeader Whether to use the default Xhr + * header. * @return {Ext.Promise} A promise containing the olLayer. */ createOlLayerFromArcGISRest: function(layerConfig, useDefaultHeader) { @@ -152,6 +192,19 @@ Ext.define('BasiGX.util.ArcGISRest', { Ext.log.warn('Provided URL is not a valid ArcGISRest URL'); return Ext.Promise.reject(); } + + // collect all sublayer indexes that the user has marked as visible + var visibleLayerIndexes = []; + layerConfig.subLayerStore.each(function(sublayer){ + var visibility = sublayer.get('visibility'); + var layerId = sublayer.get('layerId'); + var layerIdValid = Ext.isNumeric(layerId) && layerId >= 0; + + if (visibility && layerIdValid){ + visibleLayerIndexes.push(layerId); + } + }); + var serviceUrl = [rootUrl, service.name, service.type].join('/'); var onReject = function() { return Ext.Promise.reject(); @@ -172,7 +225,12 @@ Ext.define('BasiGX.util.ArcGISRest', { source = new ol.source.TileArcGISRest({ url: serviceUrl, projection: 'EPSG:' + - serviceInfo.spatialReference.wkid + serviceInfo.spatialReference.wkid, + params: { + 'LAYERS': 'show:' + + visibleLayerIndexes.join(',') + } + }); layer = new ol.layer.Tile({ source: source, diff --git a/src/view/form/AddArcGISRest.js b/src/view/form/AddArcGISRest.js index e45c94d4e..f2fde697c 100644 --- a/src/view/form/AddArcGISRest.js +++ b/src/view/form/AddArcGISRest.js @@ -29,11 +29,16 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { 'Ext.form.FieldSet', 'Ext.form.field.ComboBox', 'Ext.form.CheckboxGroup', + 'Ext.tree.Panel', 'Ext.Promise', + 'Ext.data.TreeStore', + 'Ext.data.Model', + 'Ext.data.proxy.Ajax', 'BasiGX.util.Map', 'BasiGX.util.MsgBox', 'BasiGX.util.Url', - 'BasiGX.util.ArcGISRest' + 'BasiGX.util.ArcGISRest', + 'BasiGX.view.tree.ArcGISRestServiceTree' ], viewModel: { @@ -67,7 +72,9 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { msgInvalidUrl: 'Die angegebene URL ist keine valide ArcGISRest URL', documentation: '

ArcGISRest Layer hinzufügen

• In ' + 'diesem Dialog können Sie mit Hilfe einer ArcGISRest-URL ' + - 'einen beliebigen Kartendienst der Karte hinzufügen.' + 'einen beliebigen Kartendienst der Karte hinzufügen.', + serviceLayersVisibility: {}, + availableLayersFieldSetMaxHeight: null } }, @@ -105,7 +112,7 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { /** * Default url for the textfield or combobox. */ - defaultUrl: 'https://gis.epa.ie/arcgis/rest/services', + defaultUrl: 'https://gis.epa.ie/arcgis/rest/services/Copernicus', /** * Whether we will send the `X-Requested-With` header when fetching the @@ -115,7 +122,12 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { * * @type {Boolean} */ - useDefaultXhrHeader: false + useDefaultXhrHeader: false, + + /** + * Allow parent configure available layers fieldset maxHeight + */ + availableLayersFieldSetMaxHeight: null }, /** @@ -127,18 +139,18 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { defaultButton: 'requestLayersBtn', + layout: 'vbox', + items: [ { xtype: 'fieldset', - layout: 'anchor', - defaults: { - anchor: '100%' - }, + width: '100%', bind: { title: '{queryParamsFieldSetTitle}' }, items: [{ xtype: 'textfield', + width: '100%', bind: { fieldLabel: '{arcGISUrlTextFieldLabel}' }, @@ -183,19 +195,63 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { } } } + }, { + xtype: 'container', + layout: { + type: 'hbox', + pack: 'end' + }, + items: [{ + xtype: 'button', + name: 'resetFormBtn', + bind: { + text: '{resetBtnText}' + }, + handler: function(btn) { + var view = btn.up('basigx-form-addarcgisrest'); + view.getForm().reset(); + view.removeAddLayersComponents(); + view.resetState(); + var defaultValue = view.defaultUrl; + var combo = view.down('combobox[name=urlCombo]'); + combo.setValue(defaultValue); + var textfield = view.down('textfield[name=url]'); + textfield.setValue(defaultValue); + var fs = view.down('[name=fs-available-layers]'); + fs.setHidden(true); + } + }, { + xtype: 'button', + bind: { + text: '{requestLayersBtnText}' + }, + margin: '0 0 0 5', + name: 'requestLayersBtn', + reference: 'requestLayersBtn', + formBind: true, // only enabled once the form is valid + disabled: true, + handler: function(btn) { + var view = btn.up('basigx-form-addarcgisrest'); + view.resetState(); + view.requestLayers() + .then( + view.onGetServicesSuccess.bind(view), + view.onGetServicesFailure.bind(view) + ); + } + }] }] }, { xtype: 'fieldset', name: 'fs-available-layers', - layout: 'anchor', + flex: 1, + width: '100%', scrollable: 'y', - maxHeight: 200, - defaults: { - anchor: '100%' - }, + hidden: true, bind: { - title: '{availableLayersFieldSetTitle}' + title: '{availableLayersFieldSetTitle}', + maxHeight: '{availableLayersFieldSetMaxHeight}' }, items: { xtype: 'checkboxgroup', @@ -214,46 +270,6 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { } ], - // Reset and Submit buttons - buttons: [ - { - name: 'resetFormBtn', - bind: { - text: '{resetBtnText}' - }, - handler: function(btn) { - var view = btn.up('basigx-form-addarcgisrest'); - view.getForm().reset(); - view.removeAddLayersComponents(); - view.resetState(); - var defaultValue = view.defaultUrl; - var combo = view.down('combobox[name=urlCombo]'); - combo.setValue(defaultValue); - var textfield = view.down('textfield[name=url]'); - textfield.setValue(defaultValue); - } - }, - '->', - { - bind: { - text: '{requestLayersBtnText}' - }, - name: 'requestLayersBtn', - reference: 'requestLayersBtn', - formBind: true, // only enabled once the form is valid - disabled: true, - handler: function(btn) { - var view = btn.up('basigx-form-addarcgisrest'); - view.resetState(); - view.requestLayers() - .then( - view.onGetServicesSuccess.bind(view), - view.onGetServicesFailure.bind(view) - ); - } - } - ], - /** * Initializes the form and sets up the parser instance. */ @@ -360,19 +376,19 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { return layerConfig.service.type === 'FeatureServer'; } ); + var nonFeatureServers = Ext.Array.filter( + layerConfigs, function(layerConfig) { + return layerConfig.service.type !== 'FeatureServer'; + } + ); this.loadLayersOfFeatureServers(featureServers) .then(function(featureServerConfigs) { - layerConfigs = Ext.Array.filter( - layerConfigs, function(layerConfig) { - return layerConfig.service.type !== 'FeatureServer'; - } - ); - layerConfigs = Ext.Array.merge( - layerConfigs, featureServerConfigs); - this.fillAvailableLayersFieldset(layerConfigs); + var mergedConfigs = Ext.Array.merge( + nonFeatureServers, featureServerConfigs); + this.fillAvailableLayersFieldset(mergedConfigs); this.updateControlToolbarState(); this.setLoading(false); - }.bind(this)); + }.bind(this)); }, /** @@ -407,7 +423,7 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { */ loadLayersOfFeatureServers: function(featureServers) { var me = this; - var mappedPromises = Ext.Array.map(featureServers, function(server) { + var mappedPromises = Ext.Array.map(featureServers, function(server) { return me.requestFeatureServer.call(me, server) .then(function(res) { var config = me.getFeatureServerConfigs( @@ -608,16 +624,27 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { var cbGroup = fs.down('checkboxgroup'); var checkBoxes = []; var candidatesInitiallyChecked = me.getCandidatesInitiallyChecked(); + + fs.setHidden(false); + Ext.each(layers, function(layer) { - var boxLabel = layer.service.name; - if (layer.service.type === 'FeatureServer') { - boxLabel += '/' + layer.layer.name; - } checkBoxes.push({ - xtype: 'checkbox', - boxLabel: boxLabel, + xtype: 'basigx-tree-arcgisrestservicetree', + arcGISLayerConfig: layer, checked: candidatesInitiallyChecked, - arcGISLayerConfig: layer + listeners: { + arcgisrestservicetreenodeexpand: function (expandedNode) { + me.requestLayer(layer).then( + function (response) { + me.onRequestLayerSuccess( + response, + expandedNode + ); + }, + me.onGetServicesFailure.bind(me) + ); + } + } }); }); cbGroup.add(checkBoxes); @@ -662,6 +689,7 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { me.add({ xtype: 'toolbar', name: 'interact-w-available-layers', + width: '100%', items: tbItems }); }, @@ -687,11 +715,37 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { addCheckedLayers: function() { var me = this; var fs = me.down('[name=fs-available-layers]'); - var checkboxes = fs.query('checkbox[checked=true][disabled=false]'); + + // collect checked layers from form + var layerItems = []; + var layerConfigTrees = fs.query('basigx-tree-arcgisrestservicetree'); + Ext.each(layerConfigTrees, function(layerConfig){ + var store = layerConfig.getStore(); + if (!store) { + return; + } + + var root = store.getRoot(); + if (!root) { + return; + } + + if (root.get('checked')) { + layerItems.push(layerConfig); + } + }); + var map = BasiGX.util.Map.getMapComponent().getMap(); var useDefaultHeader = me.getUseDefaultXhrHeader(); - Ext.each(checkboxes, function(checkbox) { - var config = checkbox.arcGISLayerConfig; + Ext.each(layerItems, function(layerItem) { + var config = layerItem.arcGISLayerConfig; + + var subLayerStore = layerItem.getStore(); + if (!subLayerStore){ + return; + } + + config.subLayerStore = subLayerStore; BasiGX.util.ArcGISRest.createOlLayerFromArcGISRest( config, useDefaultHeader ) @@ -702,7 +756,7 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { me.fireEvent('beforearcgisrestadd', layer); map.addLayer(layer); me.fireEvent('arcgisrestadd', layer); - checkbox.setDisabled(true); + layerItem.setDisabled(true); me.updateControlToolbarState(); }); }); @@ -712,10 +766,11 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { * Checks all checkboxes in the available layers fieldset. */ checkAllLayers: function() { - var sel = '[name=fs-available-layers] checkbox[disabled=false]'; - var checkboxes = this.query(sel); - Ext.each(checkboxes, function(checkbox) { - checkbox.setValue(true); + var sel = '[name=fs-available-layers]' + + ' basigx-tree-arcgisrestservicetree'; + var trees = this.query(sel); + Ext.each(trees, function(tree) { + tree.getStore().getAt(0).set('checked', true); }); }, @@ -723,10 +778,71 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { * Unchecks all checkboxes in the available layers fieldset. */ uncheckAllLayers: function() { - var sel = '[name=fs-available-layers] checkbox[disabled=false]'; - var checkboxes = this.query(sel); - Ext.each(checkboxes, function(checkbox) { - checkbox.setValue(false); + var sel = '[name=fs-available-layers]' + + ' basigx-tree-arcgisrestservicetree'; + var trees = this.query(sel); + Ext.each(trees, function(tree) { + tree.getStore().getAt(0).set('checked', false); + }); + }, + + /** + * Set the viewModel property availableLayersFieldSetMaxHeight + * when the component config property availableLayersFieldSetMaxHeight + * changes to it can be used in a binding + * + * @param {Number} newValue Value to set + */ + updateAvailableLayersFieldSetMaxHeight: function(newValue) { + var me = this; + var vm = me.getViewModel(); + vm.set('availableLayersFieldSetMaxHeight', newValue); + }, + + /** + * Request layer to get information about sub layers within the layer + * + * @param {Object} config Layer ArcGIS layer config + */ + requestLayer: function(config) { + var me = this; + var serviceUrl = BasiGX.util.ArcGISRest.createMapServerUrl( + config.url, + config.service.name, + 'json' + ); + return new Ext.Promise(function (resolve, reject) { + Ext.Ajax.request({ + url: serviceUrl, + method: 'GET', + useDefaultXhrHeader: me.getUseDefaultXhrHeader(), + success: function (response) { + var respJson = Ext.decode(response.responseText); + resolve(respJson); + }, + failure: function (response) { + reject(response.status); + } + }); + }); + }, + + /** + * Request layer to get information about sub layers within the layer + * + * @param {Object} response ArcGIS Rest response + * @param {GeoExt.data.model.ArcGISRestServiceLayer} expandedNode Layer ArcGIS layer config + */ + onRequestLayerSuccess: function(response, expandedNode) { + var layers = Ext.Array.map(response.layers, function(layer) { + return Ext.create('GeoExt.data.model.ArcGISRestServiceLayer',{ + layerId: layer.id, + name: layer.name, + defaultVisibility: layer.defaultVisibility, + visibility: layer.defaultVisibility, + leaf: true + }); }); + expandedNode.appendChild(layers); } }); diff --git a/src/view/tree/ArcGISRestServiceTree.js b/src/view/tree/ArcGISRestServiceTree.js new file mode 100644 index 000000000..a3cf02b45 --- /dev/null +++ b/src/view/tree/ArcGISRestServiceTree.js @@ -0,0 +1,117 @@ +/* Copyright (c) 2022-present terrestris GmbH & Co. KG + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +/** + * Used to add an ArcGIS REST layer to the map + * + * @class BasiGX.view.tree.ArcGISRestServiceTree + */ +Ext.define('BasiGX.view.tree.ArcGISRestServiceTree', { + extend: 'Ext.tree.Panel', + xtype: 'basigx-tree-arcgisrestservicetree', + + requires: [ + 'GeoExt.data.model.ArcGISRestServiceLayer', + 'BasiGX.util.ArcGISRest', + 'Ext.tree.Column' + ], + + config: { + /** + * Whether we will send the `X-Requested-With` header when fetching the + * document from the URL. The `X-Requested-With` header is + * usually added for XHR, but adding it should lead to a preflight + * request (see https://goo.gl/6JzdUI), which some servers fail. + * + * @type {Boolean} + */ + useDefaultXhrHeader: false + }, + + arcGISLayerConfig: null, + + + listeners: { + itemclick: function(view, record){ + // toggle visibility of sublayer + var currentVisibility = record.get('visibility'); + record.set('visibility', !currentVisibility); + }, + beforecheckchange: function(node, checked){ + // when layer is not checked anymore it will be collapsed + if (checked) { + node.collapse(); + } + } + }, + + columns: { + header: false, + items: [{ + xtype: 'treecolumn', + renderer: function(v, metaData, record) { + if (!record.isRoot()) { + var eyeGlyph = 'xf06e@FontAwesome'; + var eyeSlashGlyph = 'xf070@FontAwesome'; + if (record.get('visibility')) { + metaData.glyph = eyeGlyph; + } else { + metaData.glyph = eyeSlashGlyph; + } + } + return record.get('name'); + } + }], + defaults: { + flex: 1 + } + }, + + initComponent: function() { + var me = this; + me.callParent(); + + var rootLabel = me.arcGISLayerConfig.service.name; + if (me.arcGISLayerConfig.service.type === 'FeatureServer') { + rootLabel += '/' + me.arcGISLayerConfig.layer.name; + } + + me.setStore({ + model: 'GeoExt.data.model.ArcGISRestServiceLayer', + root: { + name: rootLabel, + checked: true, + children: [] + }, + listeners: { + 'nodeexpand': me.onNodeExpand.bind(me) + } + }); + }, + + onNodeExpand: function(expandedNode) { + var me = this; + + // ensure expanded layer is always checked + expandedNode.set('checked', true); + + if (expandedNode.hasChildNodes()) { + return; + } + + me.fireEvent('arcgisrestservicetreenodeexpand', expandedNode); + } + +}); diff --git a/test/resources/arcgis/copernicus-hotspotmonitoring-mapServer.json b/test/resources/arcgis/copernicus-hotspotmonitoring-mapServer.json new file mode 100644 index 000000000..3db821f34 --- /dev/null +++ b/test/resources/arcgis/copernicus-hotspotmonitoring-mapServer.json @@ -0,0 +1,197 @@ +{ + "currentVersion": 10.41, + "serviceDescription": "", + "mapName": "Layers", + "description": "", + "copyrightText": "", + "supportsDynamicLayers": false, + "layers": [ + { + "id": 0, + "name": "Urban Atlas", + "parentLayerId": -1, + "defaultVisibility": false, + "subLayerIds": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ], + "minScale": 0, + "maxScale": 0 + }, + { + "id": 1, + "name": "Street Trees - Dublin", + "parentLayerId": 0, + "defaultVisibility": false, + "subLayerIds": null, + "minScale": 0, + "maxScale": 0 + }, + { + "id": 2, + "name": "Street Trees - Galway", + "parentLayerId": 0, + "defaultVisibility": false, + "subLayerIds": null, + "minScale": 0, + "maxScale": 0 + }, + { + "id": 3, + "name": "Street Trees - Waterford", + "parentLayerId": 0, + "defaultVisibility": false, + "subLayerIds": null, + "minScale": 0, + "maxScale": 0 + }, + { + "id": 4, + "name": "Land Cover - Cork", + "parentLayerId": 0, + "defaultVisibility": false, + "subLayerIds": null, + "minScale": 0, + "maxScale": 0 + }, + { + "id": 5, + "name": "Land Cover - Dublin", + "parentLayerId": 0, + "defaultVisibility": false, + "subLayerIds": null, + "minScale": 0, + "maxScale": 0 + }, + { + "id": 6, + "name": "Land Cover - Galway", + "parentLayerId": 0, + "defaultVisibility": false, + "subLayerIds": null, + "minScale": 0, + "maxScale": 0 + }, + { + "id": 7, + "name": "Land Cover - Limerick", + "parentLayerId": 0, + "defaultVisibility": false, + "subLayerIds": null, + "minScale": 0, + "maxScale": 0 + }, + { + "id": 8, + "name": "Land Cover - Waterford", + "parentLayerId": 0, + "defaultVisibility": false, + "subLayerIds": null, + "minScale": 0, + "maxScale": 0 + }, + { + "id": 9, + "name": "Riparian Zones", + "parentLayerId": -1, + "defaultVisibility": false, + "subLayerIds": [ + 10, + 11 + ], + "minScale": 0, + "maxScale": 0 + }, + { + "id": 10, + "name": "Green Linear Elements", + "parentLayerId": 9, + "defaultVisibility": false, + "subLayerIds": null, + "minScale": 0, + "maxScale": 0 + }, + { + "id": 11, + "name": "Land Cover", + "parentLayerId": 9, + "defaultVisibility": false, + "subLayerIds": null, + "minScale": 0, + "maxScale": 0 + }, + { + "id": 12, + "name": "Natura 2000", + "parentLayerId": -1, + "defaultVisibility": false, + "subLayerIds": [ + 13 + ], + "minScale": 0, + "maxScale": 0 + }, + { + "id": 13, + "name": "Natura 2000", + "parentLayerId": 12, + "defaultVisibility": false, + "subLayerIds": null, + "minScale": 0, + "maxScale": 0 + } + ], + "tables": [], + "spatialReference": { + "wkid": 2157, + "latestWkid": 2157 + }, + "singleFusedMapCache": false, + "initialExtent": { + "xmin": -2192431.987076646, + "ymin": -1622206.551353204, + "xmax": 5343982.999664094, + "ymax": 3891074.1323311795, + "spatialReference": { + "wkid": 2157, + "latestWkid": 2157 + } + }, + "fullExtent": { + "xmin": 448851.36479999963, + "ymin": 533041.3618999999, + "xmax": 754159.6705, + "ymax": 950054.5759999994, + "spatialReference": { + "wkid": 2157, + "latestWkid": 2157 + } + }, + "minScale": 0, + "maxScale": 0, + "units": "esriMeters", + "supportedImageFormatTypes": "PNG32,PNG24,PNG,JPG,DIB,TIFF,EMF,PS,PDF,GIF,SVG,SVGZ,BMP", + "documentInfo": { + "Title": "", + "Author": "", + "Comments": "", + "Subject": "The European Environment Agency, as lead authority under the Copernicus Land Monitoring Service, co-ordinate the development of high resolution \u201clocal component\u201d or \u201chot spot monitoring\u201d datasets. Comparable data is produced across Europe using earth", + "Category": "", + "AntialiasingMode": "None", + "TextAntialiasingMode": "Force", + "Keywords": "Copernicus,Land Cover,Land Use,Riparian Zones,Natura,Urban Atlas,Hot Spot Monitoring" + }, + "capabilities": "Map,Query,Data", + "supportedQueryFormats": "JSON, AMF, geoJSON", + "exportTilesAllowed": false, + "maxRecordCount": 1000, + "maxImageHeight": 4096, + "maxImageWidth": 4096, + "supportedExtensions": "KmlServer" +} diff --git a/test/resources/arcgis/copernicus.json b/test/resources/arcgis/copernicus.json new file mode 100644 index 000000000..e80cf988a --- /dev/null +++ b/test/resources/arcgis/copernicus.json @@ -0,0 +1,14 @@ +{ + "currentVersion": 10.41, + "folders": [], + "services": [ + { + "name": "Copernicus/HighResolutionLayers", + "type": "MapServer" + }, + { + "name": "Copernicus/HotSpotMonitoring", + "type": "MapServer" + } + ] +} diff --git a/test/resources/arcgis/services.json b/test/resources/arcgis/services.json new file mode 100644 index 000000000..68a533b8e --- /dev/null +++ b/test/resources/arcgis/services.json @@ -0,0 +1,16 @@ +{ + "currentVersion": 10.41, + "folders": [ + "BW", + "Copernicus", + "EPAMapServices", + "TimpeallAnTi", + "Utilities" + ], + "services": [ + { + "name": "BasemapHW", + "type": "MapServer" + } + ] +} diff --git a/test/spec/util/ArcGISRest.test.js b/test/spec/util/ArcGISRest.test.js index 9b062f02f..b834168ff 100644 --- a/test/spec/util/ArcGISRest.test.js +++ b/test/spec/util/ArcGISRest.test.js @@ -100,4 +100,35 @@ describe('BasiGX.util.ArcGISRest', function() { }); }); + + describe('#createMapServerUrl', function() { + + it('returns the MapServer url when the service in on the root', function() { + var serviceUrl = 'http://example.com/services'; + var mapServerName = 'foo'; + var url = serviceUrl + '/' + mapServerName + '/MapServer'; + var result = BasiGX.util.ArcGISRest.createMapServerUrl( + serviceUrl, mapServerName); + expect(result).to.equal(url); + }); + + it('returns the MapServer url when the service is in a folder', function() { + var serviceUrl = 'http://example.com/services/folder'; + var mapServerName = 'folder/servername'; + var expectedUrl = 'http://example.com/services/folder/servername/MapServer'; + var result = BasiGX.util.ArcGISRest.createMapServerUrl( + serviceUrl, mapServerName); + expect(result).to.equal(expectedUrl); + }); + + it('returns the MapServer url with a specified format', function() { + var serviceUrl = 'http://example.com/services'; + var featureServerName = 'foo'; + var format = 'bar'; + var url = serviceUrl + '/' + featureServerName + '/MapServer?f=' + format; + var result = BasiGX.util.ArcGISRest.createMapServerUrl( + serviceUrl, featureServerName, format); + expect(result).to.equal(url); + }); + }); }); diff --git a/test/spec/view/form/AddArcGISRest.test.js b/test/spec/view/form/AddArcGISRest.test.js index 76634edef..8ef4eff83 100644 --- a/test/spec/view/form/AddArcGISRest.test.js +++ b/test/spec/view/form/AddArcGISRest.test.js @@ -94,6 +94,19 @@ describe('BasiGX.view.form.AddArcGISRest', function() { describe('Methods', function() { var form = null; + var layers = [{ + service: { + name: 'Copernicus/HighResolutionLayers', + type: 'MapServer' + }, + url: 'https://gis.epa.ie/arcgis/rest/services/Copernicus?f=json' + },{ + service: { + name: 'Copernicus/HotSpotMonitoring', + type: 'MapServer' + }, + url: 'https://gis.epa.ie/arcgis/rest/services/Copernicus?f=json' + }]; beforeEach(function() { form = Ext.create('BasiGX.view.form.AddArcGISRest', { renderTo: Ext.getBody() @@ -185,5 +198,173 @@ describe('BasiGX.view.form.AddArcGISRest', function() { }); }); }); + describe('checkAllLayers / uncheckAllLayers', function() { + it('checks and unchecks all layers', function() { + function isAllChecked(trees) { + return Ext.Array.every(trees, function (tree) { + return tree.getStore().getAt(0).get('checked'); + }); + } + + form.fillAvailableLayersFieldset(layers); + var trees = form.query('[name=fs-available-layers] basigx-tree-arcgisrestservicetree'); + expect(isAllChecked(trees)).to.be(true); + + form.uncheckAllLayers(); + expect(isAllChecked(trees)).to.be(false); + + form.checkAllLayers(); + expect(isAllChecked(trees)).to.be(true); + }); + }); + }); + + describe('Behaviour', function() { + var form = null; + var xhr; + var requests; + beforeEach(function() { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = function(xhr) { + requests.push(xhr); + }; + form = Ext.create('BasiGX.view.form.AddArcGISRest', { + renderTo: Ext.getBody() + }); + }); + afterEach(function() { + xhr.restore(); + //requests = []; + if (form) { + form.destroy(); + form = null; + } + }); + + function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + async function fakeXhrResponse(path) { + const res = await fetch(path); + const json = await res.text(); + requests[requests.length - 1].respond(200, { 'Content-Type': 'application/json' }, json); + // allow time for Extjs to render UI items + await sleep(10); + } + + it('fetches root services and creates checkboxgroups', async function() { + var spy = sinon.spy(form, 'requestLayers'); + form.down('[name="url"]').setValue('https://test.com/services/service'); + form.down('[name="requestLayersBtn"]').click(); + await fakeXhrResponse('/resources/arcgis/services.json'); + + var checkboxes = form.query('basigx-tree-arcgisrestservicetree'); + expect(checkboxes.length).to.be(1); + expect(spy.called).to.be(true); + }); + + it('fetches services and creates checkboxgroups', async function() { + var spy = sinon.spy(form, 'requestLayers'); + form.down('[name="url"]').setValue('https://test.com/services/service'); + form.down('[name="requestLayersBtn"]').click(); + await fakeXhrResponse('/resources/arcgis/copernicus.json'); + + var checkboxes = form.query('basigx-tree-arcgisrestservicetree'); + expect(checkboxes.length).to.be(2); + expect(spy.called).to.be(true); + }); + + it('fetches layers and populates checkboxgroups when a checkboxgroup is expanded', async function() { + var spy = sinon.spy(form, 'requestLayer'); + form.down('[name="url"]').setValue('https://test.com/services/service'); + form.down('[name="requestLayersBtn"]').click(); + await fakeXhrResponse('/resources/arcgis/copernicus.json'); + + var tree = form.down('basigx-tree-arcgisrestservicetree'); + tree.expandNode(tree.getStore().getAt(0)); + await fakeXhrResponse('/resources/arcgis/copernicus-hotspotmonitoring-mapServer.json'); + + expect(tree.getStore().getAt(0).childNodes.length).to.be(14); + expect(spy.called).to.be(true); + }); + + it('adds checked layers', async function() { + var spy = sinon.spy(); + form.down('[name="url"]').setValue('https://test.com/services/service'); + form.down('[name="requestLayersBtn"]').click(); + await fakeXhrResponse('/resources/arcgis/copernicus.json'); + + var tree = form.down('basigx-tree-arcgisrestservicetree'); + tree.expandNode(tree.getStore().getAt(0)); + await fakeXhrResponse('/resources/arcgis/copernicus-hotspotmonitoring-mapServer.json'); + + form.down('[name=add-checked-layers]').click(); + + // monitor arcgisrestadd events + form.on('arcgisrestadd', spy); + + // respond again with layer info since createOlLayerFromArcGISRest calls the service url again + // two layers are checked so two xhr requests are created in quick succession + // requests are created + const res = await fetch('/resources/arcgis/copernicus-hotspotmonitoring-mapServer.json'); + const json = await res.text(); + requests[requests.length - 2].respond(200, { 'Content-Type': 'application/json' }, json); + requests[requests.length - 1].respond(200, { 'Content-Type': 'application/json' }, json); + await sleep(10); + + // two layers added, means two arcgisrestadd events fired + expect(spy.callCount).to.be(2); + }); + + it('handles service call errors', async function() { + var spy = sinon.spy(form, 'onGetServicesFailure'); + var codes = [0, 400, 401, 403, 404, 429, 500, 503, 504, 505]; + var count = 0; + for (var code of codes) { + form.down('[name="url"]').setValue('https://test.com/services/service'); + form.down('[name="requestLayersBtn"]').click(); + requests[requests.length - 1].respond(code); + count++; + await sleep(10); + expect(spy.callCount).to.be(count); + } + }); + + it('handles arcgis rest errors', async function() { + // doesn't seem to be an easy way to check if the warn dialog is present + // instead check the getErrorMessage is called along with onGetServicesSuccess + // this means the response is 200 but body has an arcgis rest error + var spy1 = sinon.spy(form, 'onGetServicesSuccess'); + var spy2 = sinon.spy(form, 'getErrorMessage'); + + form.down('[name="url"]').setValue('https://test.com/services/service'); + form.down('[name="requestLayersBtn"]').click(); + requests[requests.length - 1].respond( + 200, + { 'Content-Type': 'application/json' }, + '{"error":{"code":404,"message":"Folder not found","details":[]}}'); + await sleep(10); + + expect(spy1.callCount).to.be(1); + expect(spy2.callCount).to.be(1); + }); + + it('resets ui and state on reset button click', async function() { + // populate the ui + form.down('[name="url"]').setValue('https://test.com/services/service'); + form.down('[name="requestLayersBtn"]').click(); + await fakeXhrResponse('/resources/arcgis/services.json'); + var fs = form.down('[name=fs-available-layers]'); + + // verify available-layers is show, then click reset + expect(fs.hidden).to.be(false); + form.down('[name="resetFormBtn"]').click(); + + // verify available-layers is hidden and url textbox has been reset + expect(form.down('[name="url"]').getValue()).to.not.be('https://test.com/services/service'); + expect(fs.hidden).to.be(true); + }); }); });