diff --git a/web/client/api/WMTS.js b/web/client/api/WMTS.js index 070609ed40..d447a5a3a6 100644 --- a/web/client/api/WMTS.js +++ b/web/client/api/WMTS.js @@ -92,7 +92,10 @@ const Api = { return searchAndPaginate(json, startPosition, maxRecords, text, url); }); }, - getCapabilities: (url) => { + getCapabilities: (url, options) => { + if (options?.force && capabilitiesCache[url]) { + delete capabilitiesCache[url]; + } const cached = capabilitiesCache[url]; if (cached && new Date().getTime() < cached.timestamp + (getConfigProp('cacheExpire') || 60) * 1000) { return new Promise((resolve) => { @@ -121,4 +124,45 @@ const Api = { } }; +/** + * Return tileMatrixSets, tileMatrixSetLinks, tileGrids, styles and formats of from WMTS capabilities for a specific layer + * @param {string} url wmts endpoint url + * @param {string} layerName layer name + * @param {object} options optional configuration + * @param {boolean} options.force if true delete the cache for this url and force the request + * @return {promise} + */ +export const getLayerTileMatrixSetsInfo = (url, layerName = '', options) => { + return Api.getCapabilities(url, { force: options?.force }) + .then((response) => { + const layerParts = layerName.split(':'); + const layers = castArray(response?.Capabilities?.Contents?.Layer || []); + const wmtsLayer = layers.find((layer) => layer['ows:Identifier'] === layerParts[1] || layer['ows:Identifier'] === layerName); + const tileMatrixSetLinks = castArray(wmtsLayer?.TileMatrixSetLink || []).map(({ TileMatrixSet }) => TileMatrixSet); + const tileMatrixSets = castArray(response?.Capabilities?.Contents?.TileMatrixSet || []).filter((tileMatrixSet) => tileMatrixSetLinks.includes(tileMatrixSet['ows:Identifier'])); + const tileGrids = tileMatrixSets.map((tileMatrixSet) => { + const origins = tileMatrixSet.TileMatrix.map((tileMatrixLevel) => tileMatrixLevel.TopLeftCorner.split(' ').map(parseFloat)); + const tileSizes = tileMatrixSet.TileMatrix.map((tileMatrixLevel) => [parseFloat(tileMatrixLevel.TileWidth), parseFloat(tileMatrixLevel.TileHeight)]); + const firstOrigin = origins[0]; + const firsTileSize = tileSizes[0]; + const isSingleOrigin = origins.every(entry => firstOrigin[0] === entry[0] && firstOrigin[1] === entry[1]); + const isSingleTileSize = tileSizes.every(entry => firsTileSize[0] === entry[0] && firsTileSize[1] === entry[1]); + return { + id: tileMatrixSet['ows:Identifier'], + crs: getEPSGCode(tileMatrixSet['ows:SupportedCRS']), + scales: tileMatrixSet.TileMatrix.map((tileMatrixLevel) => parseFloat(tileMatrixLevel.ScaleDenominator)), + ...(isSingleOrigin ? { origin: firstOrigin } : { origins }), + ...(isSingleTileSize ? { tileSize: firsTileSize } : { tileSizes }) + }; + }); + return { + tileMatrixSets, + tileMatrixSetLinks, + tileGrids, + styles: castArray(wmtsLayer?.Style || []).map((style) => style['ows:Identifier']), + formats: castArray(wmtsLayer?.Format || []) + }; + }); +}; + export default Api; diff --git a/web/client/api/__tests__/WMTS-test.js b/web/client/api/__tests__/WMTS-test.js index 1828e9ca85..f28a616e9c 100644 --- a/web/client/api/__tests__/WMTS-test.js +++ b/web/client/api/__tests__/WMTS-test.js @@ -8,8 +8,11 @@ import expect from 'expect'; -import API from '../WMTS'; +import API, { getLayerTileMatrixSetsInfo } from '../WMTS'; import { getGetTileURL } from '../../utils/WMTSUtils'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '../../libs/ajax'; +let mockAxios; describe('Test correctness of the WMTS APIs', () => { it('GetRecords KVP', (done) => { @@ -76,3 +79,175 @@ describe('Test correctness of the WMTS APIs', () => { }); }); }); + +describe('Test correctness of the WMTS APIs (mock axios)', () => { + beforeEach(done => { + mockAxios = new MockAdapter(axios); + setTimeout(done); + }); + + afterEach(done => { + API.reset(); + mockAxios.restore(); + setTimeout(done); + }); + + it('getLayerTileMatrixSetsInfo', (done) => { + mockAxios.onGet(/\/geoserver/).reply(200, ` + + + + USA Population + This is some census data on the states. + + -124.73142200000001 24.955967 + -66.969849 49.371735 + + states + + image/png + image/jpeg + text/plain + application/vnd.ogc.gml + text/xml + application/vnd.ogc.gml/3.1.1 + text/xml + text/html + application/json + + EPSG:32122 + + + EPSG:900913 + + + + EPSG:32122 + urn:ogc:def:crs:EPSG::32122 + + EPSG:32122:0 + 2557541.55271451 + 403035.4105968763 414783.0 + 512 + 512 + 1 + 1 + + + EPSG:32122:1 + 1278770.776357255 + 403035.4105968763 414783.0 + 512 + 512 + 2 + 2 + + + EPSG:32122:2 + 639385.3881786275 + 403035.4105968763 323121.0 + 512 + 512 + 4 + 3 + + + + EPSG:4326 + urn:ogc:def:crs:EPSG::4326 + + EPSG:4326:0 + 2.795411320143589E8 + 90.0 -180.0 + 256 + 256 + 2 + 1 + + + EPSG:4326:1 + 1.3977056600717944E8 + 90.0 -180.0 + 256 + 256 + 4 + 2 + + + EPSG:4326:2 + 6.988528300358972E7 + 90.0 -180.0 + 256 + 256 + 8 + 4 + + + + EPSG:900913 + urn:ogc:def:crs:EPSG::900913 + + EPSG:900913:0 + 5.590822639508929E8 + -2.003750834E7 2.0037508E7 + 256 + 256 + 1 + 1 + + + EPSG:900913:1 + 2.7954113197544646E8 + -2.003750834E7 2.0037508E7 + 256 + 256 + 2 + 2 + + + EPSG:900913:2 + 1.3977056598772323E8 + -2.003750834E7 2.0037508E7 + 256 + 256 + 4 + 4 + + + + + + `); + const url = '/geoserver/topp/states/gwc/service/wmts'; + const layerName = 'topp:states'; + getLayerTileMatrixSetsInfo(url, layerName) + .then((response) => { + expect(response.tileGrids.length).toBe(2); + expect(response.tileGrids).toEqual([ + { + id: 'EPSG:32122', + crs: 'EPSG:32122', + scales: [ 2557541.55271451, 1278770.776357255, 639385.3881786275 ], + origins: [ [ 403035.4105968763, 414783 ], [ 403035.4105968763, 414783 ], [ 403035.4105968763, 323121 ] ], + tileSize: [ 512, 512 ] + }, + { + id: 'EPSG:900913', + crs: 'EPSG:900913', + scales: [ 559082263.9508929, 279541131.97544646, 139770565.98772323 ], + origin: [ -20037508.34, 20037508 ], + tileSize: [ 256, 256 ] + } + ]); + expect(response.styles.length).toBe(1); + expect(response.styles).toEqual(['population']); + expect(response.formats.length).toBe(2); + expect(response.formats).toEqual(['image/png', 'image/jpeg']); + done(); + }) + .catch(done); + }); +}); diff --git a/web/client/components/TOC/fragments/settings/Display.jsx b/web/client/components/TOC/fragments/settings/Display.jsx index 536d1ebd2d..d4f93e06e4 100644 --- a/web/client/components/TOC/fragments/settings/Display.jsx +++ b/web/client/components/TOC/fragments/settings/Display.jsx @@ -9,7 +9,7 @@ import { clamp, isNil, isNumber } from 'lodash'; import PropTypes from 'prop-types'; import React from 'react'; -import {Checkbox, Col, ControlLabel, FormGroup, Glyphicon, Grid, Row, Button as ButtonRB} from 'react-bootstrap'; +import {Checkbox, Col, ControlLabel, FormGroup, Glyphicon, Grid, Row, Button as ButtonRB } from 'react-bootstrap'; import tooltip from '../../../misc/enhancers/buttonTooltip'; const Button = tooltip(ButtonRB); import IntlNumberFormControl from '../../../I18N/IntlNumberFormControl'; @@ -20,6 +20,7 @@ import VisibilityLimitsForm from './VisibilityLimitsForm'; import { ServerTypes } from '../../../../utils/LayersUtils'; import Select from 'react-select'; import { getSupportedFormat } from '../../../../api/WMS'; +import WMSCacheOptions from './WMSCacheOptions'; export default class extends React.Component { static propTypes = { opacityText: PropTypes.node, @@ -225,13 +226,6 @@ export default class extends React.Component { {this.props.onChange("transparent", event.target.checked); }}> - {(this.props.element?.serverType !== ServerTypes.NO_VENDOR && ( - 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)}> @@ -252,6 +246,17 @@ export default class extends React.Component { checked={this.props.element.forceProxy} > )} + {(this.props.element?.serverType !== ServerTypes.NO_VENDOR && ( + <> +
+ + + ))}
diff --git a/web/client/components/TOC/fragments/settings/WMSCacheOptions.jsx b/web/client/components/TOC/fragments/settings/WMSCacheOptions.jsx new file mode 100644 index 0000000000..c36393ad91 --- /dev/null +++ b/web/client/components/TOC/fragments/settings/WMSCacheOptions.jsx @@ -0,0 +1,216 @@ +/* + * Copyright 2023, 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. + */ + +import React, { useState } from 'react'; +import { Checkbox, Glyphicon, Button as ButtonRB, Table, Alert } from 'react-bootstrap'; +import tooltip from '../../../misc/enhancers/buttonTooltip'; +import ToolbarButton from '../../../misc/toolbar/ToolbarButton'; +import Message from '../../../I18N/Message'; +import InfoPopover from '../../../widgets/widget/InfoPopover'; +import { getLayerTileMatrixSetsInfo } from '../../../../api/WMTS'; +import { generateGeoServerWMTSUrl } from '../../../../utils/WMTSUtils'; +import { normalizeSRS } from '../../../../utils/CoordinatesUtils'; +import { isProjectionAvailable } from '../../../../utils/ProjectionUtils'; +import { getTileGridFromLayerOptions } from '../../../../utils/WMSUtils'; + +const Button = tooltip(ButtonRB); + +/** + * Allow to set the cache options for a WMS layer + * @memberof components.TOC + * @name WMSCacheOptions + * @prop {object} layer layer configuration + * @prop {boolean} disableTileGrids disable tile grids toolbar + * @prop {function} onChange callback triggered after changing the form + */ +function WMSCacheOptions({ + layer = {}, + projection, + onChange, + disableTileGrids +}) { + + const [tileGridLoading, setTileGridLoading] = useState(false); + const [tileGridsResponseMsgId, setTileGridsResponseMsgId] = useState(''); + const [tileGridsResponseMsgStyle, setTileGridsResponseMsgStyle] = useState(''); + + const selectedTileGridId = layer.tileGridStrategy === 'custom' && getTileGridFromLayerOptions({ + ...layer, + projection + })?.id; + + const supportFormatCache = !layer.format || !!((layer?.tileGridCacheSupport?.formats || []).includes(layer.format)); + const supportStyleCache = !layer.style || !!((layer?.tileGridCacheSupport?.styles || []).includes(layer.style)); + const hasCustomParams = !!layer.localizedLayerStyles; + const tiled = layer && layer.tiled !== undefined ? layer.tiled : true; + + const requestUrl = generateGeoServerWMTSUrl(layer); + + const onTileMatrixSetsFetch = (options) => { + setTileGridLoading(true); + setTileGridsResponseMsgId(''); + setTileGridsResponseMsgStyle(''); + return getLayerTileMatrixSetsInfo(requestUrl, options.name, options) + .then(({ tileGrids, styles, formats }) => { + const filteredTileGrids = tileGrids.filter(({ crs }) => isProjectionAvailable(normalizeSRS(crs))); + if (filteredTileGrids?.length === 0) { + setTileGridsResponseMsgId('layerProperties.noConfiguredGridSets'); + } + return { + tileGrids: filteredTileGrids, + tileGridCacheSupport: filteredTileGrids?.length > 0 ? { + styles, + formats + } : undefined + }; + }) + .catch(() => { + setTileGridsResponseMsgId('layerProperties.notPossibleToConnectToWMTSService'); + setTileGridsResponseMsgStyle('danger'); + return { }; + }) + // delay the loading phase the show the loader and give a feedback to user + // in particular when the request is cached or too fast + .finally(() => setTimeout(() => setTileGridLoading(false), 500)); + }; + + return ( +
+
+ onChange('tiled', e.target.checked)} + checked={tiled} > + + + {requestUrl && !disableTileGrids &&
+ {(layer.tileGridStrategy === 'custom' && layer.tileGrids && tiled && !layer.singleTile) && } + popoverStyle={{ maxWidth: 'none' }} + text={ + <> + + + + + + + + + + {layer?.tileGrids?.map((tileGrid) => { + const size = (tileGrid.tileSize || tileGrid.tileSizes[0] || [])[0]; + const markClassName = supportFormatCache && supportFormatCache ? 'bg-success' : ''; + return ( + + {tileGrid.id === selectedTileGridId + ? <> + + + + + : !selectedTileGridId + ? <> + + + + + : <> + + + + } + + ); + })} + +
{tileGrid.id}{tileGrid.crs}{size}{tileGrid.id}{normalizeSRS(tileGrid.crs) === normalizeSRS(projection) ? {tileGrid.crs} : tileGrid.crs}{size}{tileGrid.id}{tileGrid.crs}{size}
+
+ {(selectedTileGridId && supportFormatCache && supportStyleCache) && + + } + {!selectedTileGridId + ? + + + : (!supportFormatCache || !supportStyleCache) + ? ( + + {!supportFormatCache && } + {!supportStyleCache && } + + ) + : null} + {hasCustomParams && + + } +
+ + } + />} + {layer.tileGridStrategy === 'custom' && } + { + const newTileGridStrategy = layer.tileGridStrategy !== 'custom' + ? 'custom' + : undefined; + const promise = newTileGridStrategy === 'custom' + && ((layer?.tileGrids?.length || 0) === 0 || !layer?.tileGridCacheSupport) + ? onTileMatrixSetsFetch(layer) + : Promise.resolve(undefined); + return promise.then(({ tileGrids, tileGridCacheSupport } = {}) => { + const hasTileGrids = (tileGrids?.length || 0) > 0; + const tileGridStrategy = hasTileGrids + ? newTileGridStrategy + : undefined; + onChange({ + tileGridCacheSupport, + tileGridStrategy, + tileGrids + }); + }); + }} + /> +
} +
+ {!layer.singleTile && tiled && tileGridsResponseMsgId && + + } +
+ ); +} + +export default WMSCacheOptions; diff --git a/web/client/components/TOC/fragments/settings/__tests__/WMSCacheOptions-test.jsx b/web/client/components/TOC/fragments/settings/__tests__/WMSCacheOptions-test.jsx new file mode 100644 index 0000000000..1068b05674 --- /dev/null +++ b/web/client/components/TOC/fragments/settings/__tests__/WMSCacheOptions-test.jsx @@ -0,0 +1,796 @@ +/* + * Copyright 2023, 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. + */ + +import expect from 'expect'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Simulate } from 'react-dom/test-utils'; +import WMSCacheOptions from '../WMSCacheOptions'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '../../../../../libs/ajax'; +import WMTSAPI from '../../../../../api/WMTS'; +let mockAxios; + +describe('WMSCacheOptions', () => { + beforeEach((done) => { + mockAxios = new MockAdapter(axios); + document.body.innerHTML = '
'; + setTimeout(done); + }); + afterEach((done) => { + WMTSAPI.reset(); + mockAxios.restore(); + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(, document.getElementById('container')); + expect(document.querySelector('.ms-wms-cache-options')).toBeTruthy(); + const inputs = document.querySelectorAll('input[type="checkbox"]'); + expect(inputs.length).toBe(1); + const buttons = document.querySelectorAll('button'); + expect(buttons.length).toBe(0); + }); + it('should not display the tile grid button if geoserver wms and disableTileGrids is true', () => { + ReactDOM.render(, document.getElementById('container')); + expect(document.querySelector('.ms-wms-cache-options')).toBeTruthy(); + const inputs = document.querySelectorAll('input[type="checkbox"]'); + expect(inputs.length).toBe(1); + const buttons = document.querySelectorAll('button'); + expect(buttons.length).toBe(0); + }); + it('should display the tile grid button if geoserver wms', () => { + ReactDOM.render(, document.getElementById('container')); + expect(document.querySelector('.ms-wms-cache-options')).toBeTruthy(); + const inputs = document.querySelectorAll('input[type="checkbox"]'); + expect(inputs.length).toBe(1); + const buttons = document.querySelectorAll('button'); + expect(buttons.length).toBe(1); + expect([...buttons].map(button => button.querySelector('.glyphicon').getAttribute('class'))).toEqual([ + 'glyphicon glyphicon-grid-regular' + ]); + }); + it('should display the tile grid buttons if geoserver wms and tile grid strategy is custom (match with map projection)', () => { + ReactDOM.render(, document.getElementById('container')); + expect(document.querySelector('.ms-wms-cache-options')).toBeTruthy(); + const inputs = document.querySelectorAll('input[type="checkbox"]'); + expect(inputs.length).toBe(1); + const buttons = document.querySelectorAll('button'); + expect(buttons.length).toBe(2); + expect([...buttons].map(button => button.querySelector('.glyphicon').getAttribute('class'))).toEqual([ + 'glyphicon glyphicon-refresh', + 'glyphicon glyphicon-grid-custom' + ]); + const info = document.querySelector('.glyphicon-info-sign'); + expect(info).toBeTruthy(); + expect(info.getAttribute('class')).toBe('text-success glyphicon glyphicon-info-sign'); + + let table = document.querySelector('table'); + expect(table).toBeFalsy(); + + Simulate.mouseOver(info); + + table = document.querySelector('table'); + expect(table).toBeTruthy(); + + const tableRows = table.querySelectorAll('tbody > tr'); + expect([...tableRows].map((row) => row.innerText)).toEqual([ + 'EPSG:32122x2\tEPSG:32122\t512', + 'EPSG:900913\tEPSG:900913\t256' + ]); + const alert = document.querySelector('.alert'); + expect(alert.innerText).toBe('layerProperties.tileGridInUse'); + }); + it('should display the tile grid buttons if geoserver wms and tile grid strategy is custom (no match with map projection)', () => { + ReactDOM.render(, document.getElementById('container')); + expect(document.querySelector('.ms-wms-cache-options')).toBeTruthy(); + const inputs = document.querySelectorAll('input[type="checkbox"]'); + expect(inputs.length).toBe(1); + const buttons = document.querySelectorAll('button'); + expect(buttons.length).toBe(2); + expect([...buttons].map(button => button.querySelector('.glyphicon').getAttribute('class'))).toEqual([ + 'glyphicon glyphicon-refresh', + 'glyphicon glyphicon-grid-custom' + ]); + const info = document.querySelector('.glyphicon-info-sign'); + expect(info).toBeTruthy(); + expect(info.getAttribute('class')).toBe('text-danger glyphicon glyphicon-info-sign'); + + let table = document.querySelector('table'); + expect(table).toBeFalsy(); + + Simulate.mouseOver(info); + + table = document.querySelector('table'); + expect(table).toBeTruthy(); + + const tableRows = table.querySelectorAll('tbody > tr'); + expect([...tableRows].map((row) => row.innerText)).toEqual([ + 'EPSG:32122x2\tEPSG:32122\t512', + 'EPSG:900913\tEPSG:900913\t256' + ]); + const alert = document.querySelector('.alert'); + expect(alert.innerText).toBe('layerProperties.noTileGridMatchesConfiguration'); + }); + it('should request tile grids (success)', (done) => { + mockAxios.onGet().reply(200, ` + + + + USA Population + This is some census data on the states. + + -124.73142200000001 24.955967 + -66.969849 49.371735 + + states + + image/png + image/jpeg + text/plain + application/vnd.ogc.gml + text/xml + application/vnd.ogc.gml/3.1.1 + text/xml + text/html + application/json + + EPSG:32122 + + + EPSG:4326 + + + EPSG:900913 + + + + EPSG:32122 + urn:ogc:def:crs:EPSG::32122 + + EPSG:32122:0 + 2557541.55271451 + 403035.4105968763 414783.0 + 512 + 512 + 1 + 1 + + + EPSG:32122:1 + 1278770.776357255 + 403035.4105968763 414783.0 + 512 + 512 + 2 + 2 + + + EPSG:32122:2 + 639385.3881786275 + 403035.4105968763 323121.0 + 512 + 512 + 4 + 3 + + + + EPSG:4326 + urn:ogc:def:crs:EPSG::4326 + + EPSG:4326:0 + 2.795411320143589E8 + 90.0 -180.0 + 256 + 256 + 2 + 1 + + + EPSG:4326:1 + 1.3977056600717944E8 + 90.0 -180.0 + 256 + 256 + 4 + 2 + + + EPSG:4326:2 + 6.988528300358972E7 + 90.0 -180.0 + 256 + 256 + 8 + 4 + + + + EPSG:900913 + urn:ogc:def:crs:EPSG::900913 + + EPSG:900913:0 + 5.590822639508929E8 + -2.003750834E7 2.0037508E7 + 256 + 256 + 1 + 1 + + + EPSG:900913:1 + 2.7954113197544646E8 + -2.003750834E7 2.0037508E7 + 256 + 256 + 2 + 2 + + + EPSG:900913:2 + 1.3977056598772323E8 + -2.003750834E7 2.0037508E7 + 256 + 256 + 4 + 4 + + + + + + `); + ReactDOM.render( { + try { + expect(options).toEqual({ + tileGridStrategy: 'custom', + tileGrids: [{ + id: 'EPSG:4326', + crs: 'EPSG:4326', + scales: [279541132.0143589, 139770566.00717944, 69885283.00358972], + origin: [90, -180], + tileSize: [256, 256] + }, + { + id: 'EPSG:900913', + crs: 'EPSG:900913', + scales: [559082263.9508929, 279541131.97544646, 139770565.98772323], + origin: [-20037508.34, 20037508], + tileSize: [256, 256] + }], + tileGridCacheSupport: { + styles: ['population'], + formats: ['image/png', 'image/jpeg'] + } + }); + } catch (e) { + done(e); + } + done(); + }} + />, document.getElementById('container')); + expect(document.querySelector('.ms-wms-cache-options')).toBeTruthy(); + const inputs = document.querySelectorAll('input[type="checkbox"]'); + expect(inputs.length).toBe(1); + const buttons = document.querySelectorAll('button'); + expect(buttons.length).toBe(1); + expect([...buttons].map(button => button.querySelector('.glyphicon').getAttribute('class'))).toEqual([ + 'glyphicon glyphicon-grid-regular' + ]); + + Simulate.click(buttons[0]); + + }); + it('should request tile grids (warning no available tile grids)', (done) => { + mockAxios.onGet().reply(200, ` + + + + USA Population + This is some census data on the states. + + -124.73142200000001 24.955967 + -66.969849 49.371735 + + states + + image/png + image/jpeg + text/plain + application/vnd.ogc.gml + text/xml + application/vnd.ogc.gml/3.1.1 + text/xml + text/html + application/json + + EPSG:32122 + + + + EPSG:32122 + urn:ogc:def:crs:EPSG::32122 + + EPSG:32122:0 + 2557541.55271451 + 403035.4105968763 414783.0 + 512 + 512 + 1 + 1 + + + EPSG:32122:1 + 1278770.776357255 + 403035.4105968763 414783.0 + 512 + 512 + 2 + 2 + + + EPSG:32122:2 + 639385.3881786275 + 403035.4105968763 323121.0 + 512 + 512 + 4 + 3 + + + + + + `); + ReactDOM.render( { + try { + expect(options).toEqual({ tileGridStrategy: undefined, tileGrids: [], tileGridCacheSupport: undefined }); + const alert = document.querySelector('.alert'); + expect(alert.innerText).toBe('layerProperties.noConfiguredGridSets'); + } catch (e) { + done(e); + } + done(); + }} + />, document.getElementById('container')); + expect(document.querySelector('.ms-wms-cache-options')).toBeTruthy(); + const inputs = document.querySelectorAll('input[type="checkbox"]'); + expect(inputs.length).toBe(1); + const buttons = document.querySelectorAll('button'); + expect(buttons.length).toBe(1); + expect([...buttons].map(button => button.querySelector('.glyphicon').getAttribute('class'))).toEqual([ + 'glyphicon glyphicon-grid-regular' + ]); + + Simulate.click(buttons[0]); + + }); + it('should request tile grids (error)', (done) => { + mockAxios.onGet().reply(500); + ReactDOM.render( { + try { + expect(options).toEqual({ + tileGridStrategy: undefined, + tileGrids: undefined, + tileGridCacheSupport: undefined + }); + const alert = document.querySelector('.alert'); + expect(alert.innerText).toBe('layerProperties.notPossibleToConnectToWMTSService'); + } catch (e) { + done(e); + } + done(); + }} + />, document.getElementById('container')); + expect(document.querySelector('.ms-wms-cache-options')).toBeTruthy(); + const inputs = document.querySelectorAll('input[type="checkbox"]'); + expect(inputs.length).toBe(1); + const buttons = document.querySelectorAll('button'); + expect(buttons.length).toBe(1); + expect([...buttons].map(button => button.querySelector('.glyphicon').getAttribute('class'))).toEqual([ + 'glyphicon glyphicon-grid-regular' + ]); + + Simulate.click(buttons[0]); + }); + it('should refresh the tile grids and trigger the onChange', (done) => { + mockAxios.onGet().reply(200, ` + + + + USA Population + This is some census data on the states. + + -124.73142200000001 24.955967 + -66.969849 49.371735 + + states + + image/png + image/jpeg + text/plain + application/vnd.ogc.gml + text/xml + application/vnd.ogc.gml/3.1.1 + text/xml + text/html + application/json + + EPSG:900913 + + + + EPSG:900913 + urn:ogc:def:crs:EPSG::900913 + + EPSG:900913:0 + 5.590822639508929E8 + -2.003750834E7 2.0037508E7 + 256 + 256 + 1 + 1 + + + EPSG:900913:1 + 2.7954113197544646E8 + -2.003750834E7 2.0037508E7 + 256 + 256 + 2 + 2 + + + EPSG:900913:2 + 1.3977056598772323E8 + -2.003750834E7 2.0037508E7 + 256 + 256 + 4 + 4 + + + + + + `); + ReactDOM.render( { + try { + expect(options).toEqual({ + tileGrids: [{ + id: 'EPSG:900913', + crs: 'EPSG:900913', + scales: [559082263.9508929, 279541131.97544646, 139770565.98772323], + origin: [-20037508.34, 20037508], + tileSize: [256, 256] + }], + tileGridCacheSupport: { + styles: ['population'], + formats: ['image/png', 'image/jpeg'] + } + }); + } catch (e) { + done(e); + } + done(); + }} + />, document.getElementById('container')); + expect(document.querySelector('.ms-wms-cache-options')).toBeTruthy(); + const inputs = document.querySelectorAll('input[type="checkbox"]'); + expect(inputs.length).toBe(1); + const buttons = document.querySelectorAll('button'); + expect(buttons.length).toBe(2); + expect([...buttons].map(button => button.querySelector('.glyphicon').getAttribute('class'))).toEqual([ + 'glyphicon glyphicon-refresh', + 'glyphicon glyphicon-grid-custom' + ]); + + Simulate.click(buttons[0]); + + }); + it('should display the format cache warning if not listed in the supported ones', () => { + ReactDOM.render(, document.getElementById('container')); + expect(document.querySelector('.ms-wms-cache-options')).toBeTruthy(); + const inputs = document.querySelectorAll('input[type="checkbox"]'); + expect(inputs.length).toBe(1); + const buttons = document.querySelectorAll('button'); + expect(buttons.length).toBe(2); + expect([...buttons].map(button => button.querySelector('.glyphicon').getAttribute('class'))).toEqual([ + 'glyphicon glyphicon-refresh', + 'glyphicon glyphicon-grid-custom' + ]); + const info = document.querySelector('.glyphicon-info-sign'); + expect(info).toBeTruthy(); + expect(info.getAttribute('class')).toBe('text-danger glyphicon glyphicon-info-sign'); + + let table = document.querySelector('table'); + expect(table).toBeFalsy(); + + Simulate.mouseOver(info); + + table = document.querySelector('table'); + expect(table).toBeTruthy(); + + const tableRows = table.querySelectorAll('tbody > tr'); + expect([...tableRows].map((row) => row.innerText)).toEqual([ + 'EPSG:32122x2\tEPSG:32122\t512', + 'EPSG:900913\tEPSG:900913\t256' + ]); + const alert = document.querySelector('.alert'); + expect(alert.innerText).toBe('layerProperties.notSupportedSelectedFormatCache'); + }); + it('should display the style cache warning if not listed in the supported ones', () => { + ReactDOM.render(, document.getElementById('container')); + expect(document.querySelector('.ms-wms-cache-options')).toBeTruthy(); + const inputs = document.querySelectorAll('input[type="checkbox"]'); + expect(inputs.length).toBe(1); + const buttons = document.querySelectorAll('button'); + expect(buttons.length).toBe(2); + expect([...buttons].map(button => button.querySelector('.glyphicon').getAttribute('class'))).toEqual([ + 'glyphicon glyphicon-refresh', + 'glyphicon glyphicon-grid-custom' + ]); + const info = document.querySelector('.glyphicon-info-sign'); + expect(info).toBeTruthy(); + expect(info.getAttribute('class')).toBe('text-danger glyphicon glyphicon-info-sign'); + + let table = document.querySelector('table'); + expect(table).toBeFalsy(); + + Simulate.mouseOver(info); + + table = document.querySelector('table'); + expect(table).toBeTruthy(); + + const tableRows = table.querySelectorAll('tbody > tr'); + expect([...tableRows].map((row) => row.innerText)).toEqual([ + 'EPSG:32122x2\tEPSG:32122\t512', + 'EPSG:900913\tEPSG:900913\t256' + ]); + const alert = document.querySelector('.alert'); + expect(alert.innerText).toBe('layerProperties.notSupportedSelectedStyleCache'); + }); + it('should display the custom param cache warning if localized style is enabled', () => { + ReactDOM.render(, document.getElementById('container')); + expect(document.querySelector('.ms-wms-cache-options')).toBeTruthy(); + const inputs = document.querySelectorAll('input[type="checkbox"]'); + expect(inputs.length).toBe(1); + const buttons = document.querySelectorAll('button'); + expect(buttons.length).toBe(2); + expect([...buttons].map(button => button.querySelector('.glyphicon').getAttribute('class'))).toEqual([ + 'glyphicon glyphicon-refresh', + 'glyphicon glyphicon-grid-custom' + ]); + const info = document.querySelector('.glyphicon-info-sign'); + expect(info).toBeTruthy(); + expect(info.getAttribute('class')).toBe('text-success glyphicon glyphicon-info-sign'); + + let table = document.querySelector('table'); + expect(table).toBeFalsy(); + + Simulate.mouseOver(info); + + table = document.querySelector('table'); + expect(table).toBeTruthy(); + + const tableRows = table.querySelectorAll('tbody > tr'); + expect([...tableRows].map((row) => row.innerText)).toEqual([ + 'EPSG:32122x2\tEPSG:32122\t512', + 'EPSG:900913\tEPSG:900913\t256' + ]); + const alert = [...document.querySelectorAll('.alert')]; + expect(alert[0].innerText).toBe('layerProperties.tileGridInUse'); + expect(alert[1].innerText).toBe('layerProperties.customParamsCacheWarning'); + }); + it('should allow to click the grid button to switch to normal grid', (done) => { + ReactDOM.render( { + try { + expect(options).toEqual({ + tileGridStrategy: undefined, + tileGrids: undefined, + tileGridCacheSupport: undefined + }); + } catch (e) { + done(e); + } + done(); + }} + layer={{ + url: '/geoserver/wms', + name: 'topp:states', + tileGridStrategy: 'custom', + tileGrids: [ + { + id: 'EPSG:32122x2', + crs: 'EPSG:32122', + scales: [2557541.55271451, 1278770.776357255, 639385.3881786275], + origins: [[403035.4105968763, 414783], [403035.4105968763, 414783], [403035.4105968763, 323121]], + tileSize: [512, 512] + }, + { + id: 'EPSG:900913', + crs: 'EPSG:900913', + scales: [559082263.9508929, 279541131.97544646, 139770565.98772323], + origin: [-20037508.34, 20037508], + tileSize: [256, 256] + } + ] + }} projection="EPSG:3857" />, document.getElementById('container')); + expect(document.querySelector('.ms-wms-cache-options')).toBeTruthy(); + const inputs = document.querySelectorAll('input[type="checkbox"]'); + expect(inputs.length).toBe(1); + const buttons = document.querySelectorAll('button'); + expect(buttons.length).toBe(2); + expect([...buttons].map(button => button.querySelector('.glyphicon').getAttribute('class'))).toEqual([ + 'glyphicon glyphicon-refresh', + 'glyphicon glyphicon-grid-custom' + ]); + Simulate.click(buttons[1]); + + }); +}); + diff --git a/web/client/components/map/openlayers/__tests__/Map-test.jsx b/web/client/components/map/openlayers/__tests__/Map-test.jsx index 079285a8e7..c252bab5f6 100644 --- a/web/client/components/map/openlayers/__tests__/Map-test.jsx +++ b/web/client/components/map/openlayers/__tests__/Map-test.jsx @@ -1379,6 +1379,64 @@ describe('OpenlayersMap', () => { expect(map.map.getLayers().getLength()).toBe(1); expect(map.map.getLayers().getArray()[0].getSource().getTileGrid().getResolutions().length).toBe(31); }); + it('should use tile grid resolutions based on custom strategy and not the map resolutions', () => { + const options = { + url: '/geoserver/wms', + name: 'workspace:layer', + visibility: true, + tileGridStrategy: 'custom', + tileSize: 256, + tileGrids: [ + { + id: 'EPSG:4326', + crs: 'EPSG:4326', + scales: [ 279541132.0143589, 139770566.00717944, 69885283.00358972 ], + origin: [ 90, -180 ], + tileSize: [ 256, 256 ] + }, + { + id: 'EPSG:900913', + crs: 'EPSG:900913', + scales: [ 559082263.9508929, 279541131.97544646, 139770565.98772323 ], + origin: [ -20037508.34, 20037508 ], + tileSize: [ 256, 256 ] + } + ] + }; + const resolutions = [ + 529.1666666666666, + 317.5, + 158.75, + 79.375, + 26.458333333333332, + 19.84375, + 10.583333333333332, + 5.291666666666666, + 2.645833333333333, + 1.3229166666666665, + 0.6614583333333333, + 0.396875, + 0.13229166666666667, + 0.079375, + 0.0396875, + 0.021166666666666667 + ]; + const map = ReactDOM.render( + + + , document.getElementById("map") + ); + expect(map).toBeTruthy(); + expect(map.map.getView().getResolutions().length).toBe(16); + expect(map.map.getView().getResolutions().length).toBe(resolutions.length); + expect(map.map.getLayers().getLength()).toBe(1); + expect(map.map.getLayers().getArray()[0].getSource().getTileGrid().getResolutions().length).toBe(3); + expect(map.map.getLayers().getArray()[0].getSource().getTileGrid().getOrigin()).toEqual(options.tileGrids[1].origin); + }); describe("hookRegister", () => { it("default", () => { const map = ReactDOM.render(, document.getElementById("map")); diff --git a/web/client/components/map/openlayers/plugins/WMSLayer.js b/web/client/components/map/openlayers/plugins/WMSLayer.js index 7a682508e6..aa16c7502d 100644 --- a/web/client/components/map/openlayers/plugins/WMSLayer.js +++ b/web/client/components/map/openlayers/plugins/WMSLayer.js @@ -39,6 +39,7 @@ import VectorTileLayer from 'ol/layer/VectorTile'; import { isVectorFormat } from '../../../../utils/VectorTileUtils'; import { OL_VECTOR_FORMATS, applyStyle } from '../../../../utils/openlayers/VectorTileUtils'; import { generateEnvString } from '../../../../utils/LayerLocalizationUtils'; +import { getTileGridFromLayerOptions } from '../../../../utils/WMSUtils'; /** * Check source and apply proxy @@ -193,8 +194,39 @@ const toOLAttributions = credits => credits && creditsToAttribution(credits) || const generateTileGrid = (options, map) => { const mapSrs = map?.getView()?.getProjection()?.getCode() || 'EPSG:3857'; const normalizedSrs = CoordinatesUtils.normalizeSRS(options.srs || mapSrs, options.allowedSRS); - const extent = get(normalizedSrs).getExtent() || getProjection(normalizedSrs).extent; const tileSize = options.tileSize ? options.tileSize : 256; + const extent = get(normalizedSrs).getExtent() || getProjection(normalizedSrs).extent; + const { TILED } = getWMSVendorParams(options); + const customTileGrid = TILED && options.tileGridStrategy === 'custom' && options.tileGrids + ? getTileGridFromLayerOptions({ tileSize, projection: normalizedSrs, tileGrids: options.tileGrids }) + : null; + if (customTileGrid + && (customTileGrid.resolutions || customTileGrid.scales) + && (customTileGrid.origins || customTileGrid.origin) + && (customTileGrid.tileSizes || customTileGrid.tileSize)) { + const { + resolutions: customTileGridResolutions, + scales, + origin, + origins, + tileSize: customTileGridTileSize, + tileSizes + } = customTileGrid; + const projection = get(normalizedSrs); + const metersPerUnit = projection.getMetersPerUnit(); + const scaleToResolution = s => s * 0.28E-3 / metersPerUnit; + const resolutions = customTileGridResolutions + ? customTileGridResolutions + : scales.map(scale => scaleToResolution(scale)); + return new TileGrid({ + extent, + resolutions, + tileSizes, + tileSize: customTileGridTileSize, + origin, + origins + }); + } const resolutions = options.resolutions || getResolutionsForProjection(normalizedSrs, { tileWidth: tileSize, tileHeight: tileSize, @@ -296,6 +328,8 @@ const mustCreateNewLayer = (oldOptions, newOptions) => { || oldOptions.localizedLayerStyles !== newOptions.localizedLayerStyles || oldOptions.tileSize !== newOptions.tileSize || oldOptions.forceProxy !== newOptions.forceProxy + || oldOptions.tileGridStrategy !== newOptions.tileGridStrategy + || !isEqual(oldOptions.tileGrids, newOptions.tileGrids) ); }; diff --git a/web/client/components/widgets/widget/InfoPopover.jsx b/web/client/components/widgets/widget/InfoPopover.jsx index a4259d6b5c..6f36461304 100644 --- a/web/client/components/widgets/widget/InfoPopover.jsx +++ b/web/client/components/widgets/widget/InfoPopover.jsx @@ -21,6 +21,7 @@ import OverlayTrigger from '../../misc/OverlayTrigger'; * @prop {number} left left prop of popover * @prop {number} right right prop of popover * @prop {string} placement position of popover + * @prop {object} popoverStyle style for popover wrapper * @prop {boolean|String[]} trigger ['hover', 'focus'] by default. false always show the popover. Array with hover, focus and/or click string to specify events that trigger popover to show. */ class InfoPopover extends React.Component { @@ -34,7 +35,8 @@ class InfoPopover extends React.Component { placement: PropTypes.string, left: PropTypes.number, top: PropTypes.number, - trigger: PropTypes.oneOfType([PropTypes.array, PropTypes.bool]) + trigger: PropTypes.oneOfType([PropTypes.array, PropTypes.bool]), + popoverStyle: PropTypes.object }; static defaultProps = { @@ -56,7 +58,8 @@ class InfoPopover extends React.Component { placement={this.props.placement} positionLeft={this.props.left} positionTop={this.props.top} - title={this.props.title}> + title={this.props.title} + style={this.props.popoverStyle}> {this.props.text} ); diff --git a/web/client/epics/__tests__/layerinfo-test.js b/web/client/epics/__tests__/layerinfo-test.js index fa449d37dc..2c6e92329c 100644 --- a/web/client/epics/__tests__/layerinfo-test.js +++ b/web/client/epics/__tests__/layerinfo-test.js @@ -45,7 +45,7 @@ const cswRecord = ` `; -const wmsCapabilities = ` +const wmtsCapabilities = ` @@ -225,7 +225,7 @@ describe('layerinfo epics', () => { }]; mockAxios.onGet(/\/layer1catalog.*/).reply(200, cswRecord); - mockAxios.onGet(/\/layer2url.*/).reply(200, wmsCapabilities); + mockAxios.onGet(/\/layer2url.*/).reply(200, wmtsCapabilities); testEpic(layerInfoSyncLayersEpic, 15, syncLayers([testLayers[0], testLayers[1]]), actions => { expect(actions[0].type).toBe(RESET_SYNC_STATUS); diff --git a/web/client/themes/default/icons.less b/web/client/themes/default/icons.less index d75d783305..89e00c5c34 100644 --- a/web/client/themes/default/icons.less +++ b/web/client/themes/default/icons.less @@ -200,197 +200,199 @@ .glyphicon-globe:before { content: "\f19e"; } .glyphicon-grab-handle:before { content: "\f19f"; } .glyphicon-gray-scale:before { content: "\f1a0"; } -.glyphicon-hand-down:before { content: "\f1a1"; } -.glyphicon-hand-left:before { content: "\f1a2"; } -.glyphicon-hand-right:before { content: "\f1a3"; } -.glyphicon-hand-up:before { content: "\f1a4"; } -.glyphicon-hdd:before { content: "\f1a5"; } -.glyphicon-heart:before { content: "\f1a6"; } -.glyphicon-height-auto:before { content: "\f1a7"; } -.glyphicon-height-from-terrain:before { content: "\f1a8"; } -.glyphicon-height-view:before { content: "\f1a9"; } -.glyphicon-hide-marker:before { content: "\f1aa"; } -.glyphicon-home:before { content: "\f1ab"; } -.glyphicon-hourglass:before { content: "\f1ac"; } -.glyphicon-import:before { content: "\f1ad"; } -.glyphicon-inbox:before { content: "\f1ae"; } -.glyphicon-info-sign:before { content: "\f1af"; } -.glyphicon-italic:before { content: "\f1b0"; } -.glyphicon-layer-info:before { content: "\f1b1"; } -.glyphicon-leaf:before { content: "\f1b2"; } -.glyphicon-level-up:before { content: "\f1b3"; } -.glyphicon-line-dash:before { content: "\f1b4"; } -.glyphicon-line-minus:before { content: "\f1b5"; } -.glyphicon-line-plus:before { content: "\f1b6"; } -.glyphicon-line-remove:before { content: "\f1b7"; } -.glyphicon-line-trash:before { content: "\f1b8"; } -.glyphicon-line:before { content: "\f1b9"; } -.glyphicon-link:before { content: "\f1ba"; } -.glyphicon-list-alt:before { content: "\f1bb"; } -.glyphicon-list:before { content: "\f1bc"; } -.glyphicon-lock:before { content: "\f1bd"; } -.glyphicon-log-in:before { content: "\f1be"; } -.glyphicon-log-out:before { content: "\f1bf"; } -.glyphicon-loop:before { content: "\f1c0"; } -.glyphicon-magnet:before { content: "\f1c1"; } -.glyphicon-map-context:before { content: "\f1c2"; } -.glyphicon-map-edit:before { content: "\f1c3"; } -.glyphicon-map-filter:before { content: "\f1c4"; } -.glyphicon-map-marker:before { content: "\f1c5"; } -.glyphicon-map-synch:before { content: "\f1c6"; } -.glyphicon-map-view:before { content: "\f1c7"; } -.glyphicon-maps-catalog:before { content: "\f1c8"; } -.glyphicon-menu-hamburger:before { content: "\f1c9"; } -.glyphicon-minus-sign:before { content: "\f1ca"; } -.glyphicon-minus:before { content: "\f1cb"; } -.glyphicon-model-plus:before { content: "\f1cc"; } -.glyphicon-model:before { content: "\f1cd"; } -.glyphicon-mouse:before { content: "\f1ce"; } -.glyphicon-move-row-after:before { content: "\f1cf"; } -.glyphicon-move-row-before:before { content: "\f1d0"; } -.glyphicon-move:before { content: "\f1d1"; } -.glyphicon-muted:before { content: "\f1d2"; } -.glyphicon-new-window:before { content: "\f1d3"; } -.glyphicon-next:before { content: "\f1d4"; } -.glyphicon-off:before { content: "\f1d5"; } -.glyphicon-ok-circle:before { content: "\f1d6"; } -.glyphicon-ok-sign:before { content: "\f1d7"; } -.glyphicon-ok:before { content: "\f1d8"; } -.glyphicon-open:before { content: "\f1d9"; } -.glyphicon-option-horizontal:before { content: "\f1da"; } -.glyphicon-option-vertical:before { content: "\f1db"; } -.glyphicon-paperclip:before { content: "\f1dc"; } -.glyphicon-paste:before { content: "\f1dd"; } -.glyphicon-pause:before { content: "\f1de"; } -.glyphicon-pencil-add:before { content: "\f1df"; } -.glyphicon-pencil-edit:before { content: "\f1e0"; } -.glyphicon-pencil:before { content: "\f1e1"; } -.glyphicon-phone:before { content: "\f1e2"; } -.glyphicon-picture:before { content: "\f1e3"; } -.glyphicon-pie-chart:before { content: "\f1e4"; } -.glyphicon-plane:before { content: "\f1e5"; } -.glyphicon-play-circle:before { content: "\f1e6"; } -.glyphicon-play:before { content: "\f1e7"; } -.glyphicon-playback:before { content: "\f1e8"; } -.glyphicon-plug:before { content: "\f1e9"; } -.glyphicon-plus-sign:before { content: "\f1ea"; } -.glyphicon-plus-square:before { content: "\f1eb"; } -.glyphicon-plus:before { content: "\f1ec"; } -.glyphicon-point-coordinates:before { content: "\f1ed"; } -.glyphicon-point-dash:before { content: "\f1ee"; } -.glyphicon-point-minus:before { content: "\f1ef"; } -.glyphicon-point-plus:before { content: "\f1f0"; } -.glyphicon-point-remove:before { content: "\f1f1"; } -.glyphicon-point-trash:before { content: "\f1f2"; } -.glyphicon-point:before { content: "\f1f3"; } -.glyphicon-polygon-3d:before { content: "\f1f4"; } -.glyphicon-polygon-dash:before { content: "\f1f5"; } -.glyphicon-polygon-minus:before { content: "\f1f6"; } -.glyphicon-polygon-plus:before { content: "\f1f7"; } -.glyphicon-polygon-remove:before { content: "\f1f8"; } -.glyphicon-polygon-trash:before { content: "\f1f9"; } -.glyphicon-polygon:before { content: "\f1fa"; } -.glyphicon-polyline-3d:before { content: "\f1fb"; } -.glyphicon-polyline-dash:before { content: "\f1fc"; } -.glyphicon-polyline-minus:before { content: "\f1fd"; } -.glyphicon-polyline-plus:before { content: "\f1fe"; } -.glyphicon-polyline-remove:before { content: "\f1ff"; } -.glyphicon-polyline-trash:before { content: "\f200"; } -.glyphicon-polyline:before { content: "\f201"; } -.glyphicon-preview:before { content: "\f202"; } -.glyphicon-print:before { content: "\f203"; } -.glyphicon-pushpin:before { content: "\f204"; } -.glyphicon-qrcode:before { content: "\f205"; } -.glyphicon-question-sign:before { content: "\f206"; } -.glyphicon-random:before { content: "\f207"; } -.glyphicon-range-end:before { content: "\f208"; } -.glyphicon-range-start:before { content: "\f209"; } -.glyphicon-record:before { content: "\f20a"; } -.glyphicon-redo:before { content: "\f20b"; } -.glyphicon-refresh:before { content: "\f20c"; } -.glyphicon-remove-circle:before { content: "\f20d"; } -.glyphicon-remove-sign:before { content: "\f20e"; } -.glyphicon-remove-square:before { content: "\f20f"; } -.glyphicon-remove:before { content: "\f210"; } -.glyphicon-repeat:before { content: "\f211"; } -.glyphicon-resize-full:before { content: "\f212"; } -.glyphicon-resize-horizontal:before { content: "\f213"; } -.glyphicon-resize-small:before { content: "\f214"; } -.glyphicon-resize-vertical:before { content: "\f215"; } -.glyphicon-retweet:before { content: "\f216"; } -.glyphicon-rgb:before { content: "\f217"; } -.glyphicon-road:before { content: "\f218"; } -.glyphicon-row-add:before { content: "\f219"; } -.glyphicon-row-trash:before { content: "\f21a"; } -.glyphicon-save:before { content: "\f21b"; } -.glyphicon-saved:before { content: "\f21c"; } -.glyphicon-scissors:before { content: "\f21d"; } -.glyphicon-screenshot:before { content: "\f21e"; } -.glyphicon-search-coords:before { content: "\f21f"; } -.glyphicon-search:before { content: "\f220"; } -.glyphicon-send:before { content: "\f221"; } -.glyphicon-share-alt:before { content: "\f222"; } -.glyphicon-share:before { content: "\f223"; } -.glyphicon-sheet:before { content: "\f224"; } -.glyphicon-shopping-cart:before { content: "\f225"; } -.glyphicon-signal:before { content: "\f226"; } -.glyphicon-size-extra-large:before { content: "\f227"; } -.glyphicon-size-large:before { content: "\f228"; } -.glyphicon-size-medium:before { content: "\f229"; } -.glyphicon-size-small:before { content: "\f22a"; } -.glyphicon-slope:before { content: "\f22b"; } -.glyphicon-sort-by-alphabet-alt:before { content: "\f22c"; } -.glyphicon-sort-by-alphabet:before { content: "\f22d"; } -.glyphicon-sort-by-attributes-alt:before { content: "\f22e"; } -.glyphicon-sort-by-attributes:before { content: "\f22f"; } -.glyphicon-sort:before { content: "\f230"; } -.glyphicon-star-empty:before { content: "\f231"; } -.glyphicon-star:before { content: "\f232"; } -.glyphicon-stats:before { content: "\f233"; } -.glyphicon-step-backward:before { content: "\f234"; } -.glyphicon-step-forward:before { content: "\f235"; } -.glyphicon-stop:before { content: "\f236"; } -.glyphicon-story-banner-section:before { content: "\f237"; } -.glyphicon-story-carousel-section:before { content: "\f238"; } -.glyphicon-story-immersive-content:before { content: "\f239"; } -.glyphicon-story-immersive-section:before { content: "\f23a"; } -.glyphicon-story-media-section:before { content: "\f23b"; } -.glyphicon-story-paragraph-section:before { content: "\f23c"; } -.glyphicon-story-title-section:before { content: "\f23d"; } -.glyphicon-story-webpage-section:before { content: "\f23e"; } -.glyphicon-tag:before { content: "\f23f"; } -.glyphicon-tags:before { content: "\f240"; } -.glyphicon-tasks:before { content: "\f241"; } -.glyphicon-text-background:before { content: "\f242"; } -.glyphicon-text-colour:before { content: "\f243"; } -.glyphicon-text-height:before { content: "\f244"; } -.glyphicon-text-width:before { content: "\f245"; } -.glyphicon-th-large:before { content: "\f246"; } -.glyphicon-th-list:before { content: "\f247"; } -.glyphicon-th:before { content: "\f248"; } -.glyphicon-thumbs-down:before { content: "\f249"; } -.glyphicon-thumbs-up:before { content: "\f24a"; } -.glyphicon-time-current:before { content: "\f24b"; } -.glyphicon-time-offset:before { content: "\f24c"; } -.glyphicon-time:before { content: "\f24d"; } -.glyphicon-tint:before { content: "\f24e"; } -.glyphicon-transfer:before { content: "\f24f"; } -.glyphicon-trash-square:before { content: "\f250"; } -.glyphicon-trash:before { content: "\f251"; } -.glyphicon-unchecked:before { content: "\f252"; } -.glyphicon-undo:before { content: "\f253"; } -.glyphicon-unplug:before { content: "\f254"; } -.glyphicon-upload:before { content: "\f255"; } -.glyphicon-usd:before { content: "\f256"; } -.glyphicon-user:before { content: "\f257"; } -.glyphicon-vert-dashed:before { content: "\f258"; } -.glyphicon-viewport-filter:before { content: "\f259"; } -.glyphicon-warning-sign:before { content: "\f25a"; } -.glyphicon-webpage:before { content: "\f25b"; } -.glyphicon-wrench:before { content: "\f25c"; } -.glyphicon-zoom-in:before { content: "\f25d"; } -.glyphicon-zoom-out:before { content: "\f25e"; } -.glyphicon-zoom-to:before { content: "\f25f"; } +.glyphicon-grid-custom:before { content: "\f1a1"; } +.glyphicon-grid-regular:before { content: "\f1a2"; } +.glyphicon-hand-down:before { content: "\f1a3"; } +.glyphicon-hand-left:before { content: "\f1a4"; } +.glyphicon-hand-right:before { content: "\f1a5"; } +.glyphicon-hand-up:before { content: "\f1a6"; } +.glyphicon-hdd:before { content: "\f1a7"; } +.glyphicon-heart:before { content: "\f1a8"; } +.glyphicon-height-auto:before { content: "\f1a9"; } +.glyphicon-height-from-terrain:before { content: "\f1aa"; } +.glyphicon-height-view:before { content: "\f1ab"; } +.glyphicon-hide-marker:before { content: "\f1ac"; } +.glyphicon-home:before { content: "\f1ad"; } +.glyphicon-hourglass:before { content: "\f1ae"; } +.glyphicon-import:before { content: "\f1af"; } +.glyphicon-inbox:before { content: "\f1b0"; } +.glyphicon-info-sign:before { content: "\f1b1"; } +.glyphicon-italic:before { content: "\f1b2"; } +.glyphicon-layer-info:before { content: "\f1b3"; } +.glyphicon-leaf:before { content: "\f1b4"; } +.glyphicon-level-up:before { content: "\f1b5"; } +.glyphicon-line-dash:before { content: "\f1b6"; } +.glyphicon-line-minus:before { content: "\f1b7"; } +.glyphicon-line-plus:before { content: "\f1b8"; } +.glyphicon-line-remove:before { content: "\f1b9"; } +.glyphicon-line-trash:before { content: "\f1ba"; } +.glyphicon-line:before { content: "\f1bb"; } +.glyphicon-link:before { content: "\f1bc"; } +.glyphicon-list-alt:before { content: "\f1bd"; } +.glyphicon-list:before { content: "\f1be"; } +.glyphicon-lock:before { content: "\f1bf"; } +.glyphicon-log-in:before { content: "\f1c0"; } +.glyphicon-log-out:before { content: "\f1c1"; } +.glyphicon-loop:before { content: "\f1c2"; } +.glyphicon-magnet:before { content: "\f1c3"; } +.glyphicon-map-context:before { content: "\f1c4"; } +.glyphicon-map-edit:before { content: "\f1c5"; } +.glyphicon-map-filter:before { content: "\f1c6"; } +.glyphicon-map-marker:before { content: "\f1c7"; } +.glyphicon-map-synch:before { content: "\f1c8"; } +.glyphicon-map-view:before { content: "\f1c9"; } +.glyphicon-maps-catalog:before { content: "\f1ca"; } +.glyphicon-menu-hamburger:before { content: "\f1cb"; } +.glyphicon-minus-sign:before { content: "\f1cc"; } +.glyphicon-minus:before { content: "\f1cd"; } +.glyphicon-model-plus:before { content: "\f1ce"; } +.glyphicon-model:before { content: "\f1cf"; } +.glyphicon-mouse:before { content: "\f1d0"; } +.glyphicon-move-row-after:before { content: "\f1d1"; } +.glyphicon-move-row-before:before { content: "\f1d2"; } +.glyphicon-move:before { content: "\f1d3"; } +.glyphicon-muted:before { content: "\f1d4"; } +.glyphicon-new-window:before { content: "\f1d5"; } +.glyphicon-next:before { content: "\f1d6"; } +.glyphicon-off:before { content: "\f1d7"; } +.glyphicon-ok-circle:before { content: "\f1d8"; } +.glyphicon-ok-sign:before { content: "\f1d9"; } +.glyphicon-ok:before { content: "\f1da"; } +.glyphicon-open:before { content: "\f1db"; } +.glyphicon-option-horizontal:before { content: "\f1dc"; } +.glyphicon-option-vertical:before { content: "\f1dd"; } +.glyphicon-paperclip:before { content: "\f1de"; } +.glyphicon-paste:before { content: "\f1df"; } +.glyphicon-pause:before { content: "\f1e0"; } +.glyphicon-pencil-add:before { content: "\f1e1"; } +.glyphicon-pencil-edit:before { content: "\f1e2"; } +.glyphicon-pencil:before { content: "\f1e3"; } +.glyphicon-phone:before { content: "\f1e4"; } +.glyphicon-picture:before { content: "\f1e5"; } +.glyphicon-pie-chart:before { content: "\f1e6"; } +.glyphicon-plane:before { content: "\f1e7"; } +.glyphicon-play-circle:before { content: "\f1e8"; } +.glyphicon-play:before { content: "\f1e9"; } +.glyphicon-playback:before { content: "\f1ea"; } +.glyphicon-plug:before { content: "\f1eb"; } +.glyphicon-plus-sign:before { content: "\f1ec"; } +.glyphicon-plus-square:before { content: "\f1ed"; } +.glyphicon-plus:before { content: "\f1ee"; } +.glyphicon-point-coordinates:before { content: "\f1ef"; } +.glyphicon-point-dash:before { content: "\f1f0"; } +.glyphicon-point-minus:before { content: "\f1f1"; } +.glyphicon-point-plus:before { content: "\f1f2"; } +.glyphicon-point-remove:before { content: "\f1f3"; } +.glyphicon-point-trash:before { content: "\f1f4"; } +.glyphicon-point:before { content: "\f1f5"; } +.glyphicon-polygon-3d:before { content: "\f1f6"; } +.glyphicon-polygon-dash:before { content: "\f1f7"; } +.glyphicon-polygon-minus:before { content: "\f1f8"; } +.glyphicon-polygon-plus:before { content: "\f1f9"; } +.glyphicon-polygon-remove:before { content: "\f1fa"; } +.glyphicon-polygon-trash:before { content: "\f1fb"; } +.glyphicon-polygon:before { content: "\f1fc"; } +.glyphicon-polyline-3d:before { content: "\f1fd"; } +.glyphicon-polyline-dash:before { content: "\f1fe"; } +.glyphicon-polyline-minus:before { content: "\f1ff"; } +.glyphicon-polyline-plus:before { content: "\f200"; } +.glyphicon-polyline-remove:before { content: "\f201"; } +.glyphicon-polyline-trash:before { content: "\f202"; } +.glyphicon-polyline:before { content: "\f203"; } +.glyphicon-preview:before { content: "\f204"; } +.glyphicon-print:before { content: "\f205"; } +.glyphicon-pushpin:before { content: "\f206"; } +.glyphicon-qrcode:before { content: "\f207"; } +.glyphicon-question-sign:before { content: "\f208"; } +.glyphicon-random:before { content: "\f209"; } +.glyphicon-range-end:before { content: "\f20a"; } +.glyphicon-range-start:before { content: "\f20b"; } +.glyphicon-record:before { content: "\f20c"; } +.glyphicon-redo:before { content: "\f20d"; } +.glyphicon-refresh:before { content: "\f20e"; } +.glyphicon-remove-circle:before { content: "\f20f"; } +.glyphicon-remove-sign:before { content: "\f210"; } +.glyphicon-remove-square:before { content: "\f211"; } +.glyphicon-remove:before { content: "\f212"; } +.glyphicon-repeat:before { content: "\f213"; } +.glyphicon-resize-full:before { content: "\f214"; } +.glyphicon-resize-horizontal:before { content: "\f215"; } +.glyphicon-resize-small:before { content: "\f216"; } +.glyphicon-resize-vertical:before { content: "\f217"; } +.glyphicon-retweet:before { content: "\f218"; } +.glyphicon-rgb:before { content: "\f219"; } +.glyphicon-road:before { content: "\f21a"; } +.glyphicon-row-add:before { content: "\f21b"; } +.glyphicon-row-trash:before { content: "\f21c"; } +.glyphicon-save:before { content: "\f21d"; } +.glyphicon-saved:before { content: "\f21e"; } +.glyphicon-scissors:before { content: "\f21f"; } +.glyphicon-screenshot:before { content: "\f220"; } +.glyphicon-search-coords:before { content: "\f221"; } +.glyphicon-search:before { content: "\f222"; } +.glyphicon-send:before { content: "\f223"; } +.glyphicon-share-alt:before { content: "\f224"; } +.glyphicon-share:before { content: "\f225"; } +.glyphicon-sheet:before { content: "\f226"; } +.glyphicon-shopping-cart:before { content: "\f227"; } +.glyphicon-signal:before { content: "\f228"; } +.glyphicon-size-extra-large:before { content: "\f229"; } +.glyphicon-size-large:before { content: "\f22a"; } +.glyphicon-size-medium:before { content: "\f22b"; } +.glyphicon-size-small:before { content: "\f22c"; } +.glyphicon-slope:before { content: "\f22d"; } +.glyphicon-sort-by-alphabet-alt:before { content: "\f22e"; } +.glyphicon-sort-by-alphabet:before { content: "\f22f"; } +.glyphicon-sort-by-attributes-alt:before { content: "\f230"; } +.glyphicon-sort-by-attributes:before { content: "\f231"; } +.glyphicon-sort:before { content: "\f232"; } +.glyphicon-star-empty:before { content: "\f233"; } +.glyphicon-star:before { content: "\f234"; } +.glyphicon-stats:before { content: "\f235"; } +.glyphicon-step-backward:before { content: "\f236"; } +.glyphicon-step-forward:before { content: "\f237"; } +.glyphicon-stop:before { content: "\f238"; } +.glyphicon-story-banner-section:before { content: "\f239"; } +.glyphicon-story-carousel-section:before { content: "\f23a"; } +.glyphicon-story-immersive-content:before { content: "\f23b"; } +.glyphicon-story-immersive-section:before { content: "\f23c"; } +.glyphicon-story-media-section:before { content: "\f23d"; } +.glyphicon-story-paragraph-section:before { content: "\f23e"; } +.glyphicon-story-title-section:before { content: "\f23f"; } +.glyphicon-story-webpage-section:before { content: "\f240"; } +.glyphicon-tag:before { content: "\f241"; } +.glyphicon-tags:before { content: "\f242"; } +.glyphicon-tasks:before { content: "\f243"; } +.glyphicon-text-background:before { content: "\f244"; } +.glyphicon-text-colour:before { content: "\f245"; } +.glyphicon-text-height:before { content: "\f246"; } +.glyphicon-text-width:before { content: "\f247"; } +.glyphicon-th-large:before { content: "\f248"; } +.glyphicon-th-list:before { content: "\f249"; } +.glyphicon-th:before { content: "\f24a"; } +.glyphicon-thumbs-down:before { content: "\f24b"; } +.glyphicon-thumbs-up:before { content: "\f24c"; } +.glyphicon-time-current:before { content: "\f24d"; } +.glyphicon-time-offset:before { content: "\f24e"; } +.glyphicon-time:before { content: "\f24f"; } +.glyphicon-tint:before { content: "\f250"; } +.glyphicon-transfer:before { content: "\f251"; } +.glyphicon-trash-square:before { content: "\f252"; } +.glyphicon-trash:before { content: "\f253"; } +.glyphicon-unchecked:before { content: "\f254"; } +.glyphicon-undo:before { content: "\f255"; } +.glyphicon-unplug:before { content: "\f256"; } +.glyphicon-upload:before { content: "\f257"; } +.glyphicon-usd:before { content: "\f258"; } +.glyphicon-user:before { content: "\f259"; } +.glyphicon-vert-dashed:before { content: "\f25a"; } +.glyphicon-viewport-filter:before { content: "\f25b"; } +.glyphicon-warning-sign:before { content: "\f25c"; } +.glyphicon-webpage:before { content: "\f25d"; } +.glyphicon-wrench:before { content: "\f25e"; } +.glyphicon-zoom-in:before { content: "\f25f"; } +.glyphicon-zoom-out:before { content: "\f260"; } +.glyphicon-zoom-to:before { content: "\f261"; } /* classes with icon code @@ -557,194 +559,196 @@ structure .glyphicon-{iconName}-content .glyphicon-globe-content { content: "\f19e"; } .glyphicon-grab-handle-content { content: "\f19f"; } .glyphicon-gray-scale-content { content: "\f1a0"; } -.glyphicon-hand-down-content { content: "\f1a1"; } -.glyphicon-hand-left-content { content: "\f1a2"; } -.glyphicon-hand-right-content { content: "\f1a3"; } -.glyphicon-hand-up-content { content: "\f1a4"; } -.glyphicon-hdd-content { content: "\f1a5"; } -.glyphicon-heart-content { content: "\f1a6"; } -.glyphicon-height-auto-content { content: "\f1a7"; } -.glyphicon-height-from-terrain-content { content: "\f1a8"; } -.glyphicon-height-view-content { content: "\f1a9"; } -.glyphicon-hide-marker-content { content: "\f1aa"; } -.glyphicon-home-content { content: "\f1ab"; } -.glyphicon-hourglass-content { content: "\f1ac"; } -.glyphicon-import-content { content: "\f1ad"; } -.glyphicon-inbox-content { content: "\f1ae"; } -.glyphicon-info-sign-content { content: "\f1af"; } -.glyphicon-italic-content { content: "\f1b0"; } -.glyphicon-layer-info-content { content: "\f1b1"; } -.glyphicon-leaf-content { content: "\f1b2"; } -.glyphicon-level-up-content { content: "\f1b3"; } -.glyphicon-line-dash-content { content: "\f1b4"; } -.glyphicon-line-minus-content { content: "\f1b5"; } -.glyphicon-line-plus-content { content: "\f1b6"; } -.glyphicon-line-remove-content { content: "\f1b7"; } -.glyphicon-line-trash-content { content: "\f1b8"; } -.glyphicon-line-content { content: "\f1b9"; } -.glyphicon-link-content { content: "\f1ba"; } -.glyphicon-list-alt-content { content: "\f1bb"; } -.glyphicon-list-content { content: "\f1bc"; } -.glyphicon-lock-content { content: "\f1bd"; } -.glyphicon-log-in-content { content: "\f1be"; } -.glyphicon-log-out-content { content: "\f1bf"; } -.glyphicon-loop-content { content: "\f1c0"; } -.glyphicon-magnet-content { content: "\f1c1"; } -.glyphicon-map-context-content { content: "\f1c2"; } -.glyphicon-map-edit-content { content: "\f1c3"; } -.glyphicon-map-filter-content { content: "\f1c4"; } -.glyphicon-map-marker-content { content: "\f1c5"; } -.glyphicon-map-synch-content { content: "\f1c6"; } -.glyphicon-map-view-content { content: "\f1c7"; } -.glyphicon-maps-catalog-content { content: "\f1c8"; } -.glyphicon-menu-hamburger-content { content: "\f1c9"; } -.glyphicon-minus-sign-content { content: "\f1ca"; } -.glyphicon-minus-content { content: "\f1cb"; } -.glyphicon-model-plus-content { content: "\f1cc"; } -.glyphicon-model-content { content: "\f1cd"; } -.glyphicon-mouse-content { content: "\f1ce"; } -.glyphicon-move-row-after-content { content: "\f1cf"; } -.glyphicon-move-row-before-content { content: "\f1d0"; } -.glyphicon-move-content { content: "\f1d1"; } -.glyphicon-muted-content { content: "\f1d2"; } -.glyphicon-new-window-content { content: "\f1d3"; } -.glyphicon-next-content { content: "\f1d4"; } -.glyphicon-off-content { content: "\f1d5"; } -.glyphicon-ok-circle-content { content: "\f1d6"; } -.glyphicon-ok-sign-content { content: "\f1d7"; } -.glyphicon-ok-content { content: "\f1d8"; } -.glyphicon-open-content { content: "\f1d9"; } -.glyphicon-option-horizontal-content { content: "\f1da"; } -.glyphicon-option-vertical-content { content: "\f1db"; } -.glyphicon-paperclip-content { content: "\f1dc"; } -.glyphicon-paste-content { content: "\f1dd"; } -.glyphicon-pause-content { content: "\f1de"; } -.glyphicon-pencil-add-content { content: "\f1df"; } -.glyphicon-pencil-edit-content { content: "\f1e0"; } -.glyphicon-pencil-content { content: "\f1e1"; } -.glyphicon-phone-content { content: "\f1e2"; } -.glyphicon-picture-content { content: "\f1e3"; } -.glyphicon-pie-chart-content { content: "\f1e4"; } -.glyphicon-plane-content { content: "\f1e5"; } -.glyphicon-play-circle-content { content: "\f1e6"; } -.glyphicon-play-content { content: "\f1e7"; } -.glyphicon-playback-content { content: "\f1e8"; } -.glyphicon-plug-content { content: "\f1e9"; } -.glyphicon-plus-sign-content { content: "\f1ea"; } -.glyphicon-plus-square-content { content: "\f1eb"; } -.glyphicon-plus-content { content: "\f1ec"; } -.glyphicon-point-coordinates-content { content: "\f1ed"; } -.glyphicon-point-dash-content { content: "\f1ee"; } -.glyphicon-point-minus-content { content: "\f1ef"; } -.glyphicon-point-plus-content { content: "\f1f0"; } -.glyphicon-point-remove-content { content: "\f1f1"; } -.glyphicon-point-trash-content { content: "\f1f2"; } -.glyphicon-point-content { content: "\f1f3"; } -.glyphicon-polygon-3d-content { content: "\f1f4"; } -.glyphicon-polygon-dash-content { content: "\f1f5"; } -.glyphicon-polygon-minus-content { content: "\f1f6"; } -.glyphicon-polygon-plus-content { content: "\f1f7"; } -.glyphicon-polygon-remove-content { content: "\f1f8"; } -.glyphicon-polygon-trash-content { content: "\f1f9"; } -.glyphicon-polygon-content { content: "\f1fa"; } -.glyphicon-polyline-3d-content { content: "\f1fb"; } -.glyphicon-polyline-dash-content { content: "\f1fc"; } -.glyphicon-polyline-minus-content { content: "\f1fd"; } -.glyphicon-polyline-plus-content { content: "\f1fe"; } -.glyphicon-polyline-remove-content { content: "\f1ff"; } -.glyphicon-polyline-trash-content { content: "\f200"; } -.glyphicon-polyline-content { content: "\f201"; } -.glyphicon-preview-content { content: "\f202"; } -.glyphicon-print-content { content: "\f203"; } -.glyphicon-pushpin-content { content: "\f204"; } -.glyphicon-qrcode-content { content: "\f205"; } -.glyphicon-question-sign-content { content: "\f206"; } -.glyphicon-random-content { content: "\f207"; } -.glyphicon-range-end-content { content: "\f208"; } -.glyphicon-range-start-content { content: "\f209"; } -.glyphicon-record-content { content: "\f20a"; } -.glyphicon-redo-content { content: "\f20b"; } -.glyphicon-refresh-content { content: "\f20c"; } -.glyphicon-remove-circle-content { content: "\f20d"; } -.glyphicon-remove-sign-content { content: "\f20e"; } -.glyphicon-remove-square-content { content: "\f20f"; } -.glyphicon-remove-content { content: "\f210"; } -.glyphicon-repeat-content { content: "\f211"; } -.glyphicon-resize-full-content { content: "\f212"; } -.glyphicon-resize-horizontal-content { content: "\f213"; } -.glyphicon-resize-small-content { content: "\f214"; } -.glyphicon-resize-vertical-content { content: "\f215"; } -.glyphicon-retweet-content { content: "\f216"; } -.glyphicon-rgb-content { content: "\f217"; } -.glyphicon-road-content { content: "\f218"; } -.glyphicon-row-add-content { content: "\f219"; } -.glyphicon-row-trash-content { content: "\f21a"; } -.glyphicon-save-content { content: "\f21b"; } -.glyphicon-saved-content { content: "\f21c"; } -.glyphicon-scissors-content { content: "\f21d"; } -.glyphicon-screenshot-content { content: "\f21e"; } -.glyphicon-search-coords-content { content: "\f21f"; } -.glyphicon-search-content { content: "\f220"; } -.glyphicon-send-content { content: "\f221"; } -.glyphicon-share-alt-content { content: "\f222"; } -.glyphicon-share-content { content: "\f223"; } -.glyphicon-sheet-content { content: "\f224"; } -.glyphicon-shopping-cart-content { content: "\f225"; } -.glyphicon-signal-content { content: "\f226"; } -.glyphicon-size-extra-large-content { content: "\f227"; } -.glyphicon-size-large-content { content: "\f228"; } -.glyphicon-size-medium-content { content: "\f229"; } -.glyphicon-size-small-content { content: "\f22a"; } -.glyphicon-slope-content { content: "\f22b"; } -.glyphicon-sort-by-alphabet-alt-content { content: "\f22c"; } -.glyphicon-sort-by-alphabet-content { content: "\f22d"; } -.glyphicon-sort-by-attributes-alt-content { content: "\f22e"; } -.glyphicon-sort-by-attributes-content { content: "\f22f"; } -.glyphicon-sort-content { content: "\f230"; } -.glyphicon-star-empty-content { content: "\f231"; } -.glyphicon-star-content { content: "\f232"; } -.glyphicon-stats-content { content: "\f233"; } -.glyphicon-step-backward-content { content: "\f234"; } -.glyphicon-step-forward-content { content: "\f235"; } -.glyphicon-stop-content { content: "\f236"; } -.glyphicon-story-banner-section-content { content: "\f237"; } -.glyphicon-story-carousel-section-content { content: "\f238"; } -.glyphicon-story-immersive-content-content { content: "\f239"; } -.glyphicon-story-immersive-section-content { content: "\f23a"; } -.glyphicon-story-media-section-content { content: "\f23b"; } -.glyphicon-story-paragraph-section-content { content: "\f23c"; } -.glyphicon-story-title-section-content { content: "\f23d"; } -.glyphicon-story-webpage-section-content { content: "\f23e"; } -.glyphicon-tag-content { content: "\f23f"; } -.glyphicon-tags-content { content: "\f240"; } -.glyphicon-tasks-content { content: "\f241"; } -.glyphicon-text-background-content { content: "\f242"; } -.glyphicon-text-colour-content { content: "\f243"; } -.glyphicon-text-height-content { content: "\f244"; } -.glyphicon-text-width-content { content: "\f245"; } -.glyphicon-th-large-content { content: "\f246"; } -.glyphicon-th-list-content { content: "\f247"; } -.glyphicon-th-content { content: "\f248"; } -.glyphicon-thumbs-down-content { content: "\f249"; } -.glyphicon-thumbs-up-content { content: "\f24a"; } -.glyphicon-time-current-content { content: "\f24b"; } -.glyphicon-time-offset-content { content: "\f24c"; } -.glyphicon-time-content { content: "\f24d"; } -.glyphicon-tint-content { content: "\f24e"; } -.glyphicon-transfer-content { content: "\f24f"; } -.glyphicon-trash-square-content { content: "\f250"; } -.glyphicon-trash-content { content: "\f251"; } -.glyphicon-unchecked-content { content: "\f252"; } -.glyphicon-undo-content { content: "\f253"; } -.glyphicon-unplug-content { content: "\f254"; } -.glyphicon-upload-content { content: "\f255"; } -.glyphicon-usd-content { content: "\f256"; } -.glyphicon-user-content { content: "\f257"; } -.glyphicon-vert-dashed-content { content: "\f258"; } -.glyphicon-viewport-filter-content { content: "\f259"; } -.glyphicon-warning-sign-content { content: "\f25a"; } -.glyphicon-webpage-content { content: "\f25b"; } -.glyphicon-wrench-content { content: "\f25c"; } -.glyphicon-zoom-in-content { content: "\f25d"; } -.glyphicon-zoom-out-content { content: "\f25e"; } -.glyphicon-zoom-to-content { content: "\f25f"; } +.glyphicon-grid-custom-content { content: "\f1a1"; } +.glyphicon-grid-regular-content { content: "\f1a2"; } +.glyphicon-hand-down-content { content: "\f1a3"; } +.glyphicon-hand-left-content { content: "\f1a4"; } +.glyphicon-hand-right-content { content: "\f1a5"; } +.glyphicon-hand-up-content { content: "\f1a6"; } +.glyphicon-hdd-content { content: "\f1a7"; } +.glyphicon-heart-content { content: "\f1a8"; } +.glyphicon-height-auto-content { content: "\f1a9"; } +.glyphicon-height-from-terrain-content { content: "\f1aa"; } +.glyphicon-height-view-content { content: "\f1ab"; } +.glyphicon-hide-marker-content { content: "\f1ac"; } +.glyphicon-home-content { content: "\f1ad"; } +.glyphicon-hourglass-content { content: "\f1ae"; } +.glyphicon-import-content { content: "\f1af"; } +.glyphicon-inbox-content { content: "\f1b0"; } +.glyphicon-info-sign-content { content: "\f1b1"; } +.glyphicon-italic-content { content: "\f1b2"; } +.glyphicon-layer-info-content { content: "\f1b3"; } +.glyphicon-leaf-content { content: "\f1b4"; } +.glyphicon-level-up-content { content: "\f1b5"; } +.glyphicon-line-dash-content { content: "\f1b6"; } +.glyphicon-line-minus-content { content: "\f1b7"; } +.glyphicon-line-plus-content { content: "\f1b8"; } +.glyphicon-line-remove-content { content: "\f1b9"; } +.glyphicon-line-trash-content { content: "\f1ba"; } +.glyphicon-line-content { content: "\f1bb"; } +.glyphicon-link-content { content: "\f1bc"; } +.glyphicon-list-alt-content { content: "\f1bd"; } +.glyphicon-list-content { content: "\f1be"; } +.glyphicon-lock-content { content: "\f1bf"; } +.glyphicon-log-in-content { content: "\f1c0"; } +.glyphicon-log-out-content { content: "\f1c1"; } +.glyphicon-loop-content { content: "\f1c2"; } +.glyphicon-magnet-content { content: "\f1c3"; } +.glyphicon-map-context-content { content: "\f1c4"; } +.glyphicon-map-edit-content { content: "\f1c5"; } +.glyphicon-map-filter-content { content: "\f1c6"; } +.glyphicon-map-marker-content { content: "\f1c7"; } +.glyphicon-map-synch-content { content: "\f1c8"; } +.glyphicon-map-view-content { content: "\f1c9"; } +.glyphicon-maps-catalog-content { content: "\f1ca"; } +.glyphicon-menu-hamburger-content { content: "\f1cb"; } +.glyphicon-minus-sign-content { content: "\f1cc"; } +.glyphicon-minus-content { content: "\f1cd"; } +.glyphicon-model-plus-content { content: "\f1ce"; } +.glyphicon-model-content { content: "\f1cf"; } +.glyphicon-mouse-content { content: "\f1d0"; } +.glyphicon-move-row-after-content { content: "\f1d1"; } +.glyphicon-move-row-before-content { content: "\f1d2"; } +.glyphicon-move-content { content: "\f1d3"; } +.glyphicon-muted-content { content: "\f1d4"; } +.glyphicon-new-window-content { content: "\f1d5"; } +.glyphicon-next-content { content: "\f1d6"; } +.glyphicon-off-content { content: "\f1d7"; } +.glyphicon-ok-circle-content { content: "\f1d8"; } +.glyphicon-ok-sign-content { content: "\f1d9"; } +.glyphicon-ok-content { content: "\f1da"; } +.glyphicon-open-content { content: "\f1db"; } +.glyphicon-option-horizontal-content { content: "\f1dc"; } +.glyphicon-option-vertical-content { content: "\f1dd"; } +.glyphicon-paperclip-content { content: "\f1de"; } +.glyphicon-paste-content { content: "\f1df"; } +.glyphicon-pause-content { content: "\f1e0"; } +.glyphicon-pencil-add-content { content: "\f1e1"; } +.glyphicon-pencil-edit-content { content: "\f1e2"; } +.glyphicon-pencil-content { content: "\f1e3"; } +.glyphicon-phone-content { content: "\f1e4"; } +.glyphicon-picture-content { content: "\f1e5"; } +.glyphicon-pie-chart-content { content: "\f1e6"; } +.glyphicon-plane-content { content: "\f1e7"; } +.glyphicon-play-circle-content { content: "\f1e8"; } +.glyphicon-play-content { content: "\f1e9"; } +.glyphicon-playback-content { content: "\f1ea"; } +.glyphicon-plug-content { content: "\f1eb"; } +.glyphicon-plus-sign-content { content: "\f1ec"; } +.glyphicon-plus-square-content { content: "\f1ed"; } +.glyphicon-plus-content { content: "\f1ee"; } +.glyphicon-point-coordinates-content { content: "\f1ef"; } +.glyphicon-point-dash-content { content: "\f1f0"; } +.glyphicon-point-minus-content { content: "\f1f1"; } +.glyphicon-point-plus-content { content: "\f1f2"; } +.glyphicon-point-remove-content { content: "\f1f3"; } +.glyphicon-point-trash-content { content: "\f1f4"; } +.glyphicon-point-content { content: "\f1f5"; } +.glyphicon-polygon-3d-content { content: "\f1f6"; } +.glyphicon-polygon-dash-content { content: "\f1f7"; } +.glyphicon-polygon-minus-content { content: "\f1f8"; } +.glyphicon-polygon-plus-content { content: "\f1f9"; } +.glyphicon-polygon-remove-content { content: "\f1fa"; } +.glyphicon-polygon-trash-content { content: "\f1fb"; } +.glyphicon-polygon-content { content: "\f1fc"; } +.glyphicon-polyline-3d-content { content: "\f1fd"; } +.glyphicon-polyline-dash-content { content: "\f1fe"; } +.glyphicon-polyline-minus-content { content: "\f1ff"; } +.glyphicon-polyline-plus-content { content: "\f200"; } +.glyphicon-polyline-remove-content { content: "\f201"; } +.glyphicon-polyline-trash-content { content: "\f202"; } +.glyphicon-polyline-content { content: "\f203"; } +.glyphicon-preview-content { content: "\f204"; } +.glyphicon-print-content { content: "\f205"; } +.glyphicon-pushpin-content { content: "\f206"; } +.glyphicon-qrcode-content { content: "\f207"; } +.glyphicon-question-sign-content { content: "\f208"; } +.glyphicon-random-content { content: "\f209"; } +.glyphicon-range-end-content { content: "\f20a"; } +.glyphicon-range-start-content { content: "\f20b"; } +.glyphicon-record-content { content: "\f20c"; } +.glyphicon-redo-content { content: "\f20d"; } +.glyphicon-refresh-content { content: "\f20e"; } +.glyphicon-remove-circle-content { content: "\f20f"; } +.glyphicon-remove-sign-content { content: "\f210"; } +.glyphicon-remove-square-content { content: "\f211"; } +.glyphicon-remove-content { content: "\f212"; } +.glyphicon-repeat-content { content: "\f213"; } +.glyphicon-resize-full-content { content: "\f214"; } +.glyphicon-resize-horizontal-content { content: "\f215"; } +.glyphicon-resize-small-content { content: "\f216"; } +.glyphicon-resize-vertical-content { content: "\f217"; } +.glyphicon-retweet-content { content: "\f218"; } +.glyphicon-rgb-content { content: "\f219"; } +.glyphicon-road-content { content: "\f21a"; } +.glyphicon-row-add-content { content: "\f21b"; } +.glyphicon-row-trash-content { content: "\f21c"; } +.glyphicon-save-content { content: "\f21d"; } +.glyphicon-saved-content { content: "\f21e"; } +.glyphicon-scissors-content { content: "\f21f"; } +.glyphicon-screenshot-content { content: "\f220"; } +.glyphicon-search-coords-content { content: "\f221"; } +.glyphicon-search-content { content: "\f222"; } +.glyphicon-send-content { content: "\f223"; } +.glyphicon-share-alt-content { content: "\f224"; } +.glyphicon-share-content { content: "\f225"; } +.glyphicon-sheet-content { content: "\f226"; } +.glyphicon-shopping-cart-content { content: "\f227"; } +.glyphicon-signal-content { content: "\f228"; } +.glyphicon-size-extra-large-content { content: "\f229"; } +.glyphicon-size-large-content { content: "\f22a"; } +.glyphicon-size-medium-content { content: "\f22b"; } +.glyphicon-size-small-content { content: "\f22c"; } +.glyphicon-slope-content { content: "\f22d"; } +.glyphicon-sort-by-alphabet-alt-content { content: "\f22e"; } +.glyphicon-sort-by-alphabet-content { content: "\f22f"; } +.glyphicon-sort-by-attributes-alt-content { content: "\f230"; } +.glyphicon-sort-by-attributes-content { content: "\f231"; } +.glyphicon-sort-content { content: "\f232"; } +.glyphicon-star-empty-content { content: "\f233"; } +.glyphicon-star-content { content: "\f234"; } +.glyphicon-stats-content { content: "\f235"; } +.glyphicon-step-backward-content { content: "\f236"; } +.glyphicon-step-forward-content { content: "\f237"; } +.glyphicon-stop-content { content: "\f238"; } +.glyphicon-story-banner-section-content { content: "\f239"; } +.glyphicon-story-carousel-section-content { content: "\f23a"; } +.glyphicon-story-immersive-content-content { content: "\f23b"; } +.glyphicon-story-immersive-section-content { content: "\f23c"; } +.glyphicon-story-media-section-content { content: "\f23d"; } +.glyphicon-story-paragraph-section-content { content: "\f23e"; } +.glyphicon-story-title-section-content { content: "\f23f"; } +.glyphicon-story-webpage-section-content { content: "\f240"; } +.glyphicon-tag-content { content: "\f241"; } +.glyphicon-tags-content { content: "\f242"; } +.glyphicon-tasks-content { content: "\f243"; } +.glyphicon-text-background-content { content: "\f244"; } +.glyphicon-text-colour-content { content: "\f245"; } +.glyphicon-text-height-content { content: "\f246"; } +.glyphicon-text-width-content { content: "\f247"; } +.glyphicon-th-large-content { content: "\f248"; } +.glyphicon-th-list-content { content: "\f249"; } +.glyphicon-th-content { content: "\f24a"; } +.glyphicon-thumbs-down-content { content: "\f24b"; } +.glyphicon-thumbs-up-content { content: "\f24c"; } +.glyphicon-time-current-content { content: "\f24d"; } +.glyphicon-time-offset-content { content: "\f24e"; } +.glyphicon-time-content { content: "\f24f"; } +.glyphicon-tint-content { content: "\f250"; } +.glyphicon-transfer-content { content: "\f251"; } +.glyphicon-trash-square-content { content: "\f252"; } +.glyphicon-trash-content { content: "\f253"; } +.glyphicon-unchecked-content { content: "\f254"; } +.glyphicon-undo-content { content: "\f255"; } +.glyphicon-unplug-content { content: "\f256"; } +.glyphicon-upload-content { content: "\f257"; } +.glyphicon-usd-content { content: "\f258"; } +.glyphicon-user-content { content: "\f259"; } +.glyphicon-vert-dashed-content { content: "\f25a"; } +.glyphicon-viewport-filter-content { content: "\f25b"; } +.glyphicon-warning-sign-content { content: "\f25c"; } +.glyphicon-webpage-content { content: "\f25d"; } +.glyphicon-wrench-content { content: "\f25e"; } +.glyphicon-zoom-in-content { content: "\f25f"; } +.glyphicon-zoom-out-content { content: "\f260"; } +.glyphicon-zoom-to-content { content: "\f261"; } diff --git a/web/client/themes/default/icons/icons.eot b/web/client/themes/default/icons/icons.eot index 4ef5172e2c..5b0b0b46e4 100755 Binary files a/web/client/themes/default/icons/icons.eot and b/web/client/themes/default/icons/icons.eot differ diff --git a/web/client/themes/default/icons/icons.svg b/web/client/themes/default/icons/icons.svg index d727eec3d1..db8b56e719 100755 --- a/web/client/themes/default/icons/icons.svg +++ b/web/client/themes/default/icons/icons.svg @@ -967,1148 +967,1160 @@ - + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/web/client/themes/default/svg/grid-regular.svg b/web/client/themes/default/svg/grid-regular.svg new file mode 100644 index 0000000000..0b4388b6e0 --- /dev/null +++ b/web/client/themes/default/svg/grid-regular.svg @@ -0,0 +1,60 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/web/client/translations/data.de-DE.json b/web/client/translations/data.de-DE.json index 980ce05649..6cd9c7abd5 100644 --- a/web/client/translations/data.de-DE.json +++ b/web/client/translations/data.de-DE.json @@ -118,6 +118,20 @@ "noVendor": "No Vendor", "geoserver": "GeoServer" }, + "availableTileGrids": "Verfügbare Kachelgitter", + "crsId": "Id", + "projection": "Projektion", + "tileSize": "Kachelgröße", + "tileGridInUse": "Das Kachelgitter {id} entspricht der aktuellen Kartenprojektion und der für diese Ebene ausgewählten Kachelgröße", + "useStandardTileGridStrategy": "Standard-Kachelraster verwenden", + "useCustomTileGridStrategy": "Entfernte benutzerdefinierte Kachelraster verwenden", + "updateTileGrids": "Verfügbare Kachelraster aktualisieren", + "noTileGridMatchesConfiguration": "Es gibt kein Kachelraster, das mit der aktuellen Kartenprojektion und der ausgewählten Kachelgröße übereinstimmt. In der Tabelle oben werden die verfügbaren Kachelraster hervorgehoben, die mit der aktuellen Kartenprojektion kompatibel sind. Sie können auf die Schaltfläche Aktualisieren klicken, um die Liste der verfügbaren zu aktualisieren Fliesengitter", + "noConfiguredGridSets": "Es ist nicht möglich, benutzerdefinierte Kachelraster abzurufen. Diese Ebene verfügt über keinen Rastersatz, der mit den verfügbaren Projektionen kompatibel ist. Bitte überprüfen Sie die Konfigurationsserverseite und versuchen Sie es erneut", + "notPossibleToConnectToWMTSService": "Es war nicht möglich, die Kachelraster vom Server abzurufen. Die an {requestUrl} gesendete Anfrage löst einen Fehler aus.", + "notSupportedSelectedFormatCache": "Das ausgewählte Format wird vom Cache nicht unterstützt. Unterstützte Formate: {formats}", + "notSupportedSelectedStyleCache": "Der ausgewählte Stil wird vom Cache nicht unterstützt", + "customParamsCacheWarning": "Für diese Ebene ist der lokalisierte Stil aktiviert. Bitte überprüfen Sie die Konfigurationsserverseite und stellen Sie sicher, dass sie den benutzerdefinierten ENV-Parameter unterstützt", "visibilityLimits": { "title": "Sichtbarkeitsgrenzen", "serverValuesUpdateUndefined": "Der Server hat keine Skalierungssichtbarkeitsbeschränkungen für diese Ebene angegeben", diff --git a/web/client/translations/data.en-US.json b/web/client/translations/data.en-US.json index dfc2351336..919a3b4cee 100644 --- a/web/client/translations/data.en-US.json +++ b/web/client/translations/data.en-US.json @@ -118,6 +118,20 @@ "noVendor": "No Vendor", "geoserver": "GeoServer" }, + "availableTileGrids": "Available Tile Grids", + "crsId": "Id", + "projection": "Projection", + "tileSize": "Tile size", + "tileGridInUse": "The tile grid '{id}' matches the current map projection and the tile size selected for this layer", + "useStandardTileGridStrategy": "Use standard tile grid", + "useCustomTileGridStrategy": "Use remote custom tile grids", + "updateTileGrids": "Update available tile grids", + "noTileGridMatchesConfiguration": "There is not a tile grid that matches the current map projection and the tile size selected. The table above highlights the tile grids available compatible with the current map projection. You can click the refresh button to update the list of available tile grids", + "noConfiguredGridSets": "It is not possible to fetch custom tile grids. This layer do not have any grid set compatible with the available projections. Please review the configuration server side and retry", + "notPossibleToConnectToWMTSService": "It was not possible to fetch the tile grids from the server. The request sent to {requestUrl} is throwing an error", + "notSupportedSelectedFormatCache": "The selected format is not supported by the cache. Supported formats: {formats}", + "notSupportedSelectedStyleCache": "The selected style is not supported by the cache", + "customParamsCacheWarning": "This layer has localized style enabled. Please verify the configuration server side and ensure it supports the custom ENV parameter", "visibilityLimits": { "title": "Visibility limits", "serverValuesUpdateUndefined": "The server did not provided scale visibility limits for this layer", diff --git a/web/client/translations/data.es-ES.json b/web/client/translations/data.es-ES.json index c93472d2c5..d4476931b3 100644 --- a/web/client/translations/data.es-ES.json +++ b/web/client/translations/data.es-ES.json @@ -115,6 +115,20 @@ "noVendor": "No Vendor", "geoserver": "GeoServer" }, + "availableTileGrids": "Cuadrículas de mosaicos disponibles", + "crsId": "Id", + "projection": "Proyección", + "tileSize": "Tamaño del mosaico", + "tileGridInUse": "La cuadrícula de mosaicos '{id}' coincide con la proyección del mapa actual y el tamaño de mosaico seleccionado para esta capa", + "useStandardTileGridStrategy": "Usar cuadrícula de mosaico estándar", + "useCustomTileGridStrategy": "Usar cuadrículas de mosaico personalizadas remotas", + "updateTileGrids": "Actualizar cuadrículas de mosaicos disponibles", + "noTileGridMatchesConfiguration": "No hay una cuadrícula de mosaicos que coincida con la proyección de mapa actual y el tamaño de mosaico seleccionado. La tabla anterior destaca las cuadrículas de mosaicos disponibles compatibles con la proyección de mapa actual. Puede hacer clic en el botón Actualizar para actualizar la lista de disponibles rejillas de azulejos", + "noConfiguredGridSets": "No es posible obtener cuadrículas de mosaico personalizadas. Esta capa no tiene ningún conjunto de cuadrícula compatible con las proyecciones disponibles. Revise el lado del servidor de configuración y vuelva a intentarlo", + "notPossibleToConnectToWMTSService": "No fue posible obtener las cuadrículas de mosaicos del servidor. La solicitud enviada a {requestUrl} arroja un error", + "notSupportedSelectedFormatCache": "El formato seleccionado no es compatible con el caché. Formatos admitidos: {formatos}", + "notSupportedSelectedStyleCache": "El estilo seleccionado no es compatible con el caché", + "customParamsCacheWarning": "Esta capa tiene activado el estilo localizado. Verifique el lado del servidor de configuración y asegúrese de que sea compatible con el parámetro ENV personalizado", "visibilityLimits": { "title": "Límites de visibilidad", "serverValuesUpdateUndefined": "El servidor no proporcionó límites de visibilidad de escala para esta capa", diff --git a/web/client/translations/data.fr-FR.json b/web/client/translations/data.fr-FR.json index 1c11d66228..07b126384c 100644 --- a/web/client/translations/data.fr-FR.json +++ b/web/client/translations/data.fr-FR.json @@ -118,6 +118,20 @@ "noVendor": "No Vendor", "geoserver": "GeoServer" }, + "availableTileGrids": "Grilles de tuiles disponibles", + "crsId": "Id", + "projection": "projection", + "tileSize": "Taille de la tuile", + "tileGridInUse": "La grille de tuiles '{id}' correspond à la projection cartographique actuelle et à la taille de tuile sélectionnée pour cette couche", + "useStandardTileGridStrategy": "Utiliser la grille de tuiles standard", + "useCustomTileGridStrategy": "Utiliser des grilles de tuiles personnalisées à distance", + "updateTileGrids": "Mettre à jour les grilles de tuiles disponibles", + "noTileGridMatchesConfiguration": "Aucune grille de tuiles ne correspond à la projection cartographique actuelle et à la taille de tuile sélectionnée. Le tableau ci-dessus met en évidence les grilles de tuiles disponibles compatibles avec la projection cartographique actuelle. Vous pouvez cliquer sur le bouton d'actualisation pour mettre à jour la liste des grilles de tuiles", + "noConfiguredGridSets": "Il n'est pas possible de récupérer des grilles de tuiles personnalisées. Cette couche n'a pas de grilles compatibles avec les projections disponibles. Veuillez revoir la configuration côté serveur et réessayer", + "notPossibleToConnectToWMTSService": "Il n'a pas été possible de récupérer les grilles de tuiles depuis le serveur. La requête envoyée à {requestUrl} génère une erreur", + "notSupportedSelectedFormatCache": "Le format sélectionné n'est pas pris en charge par le cache. Formats pris en charge : {formats}", + "notSupportedSelectedStyleCache": "Le style sélectionné n'est pas supporté par le cache", + "customParamsCacheWarning": "Ce calque a un style localisé activé. Veuillez vérifier le côté serveur de configuration et vous assurer qu'il prend en charge le paramètre ENV personnalisé", "visibilityLimits": { "title": "Limites de visibilité", "serverValuesUpdateUndefined": "Le serveur n'a pas fourni de limites de visibilité d'échelle pour cette couche", diff --git a/web/client/translations/data.it-IT.json b/web/client/translations/data.it-IT.json index d43c779a3d..e37342675b 100644 --- a/web/client/translations/data.it-IT.json +++ b/web/client/translations/data.it-IT.json @@ -118,6 +118,20 @@ "noVendor": "No Vendor", "geoserver": "GeoServer" }, + "availableTileGrids": "Tile grids disponibili", + "crsId": "Id", + "projection": "Proiezione", + "tileSize": "Dimensione tile", + "tileGridInUse": "La tile grid '{id}' corrisponde alla proiezione della mappa corrente e alla dimensione delle tile selezionata per questo layer", + "useStandardTileGridStrategy": "Usa tile grid standard", + "useCustomTileGridStrategy": "Usa tile grid personalizzate remote", + "updateTileGrids": "Aggiorna le tile grids disponibili", + "noTileGridMatchesConfiguration": "Non esiste una tile grid che corrisponda alla proiezione della mappa corrente e alla dimensione di tile selezionata. La tabella sopra evidenzia le tile grids disponibili compatibili con la proiezione della mappa corrente. Puoi fare clic sul pulsante Aggiorna per aggiornare l'elenco delle tile grids", + "noConfiguredGridSets": "Non è possibile recuperare tile grids personalizzate. Questo layer non ha alcun tile grid compatibile con le proiezioni disponibili. Per favore controlla la configurazione lato server e riprova", + "notPossibleToConnectToWMTSService": "Non è stato possibile recuperare le tile grids dal server. La richiesta inviata a {requestUrl} sta generando un errore", + "notSupportedSelectedFormatCache": "Il formato selezionato non è supportato dalla cache. Formati supportati: {formats}", + "notSupportedSelectedStyleCache": "Lo stile selezionato non è supportato dalla cache", + "customParamsCacheWarning": "Questo livello ha lo stile localizzato abilitato. Verificare la configurazione lato server ed assicurarsi che supporti il parametro ENV personalizzato", "visibilityLimits": { "title": "Limiti di visibilità", "serverValuesUpdateUndefined": "Il server non ha fornito limiti di visibilità di scala per questo livello", diff --git a/web/client/utils/LayersUtils.js b/web/client/utils/LayersUtils.js index 6721368213..de8cb46e4d 100644 --- a/web/client/utils/LayersUtils.js +++ b/web/client/utils/LayersUtils.js @@ -649,6 +649,9 @@ export const saveLayer = (layer) => { layer.localizedLayerStyles ? { localizedLayerStyles: layer.localizedLayerStyles } : {}, layer.options ? { options: layer.options } : {}, layer.credits ? { credits: layer.credits } : {}, + layer.tileGrids ? { tileGrids: layer.tileGrids } : {}, + layer.tileGridStrategy ? { tileGridStrategy: layer.tileGridStrategy } : {}, + layer.tileGridCacheSupport ? { tileGridCacheSupport: layer.tileGridCacheSupport } : {}, !isNil(layer.forceProxy) ? { forceProxy: layer.forceProxy } : {}); }; diff --git a/web/client/utils/ProjectionUtils.js b/web/client/utils/ProjectionUtils.js index bd4f5f0725..e892853580 100644 --- a/web/client/utils/ProjectionUtils.js +++ b/web/client/utils/ProjectionUtils.js @@ -49,3 +49,10 @@ export const getProjection = (code = 'EPSG:3857') => { const projections = getProjections(); return projections[code] || projections['EPSG:3857']; }; + +/** + * Check if a projection is available + * @param {string} code for the projection + * @return {boolean} true if the projection is available + */ +export const isProjectionAvailable = (code) => !!getProjections()[code]; diff --git a/web/client/utils/WMSUtils.js b/web/client/utils/WMSUtils.js index 278563f2b4..120df9aa37 100644 --- a/web/client/utils/WMSUtils.js +++ b/web/client/utils/WMSUtils.js @@ -7,6 +7,7 @@ */ import { uniq, isObject, castArray } from 'lodash'; import { getAvailableInfoFormat } from "./MapInfoUtils"; +import { normalizeSRS } from "./CoordinatesUtils"; // this list provides the supported GetMap formats // and it will be used to validate GetMap formats coming from capabilities @@ -90,3 +91,24 @@ export const getLayerOptions = function(capabilities) { } : {}; }; + +/** + * Return the tileGrids that matches the srs and tileSize + * @param {object} options + * @param {array} tileGrids array of tileGrids objects + * @param {string} projection projection code + * @param {number} tileSize selected tile size, usually one of 256 or 512 + * @return {object} the selected tileGrid or undefined + */ +export const getTileGridFromLayerOptions = ({ + tileGrids = [], + projection, + tileSize = 256 +}) => { + return tileGrids.find((tileGrid) => + normalizeSRS(tileGrid.crs) === normalizeSRS(projection) + // usually grid has the same tile grid structure + // so we could simplify the check by taking only the first tile width for WMS + && !!((tileGrid.tileSizes?.[0]?.[0] || tileGrid.tileSize?.[0]) === tileSize) + ); +}; diff --git a/web/client/utils/WMTSUtils.js b/web/client/utils/WMTSUtils.js index 2e33167838..bd1c67e164 100644 --- a/web/client/utils/WMTSUtils.js +++ b/web/client/utils/WMTSUtils.js @@ -8,6 +8,7 @@ import { getEquivalentSRS, getEPSGCode } from './CoordinatesUtils'; +import { findGeoServerName, getLayerUrl } from './LayersUtils'; import { isString, isArray, isObject, head, castArray, slice, sortBy } from 'lodash'; @@ -192,6 +193,24 @@ export const getTileMatrix = (_options, srs) => { }; }; +/** + * Generate a WMTS url endpoint based on a WMS GeoServer layer configuration + * @param {object} options layer options + * @return {string} the WMTS url or an empty string + */ +export const generateGeoServerWMTSUrl = (options) => { + const geoServerName = findGeoServerName(options); + if (!geoServerName) { + return ''; + } + const baseUrl = getLayerUrl(options) || ''; + const parts = baseUrl.split(geoServerName); + const layerParts = (options?.name || '').split(':'); + const workspacePath = layerParts.length === 2 ? `${layerParts[0]}/${layerParts[1]}/` : ''; + const wmtsCapabilitiesUrl = `${parts[0]}${geoServerName}${workspacePath}gwc/service/wmts?REQUEST=GetCapabilities`; + return wmtsCapabilitiesUrl; +}; + WMTSUtils = { getOperations, getOperation diff --git a/web/client/utils/__tests__/LayersUtils-test.js b/web/client/utils/__tests__/LayersUtils-test.js index 5e76afdcd9..e690f21a65 100644 --- a/web/client/utils/__tests__/LayersUtils-test.js +++ b/web/client/utils/__tests__/LayersUtils-test.js @@ -1205,6 +1205,40 @@ describe('LayersUtils', () => { expect(l.fields[0].name).toBe("test"); expect(l.fields[0].alias).toBe("test"); } + ], + // save tileGrids and tileGridStrategy + [ + { + tileGridStrategy: 'custom', + tileGrids: [ + { + id: 'EPSG:32122', + crs: 'EPSG:32122', + scales: [ 2557541.55271451, 1278770.776357255, 639385.3881786275 ], + origins: [ [ 403035.4105968763, 414783 ], [ 403035.4105968763, 414783 ], [ 403035.4105968763, 323121 ] ], + tileSize: [ 512, 512 ] + }, + { + id: 'EPSG:900913', + crs: 'EPSG:900913', + scales: [ 559082263.9508929, 279541131.97544646, 139770565.98772323 ], + origin: [ -20037508.34, 20037508 ], + tileSize: [ 256, 256 ] + } + ], + tileGridCacheSupport: { + styles: ['polygon'], + formats: ['image/png'] + } + }, + l => { + expect(l.tileGridStrategy).toBe('custom'); + expect(l.tileGrids.length).toBe(2); + expect(l.tileGridCacheSupport).toEqual({ + styles: ['polygon'], + formats: ['image/png'] + }); + } ] ]; layers.map(([layer, test]) => test(LayersUtils.saveLayer(layer)) ); diff --git a/web/client/utils/__tests__/ProjectionUtils-test.js b/web/client/utils/__tests__/ProjectionUtils-test.js index 6b7579eabf..fd92dd4160 100644 --- a/web/client/utils/__tests__/ProjectionUtils-test.js +++ b/web/client/utils/__tests__/ProjectionUtils-test.js @@ -10,7 +10,8 @@ import expect from 'expect'; import { getProjections, - getProjection + getProjection, + isProjectionAvailable } from '../ProjectionUtils'; import { setConfigProp, removeConfigProp } from '../ConfigUtils'; @@ -57,4 +58,9 @@ describe('CoordinatesUtils', () => { expect(extent).toEqual([ 1241482.0019, 973563.1609, 1830078.9331, 5215189.0853 ]); removeConfigProp('projectionDefs'); }); + it('should detect if a projection is available or not', () => { + expect(isProjectionAvailable('EPSG:4326')).toBe(true); + expect(isProjectionAvailable('EPSG:3857')).toBe(true); + expect(isProjectionAvailable('EPSG:32122')).toBe(false); + }); }); diff --git a/web/client/utils/__tests__/WMSUtils-test.js b/web/client/utils/__tests__/WMSUtils-test.js index d843f3ea76..0be7535833 100644 --- a/web/client/utils/__tests__/WMSUtils-test.js +++ b/web/client/utils/__tests__/WMSUtils-test.js @@ -10,7 +10,8 @@ import { getDefaultSupportedGetFeatureInfoFormats, isValidGetMapFormat, isValidGetFeatureInfoFormat, - getLayerOptions + getLayerOptions, + getTileGridFromLayerOptions } from '../WMSUtils'; describe('Test the WMSUtils', () => { @@ -40,4 +41,26 @@ describe('Test the WMSUtils', () => { availableStyles: [{ name: 'generic' }] }); }); + it('test getTileGridFromLayerOptions', () => { + const tileGrids = [ + { + id: 'EPSG:32122', + crs: 'EPSG:32122', + scales: [ 2557541.55271451, 1278770.776357255, 639385.3881786275 ], + origins: [ [ 403035.4105968763, 414783 ], [ 403035.4105968763, 414783 ], [ 403035.4105968763, 323121 ] ], + tileSizes: [[ 512, 512 ], [ 512, 512 ], [ 512, 512 ]] + }, + { + id: 'EPSG:900913', + crs: 'EPSG:900913', + scales: [ 559082263.9508929, 279541131.97544646, 139770565.98772323 ], + origin: [ -20037508.34, 20037508 ], + tileSize: [ 256, 256 ] + } + ]; + expect(getTileGridFromLayerOptions({ projection: 'EPSG:3857', tileSize: 256, tileGrids })).toBe(tileGrids[1]); + expect(getTileGridFromLayerOptions({ projection: 'EPSG:3857', tileSize: 512, tileGrids })).toBe(undefined); + expect(getTileGridFromLayerOptions({ projection: 'EPSG:32122', tileSize: 512, tileGrids })).toBe(tileGrids[0]); + expect(getTileGridFromLayerOptions({ projection: 'EPSG:4326', tileSize: 256, tileGrids })).toBe(undefined); + }); }); diff --git a/web/client/utils/__tests__/WMTSUtils-test.js b/web/client/utils/__tests__/WMTSUtils-test.js index a9532b1875..4d6d4f816a 100644 --- a/web/client/utils/__tests__/WMTSUtils-test.js +++ b/web/client/utils/__tests__/WMTSUtils-test.js @@ -156,4 +156,10 @@ describe('Test the WMTSUtils', () => { }); }); + it('generateGeoServerWMTSUrl', () => { + expect(WMTSUtils.generateGeoServerWMTSUrl({ url: '/geoserver/wms' })).toBe('/geoserver/gwc/service/wmts?REQUEST=GetCapabilities'); + expect(WMTSUtils.generateGeoServerWMTSUrl({ url: '/geoserver/wms', name: 'workspace:name' })).toBe('/geoserver/workspace/name/gwc/service/wmts?REQUEST=GetCapabilities'); + expect(WMTSUtils.generateGeoServerWMTSUrl({ url: '/geoserver/wms', name: 'name' })).toBe('/geoserver/gwc/service/wmts?REQUEST=GetCapabilities'); + expect(WMTSUtils.generateGeoServerWMTSUrl({ url: '/wms' })).toBe(''); + }); });