-
Notifications
You must be signed in to change notification settings - Fork 33
Add WMS DescribeLayer operation support #137
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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <!DOCTYPE WMS_DescribeLayerResponse SYSTEM "http://schemas.opengis.net/wms/1.1.1/WMS_DescribeLayerResponse.dtd"> | ||
| <WMS_DescribeLayerResponse version="1.1.1"> | ||
| <LayerDescription name="my_workspace:my_vector_layer" wfs="https://my-server.com/wfs?" owsURL="https://my-server.com/wfs?" owsType="WFS"> | ||
| <Query typeName="my_workspace:my_vector_layer"/> | ||
| </LayerDescription> | ||
| <LayerDescription name="my_workspace:my_raster_layer" owsURL="https://my-server.com/wcs?" owsType="WCS"> | ||
| <Query typeName="my_workspace:my_raster_layer"/> | ||
| </LayerDescription> | ||
| </WMS_DescribeLayerResponse> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import { parseDescribeLayerResponse } from './describelayer.js'; | ||
| // @ts-expect-error ts-migrate(7016) | ||
| import describeLayerResponse from '../../fixtures/wms/describelayer-response.xml'; | ||
| import { parseXmlString } from '../shared/xml-utils.js'; | ||
|
|
||
| describe('WMS DescribeLayer', () => { | ||
| describe('parseDescribeLayerResponse', () => { | ||
| it('parses a vector layer description (owsType WFS)', () => { | ||
| const doc = parseXmlString(describeLayerResponse); | ||
| const result = parseDescribeLayerResponse( | ||
| doc, | ||
| 'my_workspace:my_vector_layer' | ||
| ); | ||
| expect(result).toEqual({ | ||
| layerName: 'my_workspace:my_vector_layer', | ||
| owsType: 'WFS', | ||
| owsUrl: 'https://my-server.com/wfs?', | ||
| typeName: 'my_workspace:my_vector_layer', | ||
| }); | ||
| }); | ||
|
|
||
| it('parses a raster layer description (owsType WCS)', () => { | ||
| const doc = parseXmlString(describeLayerResponse); | ||
| const result = parseDescribeLayerResponse( | ||
| doc, | ||
| 'my_workspace:my_raster_layer' | ||
| ); | ||
| expect(result).toEqual({ | ||
| layerName: 'my_workspace:my_raster_layer', | ||
| owsType: 'WCS', | ||
| owsUrl: 'https://my-server.com/wcs?', | ||
| typeName: 'my_workspace:my_raster_layer', | ||
| }); | ||
| }); | ||
|
|
||
| it('returns null when the layer is not found in the response', () => { | ||
| const doc = parseXmlString(describeLayerResponse); | ||
| const result = parseDescribeLayerResponse(doc, 'nonexistent:layer'); | ||
| expect(result).toBeNull(); | ||
| }); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import { XmlDocument } from '@rgrove/parse-xml'; | ||
| import { | ||
| findChildElement, | ||
| findChildrenElement, | ||
| getElementAttribute, | ||
| getRootElement, | ||
| } from '../shared/xml-utils.js'; | ||
| import { WmsLayerDescription } from './model.js'; | ||
|
|
||
| /** | ||
| * Parses a WMS DescribeLayer response document and returns the description | ||
| * for the requested layer. | ||
| * @param describeLayerDoc The parsed XML document from a DescribeLayer response | ||
| * @param layerName The layer name to look for in the response | ||
| * @return The layer description, or null if the layer was not found in the response | ||
| */ | ||
| export function parseDescribeLayerResponse( | ||
| describeLayerDoc: XmlDocument, | ||
| layerName: string | ||
| ): WmsLayerDescription | null { | ||
| const root = getRootElement(describeLayerDoc); | ||
| const layerDescriptions = findChildrenElement(root, 'LayerDescription'); | ||
|
|
||
| const match = layerDescriptions.find( | ||
| (el) => getElementAttribute(el, 'name') === layerName | ||
| ); | ||
| if (!match) return null; | ||
|
|
||
| const owsType = getElementAttribute(match, 'owsType'); | ||
| const owsUrl = | ||
| getElementAttribute(match, 'owsURL') || getElementAttribute(match, 'wfs'); | ||
|
|
||
| const queryEl = findChildElement(match, 'Query'); | ||
| const typeName = queryEl | ||
| ? getElementAttribute(queryEl, 'typeName') | ||
| : undefined; | ||
|
|
||
| return { | ||
| layerName, | ||
| owsType, | ||
| owsUrl, | ||
| ...(typeName ? { typeName } : {}), | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| import { parseWmsCapabilities } from '../worker/index.js'; | ||
| import { useCache } from '../shared/cache.js'; | ||
| import { setQueryParams } from '../shared/http-utils.js'; | ||
| import { queryXmlDocument, setQueryParams } from '../shared/http-utils.js'; | ||
| import { | ||
| BoundingBox, | ||
| CrsCode, | ||
|
|
@@ -10,8 +10,14 @@ import { | |
| type OperationName, | ||
| type OperationUrl, | ||
| } from '../shared/models.js'; | ||
| import { WmsLayerFull, WmsLayerSummary, WmsVersion } from './model.js'; | ||
| import { generateGetMapUrl } from './url.js'; | ||
| import { | ||
| WmsLayerDescription, | ||
| WmsLayerFull, | ||
| WmsLayerSummary, | ||
| WmsVersion, | ||
| } from './model.js'; | ||
| import { generateDescribeLayerUrl, generateGetMapUrl } from './url.js'; | ||
| import { parseDescribeLayerResponse } from './describelayer.js'; | ||
|
|
||
| /** | ||
| * Represents a WMS endpoint advertising several layers arranged in a tree structure. | ||
|
|
@@ -200,6 +206,38 @@ export default class WmsEndpoint { | |
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Performs a DescribeLayer request for the given layer and returns its description, | ||
| * including the underlying OWS type (e.g. "WFS" for vector data). | ||
| * @param layerName Layer name to describe | ||
| * @return Returns null if the endpoint is not ready or does not advertise DescribeLayer | ||
| */ | ||
| describeLayer(layerName: string): Promise<WmsLayerDescription | null> | null { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make it so that a Promise is always returned, otherwise the API is very awkward |
||
| if (!this._layers) { | ||
| return null; | ||
| } | ||
| const describeLayerBaseUrl = this.getOperationUrl('DescribeLayer'); | ||
| if (!describeLayerBaseUrl) { | ||
| return null; | ||
| } | ||
| return useCache( | ||
| () => { | ||
| const url = generateDescribeLayerUrl( | ||
| describeLayerBaseUrl, | ||
| this._version, | ||
| layerName | ||
| ); | ||
| return queryXmlDocument(url).then((doc) => | ||
| parseDescribeLayerResponse(doc, layerName) | ||
| ); | ||
| }, | ||
| 'WMS', | ||
| 'DESCRIBELAYER', | ||
| this._capabilitiesUrl, | ||
| layerName | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Returns the URL reported by the WMS for the given operation | ||
| * @param operationName e.g. GetMap, GetCapabilities, etc. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,4 +50,11 @@ export type WmsLayerFull = { | |
| children?: WmsLayerFull[]; | ||
| }; | ||
|
|
||
| export type WmsLayerDescription = { | ||
| layerName: string; | ||
| owsType: string; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this can be typed as |
||
| owsUrl: string; | ||
| typeName?: string; | ||
| }; | ||
|
|
||
| export type WmsVersion = '1.1.0' | '1.1.1' | '1.3.0'; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,3 +42,22 @@ export function generateGetMapUrl( | |
|
|
||
| return setQueryParams(serviceUrl, newParams); | ||
| } | ||
|
|
||
| /** | ||
| * Generates an URL for a DescribeLayer operation | ||
| * @param serviceUrl | ||
| * @param version | ||
| * @param layerName Layer name to describe | ||
| */ | ||
| export function generateDescribeLayerUrl( | ||
| serviceUrl: string, | ||
| version: WmsVersion, | ||
| layerName: string | ||
| ): string { | ||
| return setQueryParams(serviceUrl, { | ||
| SERVICE: 'WMS', | ||
| REQUEST: 'DescribeLayer', | ||
| VERSION: version, | ||
| LAYERS: layerName, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. needs a SLD_VERSION=1.1.0 param here |
||
| }); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this fixture looks like a real response. For instance this is the response example from the spec:
Could you maybe do two different fixtures, one for WCS and one for WFS? Also slightly different, e.g. coming from different services. Thank you!