diff --git a/package.json b/package.json index d1fd3528b9..0e37065b12 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,7 @@ "leaflet-plugins": "https://github.com/Polyconseil/leaflet-plugins/tarball/master", "leaflet-simple-graticule": "1.0.2", "leaflet.locatecontrol": "0.45.1", + "leaflet.nontiledlayer": "1.0.3", "lodash": "4.16.6", "moment": "2.13.0", "object-assign": "4.1.1", diff --git a/web/client/components/TOC/fragments/settings/Display.jsx b/web/client/components/TOC/fragments/settings/Display.jsx index e81441641b..286f7059ce 100644 --- a/web/client/components/TOC/fragments/settings/Display.jsx +++ b/web/client/components/TOC/fragments/settings/Display.jsx @@ -49,10 +49,16 @@ module.exports = React.createClass({ {this.props.onChange("transparent", event.target.checked); }}> this.props.onChange("tiled", e.target.checked)} checked={this.props.element && this.props.element.tiled !== undefined ? this.props.element.tiled : true} > + this.props.onChange("singleTile", e.target.checked)}> + + )] : null} ); } diff --git a/web/client/components/TOC/fragments/settings/__tests__/Display-test.jsx b/web/client/components/TOC/fragments/settings/__tests__/Display-test.jsx index d90b90a48c..60b7004d26 100644 --- a/web/client/components/TOC/fragments/settings/__tests__/Display-test.jsx +++ b/web/client/components/TOC/fragments/settings/__tests__/Display-test.jsx @@ -68,7 +68,7 @@ describe('test Layer Properties Display module component', () => { expect(comp).toExist(); const inputs = ReactTestUtils.scryRenderedDOMComponentsWithTag( comp, "input" ); expect(inputs).toExist(); - expect(inputs.length).toBe(2); + expect(inputs.length).toBe(3); inputs[0].click(); expect(spy.calls.length).toBe(1); }); diff --git a/web/client/components/map/cesium/Layer.jsx b/web/client/components/map/cesium/Layer.jsx index cf494d5044..27a0de196c 100644 --- a/web/client/components/map/cesium/Layer.jsx +++ b/web/client/components/map/cesium/Layer.jsx @@ -1,5 +1,5 @@ -/** - * Copyright 2015, GeoSolutions Sas. +/* + * Copyright 2017, GeoSolutions Sas. * All rights reserved. * * This source code is licensed under the BSD-style license found in the @@ -19,13 +19,13 @@ const CesiumLayer = React.createClass({ componentDidMount() { this.createLayer(this.props.type, this.props.options, this.props.position, this.props.map); if (this.props.options && this.layer && this.props.options.visibility !== false) { - this.addLayer(); + this.addLayer(this.props); this.updateZIndex(); } }, componentWillReceiveProps(newProps) { const newVisibility = newProps.options && newProps.options.visibility !== false; - this.setLayerVisibility(newVisibility); + this.setLayerVisibility(newVisibility, newProps); const newOpacity = (newProps.options && newProps.options.opacity !== undefined) ? newProps.options.opacity : 1.0; this.setLayerOpacity(newOpacity); @@ -47,7 +47,7 @@ const CesiumLayer = React.createClass({ const oldProvider = this.provider; const newLayer = this.layer.updateParams(newProps.options.params); this.layer = newLayer; - this.addLayer(); + this.addLayer(newProps); setTimeout(() => { this.removeLayer(oldProvider); }, 1000); @@ -106,11 +106,11 @@ const CesiumLayer = React.createClass({ }); } }, - setLayerVisibility(visibility) { + setLayerVisibility(visibility, newProps) { var oldVisibility = this.props.options && this.props.options.visibility !== false; if (visibility !== oldVisibility) { if (visibility) { - this.addLayer(); + this.addLayer(newProps); this.updateZIndex(); } else { this.removeLayer(); @@ -136,26 +136,28 @@ const CesiumLayer = React.createClass({ updateLayer(newProps, oldProps) { const newLayer = Layers.updateLayer(newProps.type, this.layer, newProps.options, oldProps.options, this.props.map); if (newLayer) { + this.removeLayer(); this.layer = newLayer; + this.addLayer(newProps); } }, - addLayerInternal() { + addLayerInternal(newProps) { this.provider = this.props.map.imageryLayers.addImageryProvider(this.layer); this.provider._position = this.props.position; - if (this.props.options.opacity) { - this.provider.alpha = this.props.options.opacity; + if (newProps.options.opacity !== undefined) { + this.provider.alpha = newProps.options.opacity; } }, - addLayer() { + addLayer(newProps) { if (this.layer && !this.layer.detached) { - this.addLayerInternal(); + this.addLayerInternal(newProps); if (this.props.options.refresh && this.layer.updateParams) { let counter = 0; this.refreshTimer = setInterval(() => { const newLayer = this.layer.updateParams(assign({}, this.props.options.params, {_refreshCounter: counter++})); this.removeLayer(); this.layer = newLayer; - this.addLayerInternal(); + this.addLayerInternal(newProps); }, this.props.options.refresh); } } diff --git a/web/client/components/map/cesium/plugins/TileProviderLayer.js b/web/client/components/map/cesium/plugins/TileProviderLayer.js index 929e07b952..5115509319 100644 --- a/web/client/components/map/cesium/plugins/TileProviderLayer.js +++ b/web/client/components/map/cesium/plugins/TileProviderLayer.js @@ -1,4 +1,4 @@ -/** +/* * Copyright 2017, GeoSolutions Sas. * All rights reserved. * @@ -10,7 +10,7 @@ var Layers = require('../../../../utils/cesium/Layers'); var Cesium = require('../../../../libs/cesium'); var TileProvider = require('../../../../utils/TileConfigProvider'); var ConfigUtils = require('../../../../utils/ConfigUtils'); -var {isObject, isArray} = require('lodash'); +const ProxyUtils = require('../../../../utils/ProxyUtils'); function splitUrl(originalUrl) { let url = originalUrl; @@ -34,7 +34,7 @@ TileProviderProxy.prototype.getURL = function(resource) { if (url.indexOf("//") === 0) { url = location.protocol + url; } - return this.proxy + encodeURIComponent(url + queryString); + return this.proxy.url + encodeURIComponent(url + queryString); }; function NoProxy() { @@ -64,16 +64,7 @@ Layers.registerType('tileprovider', (options) => { let proxyUrl = ConfigUtils.getProxyUrl({}); let proxy; if (proxyUrl) { - let useCORS = []; - if (isObject(proxyUrl)) { - useCORS = proxyUrl.useCORS || []; - proxyUrl = proxyUrl.url; - } - if (isArray(url)) { - url = url[0]; - } - const isCORS = useCORS.reduce((found, current) => found || url.indexOf(current) === 0, false); - proxy = !isCORS && proxyUrl; + proxy = opt.noCors || ProxyUtils.needProxy(url); } const cr = opt.credits; const credit = cr ? new Cesium.Credit(cr.text, cr.imageUrl, cr.link) : opt.attribution; @@ -84,6 +75,6 @@ Layers.registerType('tileprovider', (options) => { maximumLevel: opt.maxZoom, minimumLevel: opt.minZoom, credit, - proxy: proxy && opt.noCors ? new TileProviderProxy(proxyUrl) : new NoProxy() + proxy: proxy ? new TileProviderProxy(proxyUrl) : new NoProxy() }); }); diff --git a/web/client/components/map/cesium/plugins/WMSLayer.js b/web/client/components/map/cesium/plugins/WMSLayer.js index 5af9dfcec9..b400061a51 100644 --- a/web/client/components/map/cesium/plugins/WMSLayer.js +++ b/web/client/components/map/cesium/plugins/WMSLayer.js @@ -1,5 +1,5 @@ /* - * Copyright 2015, GeoSolutions Sas. + * Copyright 2017, GeoSolutions Sas. * All rights reserved. * * This source code is licensed under the BSD-style license found in the @@ -12,6 +12,7 @@ const ProxyUtils = require('../../../../utils/ProxyUtils'); const Cesium = require('../../../../libs/cesium'); const assign = require('object-assign'); const {isArray} = require('lodash'); +const WMSUtils = require('../../../../utils/cesium/WMSUtils'); function getWMSURLs( urls ) { return urls.map((url) => url.split("\?")[0]); @@ -37,7 +38,7 @@ function WMSProxy(proxy) { WMSProxy.prototype.getURL = function(resource) { let {url, queryString} = splitUrl(resource); - return this.proxy + encodeURIComponent(url + queryString); + return this.proxy.url + encodeURIComponent(url + queryString); }; function NoProxy() { @@ -111,5 +112,21 @@ const createLayer = (options) => { }; return layer; }; - -Layers.registerType('wms', createLayer); +const updateLayer = (layer, newOptions, oldOptions) => { + const requiresUpdate = (el) => WMSUtils.PARAM_OPTIONS.indexOf(el.toLowerCase()) >= 0; + const newParams = (newOptions && newOptions.params); + const oldParams = (oldOptions && oldOptions.params); + const allParams = {...newParams, ...oldParams }; + let newParameters = Object.keys({...newOptions, ...oldOptions, ...allParams}) + .filter(requiresUpdate) + .filter((key) => { + const oldOption = oldOptions[key] === undefined ? oldParams && oldParams[key] : oldOptions[key]; + const newOption = newOptions[key] === undefined ? newParams && newParams[key] : newOptions[key]; + return oldOption !== newOption; + }); + if (newParameters.length > 0) { + return createLayer(newOptions); + } + return null; +}; +Layers.registerType('wms', {create: createLayer, update: updateLayer}); diff --git a/web/client/components/map/leaflet/Layer.jsx b/web/client/components/map/leaflet/Layer.jsx index b8f08a214d..f2b89f7c79 100644 --- a/web/client/components/map/leaflet/Layer.jsx +++ b/web/client/components/map/leaflet/Layer.jsx @@ -134,7 +134,13 @@ const LeafletLayer = React.createClass({ } }, updateLayer(newProps, oldProps) { - Layers.updateLayer(newProps.type, this.layer, this.generateOpts(newProps.options, newProps.position), this.generateOpts(oldProps.options, oldProps.position)); + const newLayer = Layers.updateLayer(newProps.type, this.layer, this.generateOpts(newProps.options, newProps.position), + this.generateOpts(oldProps.options, oldProps.position)); + if (newLayer) { + this.removeLayer(); + this.layer = newLayer; + this.addLayer(); + } }, addLayer() { if (this.isValid()) { diff --git a/web/client/components/map/leaflet/plugins/WMSLayer.js b/web/client/components/map/leaflet/plugins/WMSLayer.js index 74ec6034d5..8c4a9eb5b4 100644 --- a/web/client/components/map/leaflet/plugins/WMSLayer.js +++ b/web/client/components/map/leaflet/plugins/WMSLayer.js @@ -1,5 +1,5 @@ -/** - * Copyright 2015, GeoSolutions Sas. +/* + * Copyright 2017, GeoSolutions Sas. * All rights reserved. * * This source code is licensed under the BSD-style license found in the @@ -13,6 +13,7 @@ const L = require('leaflet'); const objectAssign = require('object-assign'); const {isArray, isEqual} = require('lodash'); const SecurityUtils = require('../../../../utils/SecurityUtils'); +require('leaflet.nontiledlayer'); L.TileLayer.MultipleUrlWMS = L.TileLayer.WMS.extend({ @@ -42,7 +43,6 @@ L.TileLayer.MultipleUrlWMS = L.TileLayer.WMS.extend({ L.setOptions(this, options); }, - getTileUrl: function(tilePoint) { // (Point, Number) -> String let map = this._map; let tileSize = this.options.tileSize; @@ -79,6 +79,7 @@ function wmsToLeafletOptions(options) { transparent: options.transparent !== undefined ? options.transparent : true, tiled: options.tiled !== undefined ? options.tiled : true, opacity: opacity, + zIndex: options.zIndex, version: options.version || "1.3.0", SRS: CoordinatesUtils.normalizeSRS(options.srs || 'EPSG:3857', options.allowedSRS), CRS: CoordinatesUtils.normalizeSRS(options.srs || 'EPSG:3857', options.allowedSRS), @@ -95,6 +96,9 @@ Layers.registerType('wms', { const urls = getWMSURLs(isArray(options.url) ? options.url : [options.url]); const queryParameters = wmsToLeafletOptions(options) || {}; urls.forEach(url => SecurityUtils.addAuthenticationParameter(url, queryParameters)); + if (options.singleTile) { + return L.nonTiledLayer.wms(urls[0], queryParameters); + } return L.tileLayer.multipleUrlWMS(urls, queryParameters); }, update: function(layer, newOptions, oldOptions) { @@ -103,12 +107,36 @@ Layers.registerType('wms', { let newQueryParameters = WMSUtils.filterWMSParamOptions(wmsToLeafletOptions(newOptions)); let newParameters = Object.keys(newQueryParameters).filter((key) => {return newQueryParameters[key] !== oldqueryParameters[key]; }); let newParams = {}; + let newLayer; + if (oldOptions.singleTile !== newOptions.singleTile) { + const urls = getWMSURLs(isArray(newOptions.url) ? newOptions.url : [newOptions.url]); + urls.forEach(url => SecurityUtils.addAuthenticationParameter(url, newQueryParameters)); + if (newOptions.singleTile) { + // return the nonTiledLayer + newLayer = L.nonTiledLayer.wms(urls[0], newQueryParameters); + } else { + newLayer = L.tileLayer.multipleUrlWMS(urls, newQueryParameters); + } + if ( newParameters.length > 0 ) { + newParams = newParameters.reduce( (accumulator, currentValue) => { + return objectAssign({}, accumulator, {[currentValue]: newQueryParameters[currentValue] }); + }, newParams); + // set new options as parameters, merged with params + newLayer.setParams(objectAssign(newParams, newParams.params, newOptions.params)); + } else if (!isEqual(newOptions.params, oldOptions.params)) { + newLayer.setParams(newOptions.params); + } + return newLayer; + } if ( newParameters.length > 0 ) { - newParameters.forEach( key => newParams[key] = newQueryParameters[key] ); + newParams = newParameters.reduce( (accumulator, currentValue) => { + return objectAssign({}, accumulator, {[currentValue]: newQueryParameters[currentValue] }); + }, newParams); // set new options as parameters, merged with params layer.setParams(objectAssign(newParams, newParams.params, newOptions.params)); } else if (!isEqual(newOptions.params, oldOptions.params)) { layer.setParams(newOptions.params); } + return null; } }); diff --git a/web/client/components/map/openlayers/Layer.jsx b/web/client/components/map/openlayers/Layer.jsx index c9f36421f6..7da08c9776 100644 --- a/web/client/components/map/openlayers/Layer.jsx +++ b/web/client/components/map/openlayers/Layer.jsx @@ -121,13 +121,18 @@ const OpenlayersLayer = React.createClass({ return; } } - Layers.updateLayer( + const newLayer = Layers.updateLayer( this.props.type, this.layer, this.generateOpts(newProps.options, newProps.position, newProps.projection), this.generateOpts(oldProps.options, oldProps.position, oldProps.projection), this.props.map, this.props.mapId); + if (newLayer) { + this.props.map.removeLayer(this.layer); + this.layer = newLayer; + this.addLayer(newProps.options); + } }, addLayer(options) { if (this.isValid()) { diff --git a/web/client/components/map/openlayers/plugins/WMSLayer.js b/web/client/components/map/openlayers/plugins/WMSLayer.js index 6ceeca0737..f1aa5fcb74 100644 --- a/web/client/components/map/openlayers/plugins/WMSLayer.js +++ b/web/client/components/map/openlayers/plugins/WMSLayer.js @@ -1,5 +1,5 @@ -/** - * Copyright 2015, GeoSolutions Sas. +/* + * Copyright 2017, GeoSolutions Sas. * All rights reserved. * * This source code is licensed under the BSD-style license found in the @@ -77,7 +77,7 @@ Layers.registerType('wms', { }, (options.forceProxy) ? {tileLoadFunction: proxyTileLoadFunction} : {})) }); }, - update: (layer, newOptions, oldOptions) => { + update: (layer, newOptions, oldOptions, map) => { if (oldOptions && layer && layer.getSource() && layer.getSource().updateParams) { let changed = false; if (oldOptions.params && newOptions.params) { @@ -110,6 +110,45 @@ Layers.registerType('wms', { if (changed) { layer.getSource().updateParams(objectAssign(newParams, newOptions.params)); } + if (oldOptions.singleTile !== newOptions.singleTile) { + const urls = getWMSURLs(isArray(newOptions.url) ? newOptions.url : [newOptions.url]); + const queryParameters = wmsToOpenlayersOptions(newOptions) || {}; + urls.forEach(url => SecurityUtils.addAuthenticationParameter(url, queryParameters)); + let newLayer; + if (newOptions.singleTile) { + // return the Image Layer with the related source + newLayer = new ol.layer.Image({ + opacity: newOptions.opacity !== undefined ? newOptions.opacity : 1, + visible: newOptions.visibility !== false, + zIndex: newOptions.zIndex, + source: new ol.source.ImageWMS({ + url: urls[0], + params: queryParameters + }) + }); + } else { + // return the Tile Layer with the related source + const mapSrs = map && map.getView() && map.getView().getProjection() && map.getView().getProjection().getCode() || 'EPSG:3857'; + const extent = ol.proj.get(CoordinatesUtils.normalizeSRS(newOptions.srs || mapSrs, newOptions.allowedSRS)).getExtent(); + newLayer = new ol.layer.Tile({ + opacity: newOptions.opacity !== undefined ? newOptions.opacity : 1, + visible: newOptions.visibility !== false, + zIndex: newOptions.zIndex, + source: new ol.source.TileWMS(objectAssign({ + urls: urls, + params: queryParameters, + tileGrid: new ol.tilegrid.TileGrid({ + extent: extent, + resolutions: mapUtils.getResolutions(), + tileSize: newOptions.tileSize ? newOptions.tileSize : 256, + origin: newOptions.origin ? newOptions.origin : [extent[0], extent[1]] + }) + }, (newOptions.forceProxy) ? {tileLoadFunction: proxyTileLoadFunction} : {})) + }); + } + return newLayer; + } + return null; } } }); diff --git a/web/client/translations/data.de-DE b/web/client/translations/data.de-DE index 097b527add..01df880190 100644 --- a/web/client/translations/data.de-DE +++ b/web/client/translations/data.de-DE @@ -30,6 +30,7 @@ "display": "Darstellung", "style": "Stil", "transparent": "Transparent", + "singleTile": "Single Tile", "cached": "Verwenden Sie Cache-Optionen", "styleCustom": "Stil \"{value}\" nutzen", "styleListLoadError": "Es gab einen Fehler beim Laden der Liste von Stilen", diff --git a/web/client/translations/data.en-US b/web/client/translations/data.en-US index 35bfcc02d6..ce11eb37fb 100644 --- a/web/client/translations/data.en-US +++ b/web/client/translations/data.en-US @@ -30,6 +30,7 @@ "display": "Display", "style": "Style", "transparent": "Transparent", + "singleTile": "Single Tile", "cached": "Use cache options", "styleCustom": "Use style named \"{value}\"", "styleListLoadError": "There was an error loading the styles list", diff --git a/web/client/translations/data.fr-FR b/web/client/translations/data.fr-FR index 734842f9e8..9fb8b1e6c9 100644 --- a/web/client/translations/data.fr-FR +++ b/web/client/translations/data.fr-FR @@ -30,6 +30,7 @@ "display": "Affichage", "style": "Style", "transparent": "Transparent", + "singleTile": "Single Tile", "cached": "Utiliser les options de cache", "styleCustom": "Utilisez un style nommé \"{value} \"", "styleListLoadError": "Une erreur s'est produite lors du chargement de la liste de styles", diff --git a/web/client/translations/data.it-IT b/web/client/translations/data.it-IT index 07efd849cd..31afee639c 100644 --- a/web/client/translations/data.it-IT +++ b/web/client/translations/data.it-IT @@ -30,6 +30,7 @@ "display": "Visualizzazione", "style": "Stile", "transparent": "Trasparenza", + "singleTile": "Single Tile", "cached": "Usa la cache", "styleCustom": "Usa stile con nome \"{value}\"", "styleListLoadError": "Si è verificato un errore durante il caricamento della lista degli stili", diff --git a/web/client/utils/cesium/Layers.js b/web/client/utils/cesium/Layers.js index 20af4b3236..068793115d 100644 --- a/web/client/utils/cesium/Layers.js +++ b/web/client/utils/cesium/Layers.js @@ -1,5 +1,5 @@ -/** - * Copyright 2015, GeoSolutions Sas. +/* + * Copyright 2017, GeoSolutions Sas. * All rights reserved. * * This source code is licensed under the BSD-style license found in the @@ -37,7 +37,6 @@ var Layers = { if (layerCreator && layerCreator.update) { return layerCreator.update(layer, newOptions, oldOptions, map); } - } }; diff --git a/web/client/utils/cesium/WMSUtils.js b/web/client/utils/cesium/WMSUtils.js new file mode 100644 index 0000000000..5b5c15e25d --- /dev/null +++ b/web/client/utils/cesium/WMSUtils.js @@ -0,0 +1,13 @@ +/* + * Copyright 2017, 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 WMSUtils = { + PARAM_OPTIONS: ["layers", "styles", "style", "format", "transparent", "version", "tiled", "opacity", "zindex", "srs", "singletile" ] +}; + +module.exports = WMSUtils; diff --git a/web/client/utils/leaflet/WMSUtils.js b/web/client/utils/leaflet/WMSUtils.js index 7698bd7ea4..c5792ee9c1 100644 --- a/web/client/utils/leaflet/WMSUtils.js +++ b/web/client/utils/leaflet/WMSUtils.js @@ -1,5 +1,5 @@ -/** - * Copyright 2016, GeoSolutions Sas. +/* + * Copyright 2017, GeoSolutions Sas. * All rights reserved. * * This source code is licensed under the BSD-style license found in the @@ -8,7 +8,7 @@ var objectAssign = require('object-assign'); var WMSUtils = { - PARAM_OPTIONS: ["layers", "styles", "format", "transparent", "version", "tiled" ], + PARAM_OPTIONS: ["layers", "styles", "format", "transparent", "version", "tiled", "opacity", "zindex" ], wmsToLeafletOptions: function(options) { var opacity = options.opacity !== undefined ? options.opacity : 1; // NOTE: can we use opacity to manage visibility?