Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #9025 WMS caching with custom scales (custom resolutions strategy from WMTS) #9184

Merged
merged 5 commits into from
May 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 45 additions & 1 deletion web/client/api/WMTS.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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 || [])
allyoucanmap marked this conversation as resolved.
Show resolved Hide resolved
};
});
};

export default Api;
177 changes: 176 additions & 1 deletion web/client/api/__tests__/WMTS-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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, `<?xml version="1.0" encoding="UTF-8"?>
<Capabilities xmlns="http://www.opengis.net/wmts/1.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gml="http://www.opengis.net/gml" xsi:schemaLocation="http://www.opengis.net/wmts/1.0 http://schemas.opengis.net/wmts/1.0/wmtsGetCapabilities_response.xsd" version="1.0.0">
<Contents>
<Layer>
<ows:Title>USA Population</ows:Title>
<ows:Abstract>This is some census data on the states.</ows:Abstract>
<ows:WGS84BoundingBox>
<ows:LowerCorner>-124.73142200000001 24.955967</ows:LowerCorner>
<ows:UpperCorner>-66.969849 49.371735</ows:UpperCorner>
</ows:WGS84BoundingBox>
<ows:Identifier>states</ows:Identifier>
<Style isDefault="true">
<ows:Identifier>population</ows:Identifier>
<LegendURL format="image/png" xlink:href="/geoserver/topp/states/ows?service=WMS&amp;request=GetLegendGraphic&amp;format=image%2Fpng&amp;width=20&amp;height=20&amp;layer=topp%3Astates" width="20" height="20"/>
</Style>
<Format>image/png</Format>
<Format>image/jpeg</Format>
<InfoFormat>text/plain</InfoFormat>
<InfoFormat>application/vnd.ogc.gml</InfoFormat>
<InfoFormat>text/xml</InfoFormat>
<InfoFormat>application/vnd.ogc.gml/3.1.1</InfoFormat>
<InfoFormat>text/xml</InfoFormat>
<InfoFormat>text/html</InfoFormat>
<InfoFormat>application/json</InfoFormat>
<TileMatrixSetLink>
<TileMatrixSet>EPSG:32122</TileMatrixSet>
</TileMatrixSetLink>
<TileMatrixSetLink>
<TileMatrixSet>EPSG:900913</TileMatrixSet>
</TileMatrixSetLink>
</Layer>
<TileMatrixSet>
<ows:Identifier>EPSG:32122</ows:Identifier>
<ows:SupportedCRS>urn:ogc:def:crs:EPSG::32122</ows:SupportedCRS>
<TileMatrix>
<ows:Identifier>EPSG:32122:0</ows:Identifier>
<ScaleDenominator>2557541.55271451</ScaleDenominator>
<TopLeftCorner>403035.4105968763 414783.0</TopLeftCorner>
<TileWidth>512</TileWidth>
<TileHeight>512</TileHeight>
<MatrixWidth>1</MatrixWidth>
<MatrixHeight>1</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>EPSG:32122:1</ows:Identifier>
<ScaleDenominator>1278770.776357255</ScaleDenominator>
<TopLeftCorner>403035.4105968763 414783.0</TopLeftCorner>
<TileWidth>512</TileWidth>
<TileHeight>512</TileHeight>
<MatrixWidth>2</MatrixWidth>
<MatrixHeight>2</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>EPSG:32122:2</ows:Identifier>
<ScaleDenominator>639385.3881786275</ScaleDenominator>
<TopLeftCorner>403035.4105968763 323121.0</TopLeftCorner>
<TileWidth>512</TileWidth>
<TileHeight>512</TileHeight>
<MatrixWidth>4</MatrixWidth>
<MatrixHeight>3</MatrixHeight>
</TileMatrix>
</TileMatrixSet>
<TileMatrixSet>
<ows:Identifier>EPSG:4326</ows:Identifier>
<ows:SupportedCRS>urn:ogc:def:crs:EPSG::4326</ows:SupportedCRS>
<TileMatrix>
<ows:Identifier>EPSG:4326:0</ows:Identifier>
<ScaleDenominator>2.795411320143589E8</ScaleDenominator>
<TopLeftCorner>90.0 -180.0</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>2</MatrixWidth>
<MatrixHeight>1</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>EPSG:4326:1</ows:Identifier>
<ScaleDenominator>1.3977056600717944E8</ScaleDenominator>
<TopLeftCorner>90.0 -180.0</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>4</MatrixWidth>
<MatrixHeight>2</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>EPSG:4326:2</ows:Identifier>
<ScaleDenominator>6.988528300358972E7</ScaleDenominator>
<TopLeftCorner>90.0 -180.0</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>8</MatrixWidth>
<MatrixHeight>4</MatrixHeight>
</TileMatrix>
</TileMatrixSet>
<TileMatrixSet>
<ows:Identifier>EPSG:900913</ows:Identifier>
<ows:SupportedCRS>urn:ogc:def:crs:EPSG::900913</ows:SupportedCRS>
<TileMatrix>
<ows:Identifier>EPSG:900913:0</ows:Identifier>
<ScaleDenominator>5.590822639508929E8</ScaleDenominator>
<TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>1</MatrixWidth>
<MatrixHeight>1</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>EPSG:900913:1</ows:Identifier>
<ScaleDenominator>2.7954113197544646E8</ScaleDenominator>
<TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>2</MatrixWidth>
<MatrixHeight>2</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>EPSG:900913:2</ows:Identifier>
<ScaleDenominator>1.3977056598772323E8</ScaleDenominator>
<TopLeftCorner>-2.003750834E7 2.0037508E7</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>4</MatrixWidth>
<MatrixHeight>4</MatrixHeight>
</TileMatrix>
</TileMatrixSet>
</Contents>
<ServiceMetadataURL xlink:href="/geoserver/topp/states/gwc/service/wmts?SERVICE=wmts&amp;REQUEST=getcapabilities&amp;VERSION=1.0.0"/>
<ServiceMetadataURL xlink:href="/geoserver/topp/states/gwc/service/wmts/rest/WMTSCapabilities.xml"/>
</Capabilities>`);
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);
});
});
21 changes: 13 additions & 8 deletions web/client/components/TOC/fragments/settings/Display.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand Down Expand Up @@ -225,13 +226,6 @@ export default class extends React.Component {
<FormGroup>
<Checkbox key="transparent" checked={this.props.element && (this.props.element.transparent === undefined ? true : this.props.element.transparent)} onChange={(event) => {this.props.onChange("transparent", event.target.checked); }}>
<Message msgId="layerProperties.transparent"/></Checkbox>
{(this.props.element?.serverType !== ServerTypes.NO_VENDOR && (
<Checkbox value="tiled" key="tiled"
disabled={!!this.props.element.singleTile}
onChange={(e) => this.props.onChange("tiled", e.target.checked)}
checked={this.props.element && this.props.element.tiled !== undefined ? this.props.element.tiled : true} >
<Message msgId="layerProperties.cached"/>
</Checkbox>))}
<Checkbox key="singleTile" value="singleTile"
checked={this.props.element && (this.props.element.singleTile !== undefined ? this.props.element.singleTile : false )}
onChange={(e) => this.props.onChange("singleTile", e.target.checked)}>
Expand All @@ -252,6 +246,17 @@ export default class extends React.Component {
checked={this.props.element.forceProxy} >
<Message msgId="layerProperties.forceProxy"/>
</Checkbox>)}
{(this.props.element?.serverType !== ServerTypes.NO_VENDOR && (
<>
<hr/>
<WMSCacheOptions
layer={this.props.element}
projection={this.props.projection}
onChange={this.props.onChange}
disableTileGrids={!!this.props.isCesiumActive}
/>
</>
))}
</FormGroup>
</Col>
<div className={"legend-options"}>
Expand Down
Loading