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

Report WMS scale denominators (scale hints) #70

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
10 changes: 7 additions & 3 deletions fixtures/wms/capabilities-brgm-1-1-1.xml
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@
<OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://mapsref.brgm.fr/legendes/geoservices/Geologie1000_legende_other.jpg"/>
</LegendURL>
</Style>
<ScaleHint min="74.8354272644456" max="3741.77136322228" />
<ScaleHint min="79.19595949289331" max="3959.7979746446654" />
</Layer>
<Layer queryable="true" opaque="true" cascaded="0">
<Name>SCAN_F_GEOL250</Name>
Expand Down Expand Up @@ -273,7 +273,7 @@
<Format>text/xml</Format>
<OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://www.geocatalogue.fr/api-public/servicesRest?Service=CSW&amp;Request=GetRecordById&amp;Version=2.0.2&amp;id=BR_CAR_ACA&amp;outputSchema=http://www.isotc211.org/2005/gmd&amp;elementSetName=full"/>
</MetadataURL>
<ScaleHint min="29.9341709057782" max="187.088568161114" />
<ScaleHint min="31.678383797157327" max="197.9898987322333" />
</Layer>
<Layer queryable="1" opaque="1" cascaded="0">
<Name>SCAN_D_GEOL50</Name>
Expand Down Expand Up @@ -308,7 +308,11 @@
<Format>text/xml</Format>
<OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://www.geocatalogue.fr/api-public/servicesRest?Service=CSW&amp;Request=GetRecordById&amp;Version=2.0.2&amp;id=72cc8d40-1bb6-41a3-8376-9734f23336ff&amp;outputSchema=http://www.isotc211.org/2005/gmd&amp;elementSetName=full"/>
</MetadataURL>
<ScaleHint min="3.36759422690005" max="93.9184612168792" />
<ScaleHint min="3.563818177180199" max="99.3909291635811" />
<Layer>
<Name>INHERIT_SCALE</Name>
<Title>Inherited scale denominators</Title>
</Layer>
</Layer>
<Layer queryable="0" opaque="0" cascaded="0">
<Name>INHERIT_BBOX</Name>
Expand Down
4 changes: 4 additions & 0 deletions fixtures/wms/capabilities-brgm-1-3-0.xml
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,10 @@
</MetadataURL>
<MinScaleDenominator>9000</MinScaleDenominator>
<MaxScaleDenominator>251000</MaxScaleDenominator>
<Layer>
<Name>INHERIT_SCALE</Name>
<Title>Inherited scale denominators</Title>
</Layer>
</Layer>
<Layer queryable="0" opaque="0" cascaded="0">
<Name>INHERIT_BBOX</Name>
Expand Down
69 changes: 68 additions & 1 deletion src/wms/capabilities.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import capabilities130 from '../../fixtures/wms/capabilities-brgm-1-3-0.xml';
// @ts-expect-error ts-migrate(7016)
import capabilities111 from '../../fixtures/wms/capabilities-brgm-1-1-1.xml';
import { parseXmlString } from '../shared/xml-utils.js';
import type { WmsLayerFull } from './model.js';

