Skip to content

Commit 92512a8

Browse files
committed
BGDIINF_SB-3180: External layers load attributes from capabilities on page reload
Now the external layers will automatically reload their Get Capabilities in order to retrieved their attributes used for display upon page reload. This also allow to save the minimal informations on the URL which is the type, the base url and the layer ID.
1 parent e1d7992 commit 92512a8

24 files changed

+695
-102
lines changed

src/api/layers/AbstractLayer.class.js

+15-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ export class LayerAttribution {
1616
}
1717
}
1818

19+
/**
20+
* Get the default layer attributions based on URL
21+
*
22+
* @param {string} baseUrl Get Capabilities base URL
23+
* @returns {LayerAttribution[]} Default list of layer attributions
24+
*/
25+
export function getDefaultAttribution(baseUrl) {
26+
return [new LayerAttribution(new URL(baseUrl).hostname)]
27+
}
28+
1929
/**
2030
* Base class for layers' config description, must be extended to a more specific flavor of Layer
2131
* (e.g. {@link GeoAdminWMTSLayer}, {@link GeoAdminWMSLayer}, {@link GeoAdminGeoJsonLayer},
@@ -34,6 +44,8 @@ export default class AbstractLayer {
3444
* @param {Boolean} hasTooltip Define if this layer shows tooltip when clicked on
3545
* @param {Boolean} isExternal Define if this layer comes from our backend, or is from another
3646
* (external) source
47+
* @param {boolean} isLoading Set to true if some parts of the layer (e.g. metadata) are still
48+
* loading
3749
*/
3850
constructor(
3951
name = '',
@@ -42,7 +54,8 @@ export default class AbstractLayer {
4254
visible = false,
4355
attributions = [],
4456
hasTooltip = false,
45-
isExternal = false
57+
isExternal = false,
58+
isLoading = false
4659
) {
4760
this.name = name
4861
this.type = type
@@ -51,6 +64,7 @@ export default class AbstractLayer {
5164
this.attributions = [...attributions]
5265
this.hasTooltip = hasTooltip
5366
this.isExternal = isExternal
67+
this.isLoading = isLoading
5468
}
5569

5670
/**

src/api/layers/ExternalGroupOfLayers.class.js

+28-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ExternalLayer from '@/api/layers/ExternalLayer.class'
22
import LayerTypes from '@/api/layers/LayerTypes.enum'
3+
import { getDefaultAttribution } from '@/api/layers/AbstractLayer.class'
34

45
/**
56
* Description of a group of layers, that could be added altogether or separately, that stems from a
@@ -12,33 +13,49 @@ import LayerTypes from '@/api/layers/LayerTypes.enum'
1213
export default class ExternalGroupOfLayers extends ExternalLayer {
1314
/**
1415
* @param {String} name Name of this layer to be shown to the user
15-
* @param {String} hostname GetCapabilities URL host name, so that it can be used in the ID
16-
* generation
16+
* @param {number} opacity The opacity of this layer, between 0.0 (transparent) and 1.0 (opaque)
17+
* @param {boolean} visible If the layer should be shown on the map
18+
* @param {String} baseUrl GetCapabilities base URL
19+
* @param {String} layerId Layer ID of the group to be found in GetCapabilities
1720
* @param {ExternalLayer[]} layers Description of the layers being part of this group (they will
1821
* all be displayed at the same time, in contrast to an aggregate layer)
1922
* @param {String} abstract Abstract of this layer to be shown to the user
2023
* @param {LayerAttribution[]} attributions Description of the data owner(s) for this layer
21-
* @param {[[number, number], [number, number]] | undefined} extent Layer extent
24+
* @param {[[number, number], [number, number]] | null} extent Layer extent
25+
* @param {boolean} isLoading Set to true if some parts of the layer (e.g. metadata) are still
26+
* loading
2227
*/
23-
constructor(name, hostname, layers, attributions = [], abstract = '', extent = undefined) {
28+
constructor(
29+
name,
30+
opacity,
31+
visible,
32+
baseUrl,
33+
layerId,
34+
layers = [],
35+
attributions = getDefaultAttribution(baseUrl),
36+
abstract = '',
37+
extent = null,
38+
isLoading = true
39+
) {
2440
super(
2541
name,
2642
LayerTypes.GROUP,
27-
`${hostname}:${name.replaceAll(' ', '_')}`,
28-
null,
29-
1,
30-
true,
43+
layerId,
44+
baseUrl,
45+
opacity,
46+
visible,
3147
attributions,
3248
abstract,
33-
extent
49+
extent,
50+
isLoading
3451
)
3552
this.layers = [...layers]
3653
}
3754

3855
getID() {
39-
return this.externalLayerId
56+
// format coming from https://github.com/geoadmin/web-mapviewer/blob/develop/adr/2021_03_16_url_param_structure.md
57+
return `GRP|${this.baseURL}|${this.externalLayerId}`
4058
}
41-
4259
clone() {
4360
let clone = super.clone()
4461
clone.layers = this.layers.map((layer) => layer.clone())

src/api/layers/ExternalLayer.class.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import AbstractLayer from '@/api/layers/AbstractLayer.class'
2+
import { getDefaultAttribution } from '@/api/layers/AbstractLayer.class'
23

34
/**
45
* Base for all external layers, defining a flag to diferentiate them from GeoAdminLayers
@@ -16,7 +17,9 @@ export default class ExternalLayer extends AbstractLayer {
1617
* @param {boolean} visible If the layer should be visible on the map
1718
* @param {LayerAttribution[]} attributions Description of the data owner(s) for this layer
1819
* @param {String} abstract Abstract of this layer to be shown to the user
19-
* @param {[[number, number], [number, number]] | undefined} extent Layer extent
20+
* @param {[[number, number], [number, number]] | null} extent Layer extent
21+
* @param {boolean} isLoading Set to true if some parts of the layer (e.g. metadata) are still
22+
* loading
2023
*/
2124
constructor(
2225
name,
@@ -25,15 +28,17 @@ export default class ExternalLayer extends AbstractLayer {
2528
baseURL,
2629
opacity,
2730
visible,
28-
attributions = [],
31+
attributions = getDefaultAttribution(baseUrl),
2932
abstract = '',
30-
extent = undefined
33+
extent = null,
34+
isLoading = true
3135
) {
3236
super(name, layerType, opacity, visible, attributions, false, true)
3337
this.externalLayerId = externalLayerId
3438
this.baseURL = baseURL
3539
this.abstract = abstract
3640
this.extent = extent
41+
this.isLoading = isLoading
3742
}
3843

3944
getURL() {
+13-9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { getDefaultAttribution } from '@/api/layers/AbstractLayer.class'
12
import ExternalLayer from '@/api/layers/ExternalLayer.class'
23
import LayerTypes from '@/api/layers/LayerTypes.enum'
34

@@ -7,39 +8,42 @@ export default class ExternalWMSLayer extends ExternalLayer {
78
* @param {String} name Name of this layer to be shown to the user
89
* @param {number} opacity The opacity of this layer, between 0.0 (transparent) and 1.0 (opaque)
910
* @param {boolean} visible If the layer should be shown on the map
10-
* @param {String} serverBaseURL Base URL to build WMS requests (no endpoint / URL param
11-
* defined)
11+
* @param {String} baseURL Base URL to build WMS requests (no endpoint / URL param defined)
1212
* @param {String} layerId Layer ID to use when requesting the tiles on the server
1313
* @param {String} wmsVersion WMS protocol version to be used when querying this server, default
1414
* is 1.3.0
1515
* @param {LayerAttribution[]} attributions Description of the data owner(s) for this layer
1616
* holder (it typically is the hostname of the server for this layer)
1717
* @param {String} format Image format for this layer, default is PNG
1818
* @param {String} abstract Abstract of this layer to be shown to the user
19-
* @param {[[number, number], [number, number]] | undefined} extent Layer extent
19+
* @param {[[number, number], [number, number]] | null} extent Layer extent
20+
* @param {boolean} isLoading Set to true if some parts of the layer (e.g. metadata) are still
21+
* loading
2022
*/
2123
constructor(
2224
name,
2325
opacity,
2426
visible,
25-
serverBaseURL,
27+
baseURL,
2628
layerId,
27-
attributions,
29+
attributions = getDefaultAttribution(baseURL),
2830
wmsVersion = '1.3.0',
2931
format = 'png',
3032
abstract = '',
31-
extent = undefined
33+
extent = null,
34+
isLoading = true
3235
) {
3336
super(
3437
name,
3538
LayerTypes.WMS,
3639
layerId,
37-
serverBaseURL,
40+
baseURL,
3841
opacity,
3942
visible,
4043
attributions,
4144
abstract,
42-
extent
45+
extent,
46+
isLoading
4347
)
4448
this.wmsVersion = wmsVersion
4549
this.format = format
@@ -48,6 +52,6 @@ export default class ExternalWMSLayer extends ExternalLayer {
4852
getID() {
4953
// format coming from https://github.com/geoadmin/web-mapviewer/blob/develop/adr/2021_03_16_url_param_structure.md
5054
// base URL and name must be URL encoded (no & signs or other reserved URL chars must pass, or it could break URL param parsing)
51-
return `WMS|${this.baseURL}|${this.externalLayerId}|${this.wmsVersion}|${this.name}`
55+
return `WMS|${this.baseURL}|${this.externalLayerId}`
5256
}
5357
}
+14-10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { getDefaultAttribution } from '@/api/layers/AbstractLayer.class'
12
import ExternalLayer from '@/api/layers/ExternalLayer.class'
23
import LayerTypes from '@/api/layers/LayerTypes.enum'
34

@@ -10,38 +11,41 @@ export default class ExternalWMTSLayer extends ExternalLayer {
1011
* @param {String} name Name of this layer to be shown to the user
1112
* @param {number} opacity The opacity of this layer, between 0.0 (transparent) and 1.0 (opaque)
1213
* @param {boolean} visible If the layer should be shown on the map or be hidden
13-
* @param {String} getCapabilitiesUrl URL to the getCapabilities.xml endpoint of the server for
14-
* this layer
14+
* @param {String} baseURL URL to the getCapabilities.xml endpoint of the server for this layer
1515
* @param {String} externalLayerId Layer ID to use when requesting the tiles on the server
1616
* @param {LayerAttribution[]} attributions Description of the data owner(s) for this layer
1717
* @param {String} abstract Abstract of this layer to be shown to the user
18-
* @param {[[number, number], [number, number]] | undefined} extent Layer extent
18+
* @param {[[number, number], [number, number]] | null} extent Layer extent
19+
* @param {boolean} isLoading Set to true if some parts of the layer (e.g. metadata) are still
20+
* loading
1921
*/
2022
constructor(
2123
name,
2224
opacity,
2325
visible,
24-
getCapabilitiesUrl,
26+
baseURL,
2527
externalLayerId,
26-
attributions,
27-
abstract,
28-
extent
28+
attributions = getDefaultAttribution(baseURL),
29+
abstract = '',
30+
extent = null,
31+
isLoading = true
2932
) {
3033
super(
3134
name,
3235
LayerTypes.WMTS,
3336
externalLayerId,
34-
getCapabilitiesUrl,
37+
baseURL,
3538
opacity,
3639
visible,
3740
attributions,
3841
abstract,
39-
extent
42+
extent,
43+
isLoading
4044
)
4145
}
4246

4347
getID() {
4448
// format coming from https://github.com/geoadmin/web-mapviewer/blob/develop/adr/2021_03_16_url_param_structure.md
45-
return `WMTS|${this.baseURL}|${this.externalLayerId}|${this.name}`
49+
return `WMTS|${this.baseURL}|${this.externalLayerId}`
4650
}
4751
}

src/api/layers/layers-external.api.js

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import axios from 'axios'
2+
import WmsCapabilities from '@/api/layers/wms-capabilities.class'
3+
import WmtsCapabilities from '@/api/layers/wmts-capabilities.class'
4+
import log from '@/utils/logging'
5+
6+
/**
7+
* Read and parse WMS GetCapabilities
8+
*
9+
* @param {string} baseUrl Base URL for the WMS server
10+
* @returns {Promise<WmsCapabilities | null>} WMS Capabilities
11+
*/
12+
export async function readWmsCapabilities(baseUrl) {
13+
const url = new URL(baseUrl)
14+
url.searchParams.set('SERVICE', 'WMS')
15+
url.searchParams.set('REQUEST', 'GetCapabilities')
16+
const response = await axios.get(url.toString(), { timeout: 20000 })
17+
18+
if (response.status !== 200) {
19+
log.error(`Failed to read GetCapabilities from ${url.toJSON()}`, response)
20+
return null
21+
}
22+
23+
return parseWmsCapabilities(response.data, baseUrl)
24+
}
25+
26+
/**
27+
* Parse WMS Get Capabilities string
28+
*
29+
* @param {string} content Input content to parse
30+
* @param {string} originUrl Origin URL of the content, this is used as default GetCapabilities URL
31+
* if not found in the Capabilities
32+
* @returns {WmsCapabilities} Get Capabilities object
33+
*/
34+
export function parseWmsCapabilities(content, originUrl) {
35+
try {
36+
return new WmsCapabilities(content, originUrl)
37+
} catch (error) {
38+
log.error(`Failed to parse WMS Get Capabilities`, error)
39+
return null
40+
}
41+
}
42+
43+
/**
44+
* Read and parse WMTS GetCapabilities
45+
*
46+
* @param {string} baseUrl Base URL for the WMTS server
47+
* @returns {Promise<WmtsCapabilities | null>} WMTS Capabilities
48+
*/
49+
export async function readWmtsCapabilities(baseUrl) {
50+
const url = new URL(baseUrl)
51+
url.searchParams.set('SERVICE', 'WMTS')
52+
url.searchParams.set('REQUEST', 'GetCapabilities')
53+
try {
54+
const response = await axios.get(url.toString(), { timeout: 20000 })
55+
56+
if (response.status !== 200) {
57+
log.error(`Failed to read GetCapabilities from ${url.toJSON()}`, response)
58+
return null
59+
}
60+
61+
return parseWmtsCapabilities(response.data, baseUrl)
62+
} catch (error) {
63+
log.error(`Failed to get and parse the capabilities: ${error}`)
64+
return null
65+
}
66+
}
67+
68+
/**
69+
* Parse WMTS Get Capabilities string
70+
*
71+
* @param {string} content Input content to parse
72+
* @param {string} originUrl Origin URL of the content, this is used as default GetCapabilities URL
73+
* if not found in the Capabilities
74+
* @returns {WmtsCapabilities} Get Capabilities object
75+
*/
76+
export function parseWmtsCapabilities(content, originUrl) {
77+
try {
78+
return new WmtsCapabilities(content, originUrl)
79+
} catch (error) {
80+
log.error(`Failed to parse WMTS Get Capabilities`, error)
81+
return null
82+
}
83+
}

0 commit comments

Comments
 (0)