WMTS服务参数解析以及Cesium加载WMTS服务

需求背景

最近开发的系统需要能够加载用户手动填写的WMTS服务,希望用户输入一个URL地址后自动加载对应的WMTS服务,不用填写额外的参数,并且很用用户也可能不知道例如layer tileMatrixSetID 这些参数怎么填,而且很有可能填错,因此,需要解析一下WMTS服务的xml文件。

cesium需要的参数

cesiumWebMapTileServiceImageryProvider构造函数至少需要urllayer tileMatrixSetID 这些参数,其他的style tileMatrixLabels tilingScheme则可以省略,通过研究WMTS服务,发现还可以解析出服务的投影方式以及中心wgs84坐标,这些也可以顺便获取。

解析xml

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)
        })
      }
    })
  }
})

再也不用自己找参数了,完美解决问题!

以上几个部分的代码可以直接拷贝在一起使用,亲测有效。

你可能感兴趣的:(cesium,cesium,wmts,geoserver,arcgis)