describe('WMS capabilities', () => {
describe('readVersionFromCapabilities', () => {
Expand Down Expand Up @@ -132,6 +133,8 @@ describe('WMS capabilities', () => {
'EPSG:4326': ['-5.86764', '41.1701', '11.0789', '51.1419'],
},
keywords: ['Geologie', 'INSPIRE:Geology', 'Geology'],
maxScaleDenominator: 1e7,
minScaleDenominator: 200000,
name: 'SCAN_F_GEOL1M',
queryable: false,
opaque: false,
Expand Down Expand Up @@ -185,6 +188,8 @@ describe('WMS capabilities', () => {
'EPSG:4326': ['-6.20495', '41.9671', '12.2874', '51.2917'],
},
keywords: ['Geologie', 'INSPIRE:Geology', 'Geology'],
maxScaleDenominator: 500000,
minScaleDenominator: 80000,
name: 'SCAN_F_GEOL250',
queryable: true,
opaque: true,
Expand Down Expand Up @@ -225,11 +230,56 @@ describe('WMS capabilities', () => {
'EPSG:4326': ['-12.2064', '40.681', '11.894', '52.1672'],
},
keywords: ['Geologie', 'INSPIRE:Geology', 'Geology'],
maxScaleDenominator: 251000,
minScaleDenominator: 9000,
name: 'SCAN_D_GEOL50',
queryable: true,
opaque: true,
styles,
title: 'Carte géologique image de la France au 1/50 000e',
children: [
{
abstract: '',
attribution,
availableCrs: [
'EPSG:4326',
'EPSG:3857',
'CRS:84',
'EPSG:32620',
'EPSG:32621',
],
boundingBoxes: {
'CRS:84': ['-12.2064', '40.681', '11.894', '52.1672'],
'EPSG:32620': [
'3.88148e+06',
'6.13796e+06',
'6.31307e+06',
'8.70752e+06',
],
'EPSG:32621': [
'3.52434e+06',
'5.74736e+06',
'5.97375e+06',
'8.23867e+06',
],
'EPSG:3857': [
'-1.35881e+06',
'4.96541e+06',
'1.32403e+06',
'6.83041e+06',
],
'EPSG:4326': ['-12.2064', '40.681', '11.894', '52.1672'],
},
keywords: [],
maxScaleDenominator: 251000,
minScaleDenominator: 9000,
name: 'INHERIT_SCALE',
queryable: false,
opaque: false,
styles,
title: 'Inherited scale denominators',
},
],
},
{
abstract: '',
Expand Down Expand Up @@ -275,7 +325,9 @@ describe('WMS capabilities', () => {
});
it('reads the layers (1.1.1)', () => {
const doc = parseXmlString(capabilities111);
expect(readLayersFromCapabilities(doc)).toEqual(expectedLayers);
expect(
readLayersFromCapabilities(doc).map(fixupScaleDenominators)
).toEqual(expectedLayers);
});
});

Expand Down Expand Up @@ -319,3 +371,18 @@ describe('WMS capabilities', () => {
});
});
});

/**
* Round scale denominators to avoid problems with floating point precision
* @param layer
*/
function fixupScaleDenominators(layer: WmsLayerFull): WmsLayerFull {
if (layer.minScaleDenominator !== undefined) {
layer.minScaleDenominator = Math.round(layer.minScaleDenominator);
}
if (layer.maxScaleDenominator !== undefined) {
layer.maxScaleDenominator = Math.round(layer.maxScaleDenominator);
}
layer.children?.forEach(fixupScaleDenominators);
return layer;
}
55 changes: 53 additions & 2 deletions src/wms/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ function parseLayer(
inheritedSrs: CrsCode[] = [],
inheritedStyles: LayerStyle[] = [],
inheritedAttribution: WmsLayerAttribution = null,
inheritedBoundingBoxes: Record<CrsCode, BoundingBox> = null
inheritedBoundingBoxes: Record<CrsCode, BoundingBox> = null,
inheritedMaxScaleDenom: number = null,
inheritedMinScaleDenom: number = null
): WmsLayerFull {
const srsTag = version === '1.3.0' ? 'CRS' : 'SRS';
const srsList = findChildrenElement(layerEl, srsTag).map(getElementText);
Expand Down Expand Up @@ -124,6 +126,30 @@ function parseLayer(
getElementAttribute(bboxEl, name)
);
}
function parseScaleHintValue(textValue, defaultValue) {
if (textValue === '') {
return defaultValue;
}
// convert resolution to scale denominator using the common pixel size of
// 0.28×0.28 mm as defined in WMS 1.3.0 specification section 7.2.4.6.9
return Math.sqrt(0.5 * parseFloat(textValue) ** 2) / 0.00028;
}
function parseScaleHint() {
const scaleHint = findChildElement(layerEl, 'ScaleHint');
if (!scaleHint) {
return [inheritedMinScaleDenom, inheritedMaxScaleDenom];
}
const min = getElementAttribute(scaleHint, 'min');
const max = getElementAttribute(scaleHint, 'max');
return [
parseScaleHintValue(min, inheritedMinScaleDenom),
parseScaleHintValue(max, inheritedMaxScaleDenom),
];
}
function parseScaleDenominator(name, inheritedValue) {
const textValue = getElementText(findChildElement(layerEl, name));
return textValue === '' ? inheritedValue : parseFloat(textValue);
}
const attributionEl = findChildElement(layerEl, 'Attribution');
const attribution =
attributionEl !== null
Expand Down Expand Up @@ -169,8 +195,31 @@ function parseLayer(
.map(getElementText)
.filter((v, i, arr) => arr.indexOf(v) === i);

let minScaleDenominator, maxScaleDenominator;
if (version === '1.3.0') {
minScaleDenominator = parseScaleDenominator(
'MinScaleDenominator',
inheritedMinScaleDenom
);
maxScaleDenominator = parseScaleDenominator(
'MaxScaleDenominator',
inheritedMaxScaleDenom
);
} else {
[minScaleDenominator, maxScaleDenominator] = parseScaleHint();
}

const children = findChildrenElement(layerEl, 'Layer').map((layer) =>
parseLayer(layer, version, availableCrs, styles, attribution, boundingBoxes)
parseLayer(
layer,
version,
availableCrs,
styles,
attribution,
boundingBoxes,
maxScaleDenominator,
minScaleDenominator
)
);
return {
name: getElementText(findChildElement(layerEl, 'Name')),
Expand All @@ -183,6 +232,8 @@ function parseLayer(
keywords,
queryable,
opaque,
...(minScaleDenominator !== null ? { minScaleDenominator } : {}),
...(maxScaleDenominator !== null ? { maxScaleDenominator } : {}),
...(children.length && { children }),
};
}
Expand Down
7 changes: 7 additions & 0 deletions src/wms/endpoint.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ describe('WmsEndpoint', () => {
"BD Scan-Géol-50 est la base de données géoréférencées des cartes géologiques 'papier' à 1/50 000",
name: 'SCAN_D_GEOL50',
title: 'Carte géologique image de la France au 1/50 000e',
children: [
{
abstract: '',
name: 'INHERIT_SCALE',
title: 'Inherited scale denominators',
},
],
},
{
abstract: '',
Expand Down
2 changes: 2 additions & 0 deletions src/wms/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export type WmsLayerFull = {
boundingBoxes: Record<CrsCode, BoundingBox>;
queryable: boolean;
opaque: boolean;
maxScaleDenominator?: number;
minScaleDenominator?: number;
attribution?: WmsLayerAttribution;
keywords?: string[];
/**
Expand Down