最近开发的系统需要能够加载用户手动填写的WMTS服务,希望用户输入一个URL地址后自动加载对应的WMTS服务,不用填写额外的参数,并且很用用户也可能不知道例如layer
、tileMatrixSetID
这些参数怎么填,而且很有可能填错,因此,需要解析一下WMTS服务的xml文件。
cesium
的WebMapTileServiceImageryProvider
构造函数至少需要url
、layer
、tileMatrixSetID
这些参数,其他的style
、tileMatrixLabels
、tilingScheme
则可以省略,通过研究WMTS服务,发现还可以解析出服务的投影方式以及中心wgs84坐标,这些也可以顺便获取。
wmts服务的定义是一个xml文件,例如 arcgis的https://localhost:6443/arcgis/rest/services/honglianhu/ezhou_land/MapServer/WMTS/1.0.0/WMTSCapabilities.xml
以及 geoserver的 http://localhost:8080/geoserver/gwc/service/wmts?REQUEST=GetCapabilities
地址,目前只针对这2种服务地址进行解析。
至于wmts的描述文件格式大家可以自行了解,这里只贴一下解析的代码
选择了xml-js 这个库进行解析,首先通过wmts服务描述文件xml地址,获取文本进行解析,去掉多余的部分
var option = {
ignoreDeclaration: true,
compact: true,
trim: true,
ignoreInstruction: true,
ignoreComment: true,
ignoreCdata: true,
ignoreDoctype: true
}
// 把_text属性直接改为值,见xml-js issue
function RemoveJsonTextAttribute(value, parentElement) {
try {
var keyNo = Object.keys(parentElement._parent).length;
var keyName = Object.keys(parentElement._parent)[keyNo - 1];
parentElement._parent[keyName] = value;
}
catch (e) { }
}
// 以文本方式获取xml文件
function getWMTSParamsFromUrl(xmlUrl) {
return new Promise((resolve, reject) => {
fetch(xmlUrl).then(res => res.text()).then(res => {
try {
// 解析xml为JS对象
var xmlObj = xml2js(res, { ...option, textFn: RemoveJsonTextAttribute })
var info = getWMTSInfo(xmlObj)
resolve(info)
} catch (e) {
console.log(e);
resolve()
}
}).catch(() => {
resolve()
})
})
}
然后获取xml中所需的属性:
// 获取服务需要的参数
function getWMTSInfo(obj) {
const WMTSXML = 'http://www.opengis.net/wmts/1.0'
const wmstList = []
if (obj.Capabilities) {
const { _attributes, Contents } = obj.Capabilities
if (_attributes?.xmlns !== WMTSXML) {
return
}
const { Layer, TileMatrixSet } = Contents
if (!Layer || !TileMatrixSet) {
return
}
const info = {
url: null,
layer: null,
style: null,
tileMatrixSetID: null,
format: null,
tileMatrixLabels: null,
crs: null,
center: null
}
const tileSet = TileMatrixSet[0]
info.tileMatrixSetID = tileSet['ows:Identifier']
info.crs = tileSet['ows:SupportedCRS']
info.tileMatrixLabels = tileSet.TileMatrix.map(s => s['ows:Identifier'])
let LayerInfo = Layer
if (!Array.isArray(LayerInfo)) {
LayerInfo = [LayerInfo]
}
LayerInfo.forEach(layer => {
let resourceURL = layer?.ResourceURL
if (!Array.isArray(resourceURL)) {
resourceURL = [resourceURL]
}
info.format = 'image/png' || layer?.Format
const resourceURLItem = resourceURL.filter(s => s._attributes.resourceType === 'tile')
let pngResource = resourceURLItem.find(s => s._attributes.format.endsWith('png')) || resourceURLItem[0]
if (pngResource) {
info.url = pngResource?._attributes?.template
info.format = pngResource?._attributes?.format
}
info.layer = layer['ows:Identifier']
info.style = layer.Style['ows:Identifier']
const wgsBox = layer['ows:WGS84BoundingBox']
const lower = wgsBox['ows:LowerCorner'].split(' ').map(s => Number(s))
const upper = wgsBox['ows:UpperCorner'].split(' ').map(s => Number(s))
const center = [lower[0] + (upper[0] - lower[0]) / 2, lower[1] + (upper[1] - lower[1]) / 2]
info.center = center
wmstList.push({ ...info })
})
return wmstList
}
}
需要注意的是arcgis的wmts服务是一个服务一个xml地址,而geoserver的wmts是所有服务都在一个xml地址,因此解析的时候需要判断是否是一个数组,返回的结果也将是一个数组,
解析出来的结果示例如下:
[
{
url: "https://localhost:6443/arcgis/rest/services/honglianhu/ezhou_land/MapServer/WMTS/tile/1.0.0/honglianhu_ezhou_land/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png",
layer: "honglianhu_ezhou_land",
style: "default",
tileMatrixSetID: "default028mm",
format: "image/png",
tileMatrixLabels: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23"],
crs: "urn:ogc:def:crs:EPSG::3857",
center: [114.59548160858947, 30.448104919756297]
}
]
在cesium中直接使用:
const viewer = new Cesium.Viewer('app')
var xmlUrl = 'https://localhost:6443/arcgis/rest/services/honglianhu/ezhou_land/MapServer/WMTS/1.0.0/WMTSCapabilities.xml'
getWMTSParamsFromUrl(xmlUrl).then(info => {
if (info) {
info.forEach(item => {
const { url, center, crs, ...other } = item
if (url) {
const provider = new Cesium.WebMapTileServiceImageryProvider({
url,
...other,
tilingScheme: crs.endsWith('4326') ? new Cesium.GeographicTilingScheme() : new Cesium.WebMercatorTilingScheme()
})
var wmts = new Cesium.ImageryLayer(provider)
viewer.imageryLayers.add(wmts)
viewer.camera.flyTo({
destination: new Cesium.Cartesian3.fromDegrees(center[0], center[1], 1000)
})
}
})
}
})
再也不用自己找参数了,完美解决问题!
以上几个部分的代码可以直接拷贝在一起使用,亲测有效。