Skip to content

OpenLayers 对照 ​

平台的服务是标准的切片、矢量瓦片与 OGC 接口,不局限于 MapLibre。这几个示例用 OpenLayers 加载同一平台的服务。OpenLayers 不经过 SDK 的 transformRequest,也不能加请求头,所以地址统一交给 client.withKey() 补上 token 参数;访问公开服务时没有 Key,地址保持不变。

OpenLayers:XYZ 切片与 WMS ​

土地利用服务的 MapServer 切片作为 XYZ 底图:地址用 client.services.tileUrl() 取得({z}/{y}/{x},行在前;OpenLayers 按占位符名替换,顺序不影响)。道路服务的 WMS 作为叠加层:图层名由 client.services.layers() 与 GeoBridge.wmsLayerNames() 算出。右上角的面板分别开关两个图层,并调 WMS 的透明度。OpenLayers 的视图默认是 Web 墨卡托,经纬度中心点要先用 ol.proj.fromLonLat() 转换。

需要: 一个提供 MapServer 切片的地图服务和一个开启了 WMS 的地图服务。

js
/**
 * @title OpenLayers:XYZ 切片与 WMS
 * @description 用 OpenLayers 加载同一平台的服务:土地利用的 MapServer 切片(XYZ)作底图,道路的 WMS 作叠加层;地址交给 withKey 补 token,WMS 图层名按 MapServer 图层列表算出;面板开关图层并调 WMS 透明度。
 * @category 对照
 * @service demo/landuse
 * @service demo/roads
 */
// 出错时给出可理解的提示(服务不存在多半是 demo 服务映射或服务路径有误)
const friendly = (e) => (e && e.status === 404 ? '服务不存在,请检查服务路径' : e && e.message)

try {
  // 拷到自己页面时:换成你的切片地图服务与开启了 WMS 的服务路径
  const landuse = services['demo/landuse']
  const roads = services['demo/roads']

  // OpenLayers 不能加请求头,所以用 withKey 把 token 补进地址(匿名时原样返回)
  // 注意:只有属于 baseUrl 的地址才会补 token;平台写进文档的地址以 webBaseUrl 为前缀,两者不一致时 Key 不会随这些请求发送
  const layerName = GeoBridge.wmsLayerNames((await client.services.layers(roads)).layers)[0]

  const xyz = new ol.layer.Tile({
    // tileUrl() 的模板是 {z}/{y}/{x}(行在前),OpenLayers 按占位符名替换,顺序不影响
    source: new ol.source.XYZ({ url: client.withKey(client.services.tileUrl(landuse)), maxZoom: 20 }),
  })
  const wms = new ol.layer.Image({
    opacity: 0.8,
    source: new ol.source.ImageWMS({ url: client.withKey(client.services.wmsUrl(roads)), params: { LAYERS: layerName }, ratio: 1 }),
  })
  const map = new ol.Map({
    target: container,
    layers: [xyz, wms],
    // OpenLayers 的视图默认是 Web 墨卡托,经纬度要先用 fromLonLat 转换;道路图层在 11 级以上才出图
    view: new ol.View({ center: ol.proj.fromLonLat([114.172, 22.31]), zoom: 14 }),
  })

  // 面板:两个图层的开关 + WMS 透明度
  const panel = document.createElement('div')
  panel.style.cssText = 'position:absolute;top:8px;right:8px;z-index:2;background:#fff;padding:6px 10px;border-radius:6px;box-shadow:0 1px 4px rgba(0,0,0,.3);font:13px/1.9 sans-serif'
  for (const [layer, text] of [[xyz, 'XYZ 切片:土地利用'], [wms, `WMS:${layerName}(道路)`]]) {
    const label = document.createElement('label')
    label.style.display = 'block'
    const box = document.createElement('input')
    box.type = 'checkbox'
    box.checked = true
    box.onchange = () => layer.setVisible(box.checked)
    label.append(box, ' ' + text)
    panel.appendChild(label)
  }
  const slider = document.createElement('input')
  slider.type = 'range'
  slider.value = '80'
  slider.oninput = () => wms.setOpacity(slider.value / 100)
  panel.append('WMS 透明度 ', slider)
  container.appendChild(panel)

  // 出图失败时 OpenLayers 只发事件,不抛错:在这里转成日志
  wms.getSource().on('imageloaderror', () => console.error('WMS 出图失败,请检查服务是否开启 WMS'))
  wms.getSource().once('imageloadend', () => console.log('WMS 图层', layerName, '已出图,视野缩放级别', map.getView().getZoom().toFixed(1)))
  xyz.getSource().once('tileloadend', () => console.log('XYZ 切片已加载:', client.services.tileUrl(landuse)))
} catch (e) {
  console.error(friendly(e))
}
在沙箱中打开

OpenLayers:矢量瓦片 ​

用 ol.layer.VectorTile 与 ol.format.MVT 加载矢量瓦片,地址来自 client.services.vectorTileUrl()。样式函数按道路等级返回不同颜色与线宽(同一种样式只建一次,避免每帧新建对象);鼠标移到道路上,右上角显示道路名称与等级。

需要: 一个矢量瓦片服务(示例用香港道路,按 fclass 字段配色)。

js
/**
 * @title OpenLayers:矢量瓦片
 * @description 用 OpenLayers 的 VectorTile 图层与 MVT 格式加载道路矢量瓦片,样式函数按道路等级配色;鼠标移到道路上,右上角显示道路名称与等级。
 * @category 对照
 * @service demo/roads
 */
// 出错时给出可理解的提示(服务不存在多半是 demo 服务映射或服务路径有误)
const friendly = (e) => (e && e.status === 404 ? '服务不存在,请检查服务路径' : e && e.message)

try {
  // 拷到自己页面时:换成你的矢量瓦片服务路径
  const path = services['demo/roads']
  const COLORS = { motorway: '#e8590c', trunk: '#f08c00', primary: '#f59f00', secondary: '#74b816', tertiary: '#1c7ed6' }

  // 每种等级的样式只建一次,样式函数里复用,避免每帧新建对象
  const styles = {}
  function roadStyle(feature) {
    const fclass = feature.get('fclass')
    const color = COLORS[fclass] || '#adb5bd'
    styles[color] ??= new ol.style.Style({ stroke: new ol.style.Stroke({ color, width: COLORS[fclass] ? 2.5 : 0.8 }) })
    return styles[color]
  }

  const layer = new ol.layer.VectorTile({
    // MVT 格式解析 pbf;地址模板是 {z}/{y}/{x}.pbf,OpenLayers 按占位符名替换。OpenLayers 不能加请求头,用 withKey 补 token
    source: new ol.source.VectorTile({ format: new ol.format.MVT(), url: client.withKey(client.services.vectorTileUrl(path)) }),
    style: roadStyle,
  })
  const map = new ol.Map({
    target: container,
    layers: [layer],
    // OpenLayers 的视图默认是 Web 墨卡托,经纬度要先用 fromLonLat 转换
    view: new ol.View({ center: ol.proj.fromLonLat([114.172, 22.298]), zoom: 16 }),
  })

  const info = document.createElement('div')
  info.style.cssText = 'position:absolute;top:8px;right:8px;z-index:2;background:#fff;padding:6px 10px;border-radius:6px;box-shadow:0 1px 4px rgba(0,0,0,.3);font:13px/1.6 sans-serif'
  info.textContent = '把鼠标移到道路上'
  container.appendChild(info)

  // 悬停:取像素处的第一个要素(矢量瓦片要素只读,属性用 get 取)
  function describe(pixel) {
    const f = map.forEachFeatureAtPixel(pixel, (x) => x, { hitTolerance: 4 })
    info.textContent = f ? `${f.get('name') || '(无名称)'} 等级:${f.get('fclass')}` : '把鼠标移到道路上'
    return info.textContent
  }
  map.on('pointermove', (e) => describe(e.pixel))

  // 演示:瓦片渲染完成后,描述画面中心处的道路
  map.once('rendercomplete', () => {
    const features = layer.getFeaturesInExtent(map.getView().calculateExtent(map.getSize()))
    console.log('视野内道路要素:', features.length, ';中心处:', describe(map.getPixelFromCoordinate(map.getView().getCenter())))
  })
} catch (e) {
  console.error(friendly(e))
}
在沙箱中打开

OpenLayers:WMTS 与 OGC API Features ​

client.openlayers.wmtsSource() 按 WMTS 能力文档建 ol.source.WMTS,能力文档中的矩阵集都能用:右上角在经纬度的 WorldCRS84Quad(视图 EPSG:4326)与 Web 墨卡托的 GoogleMapsCompatible(视图 EPSG:3857)之间切换,视图投影必须与矩阵集一致。高速公路由 client.openlayers.ogcApiVectorSource() 按 CQL2 条件从 OGC API - Features 分页读完后叠加,切换投影时自动按新投影重读。SDK 不依赖 ol 包:wmtsSource() 可以直接传全量构建的 ol 命名空间(按需引入时传 { WMTSCapabilities, WMTS, optionsFromCapabilities }),ogcApiVectorSource() 传 { VectorSource, GeoJSON }。

需要: 一个开启了 WMTS 的切片地图服务和一个开启了 OGC API 的矢量服务。

js
/**
 * @title OpenLayers:WMTS 与 OGC API Features
 * @description 用 openlayers.wmtsSource 按能力文档建 WMTS 源,可在经纬度矩阵集(WorldCRS84Quad,EPSG:4326 视图)与 Web 墨卡托(GoogleMapsCompatible)之间切换;道路高速公路由 openlayers.ogcApiVectorSource 按 CQL2 条件分页读取叠加。
 * @category 对照
 * @service demo/basemap
 * @service demo/roads
 */
// 出错时给出可理解的提示(服务不存在多半是 demo 服务映射或服务路径有误)
const friendly = (e) => (e && e.status === 404 ? '服务不存在或未开启 WMTS / OGC API,请检查服务路径' : e && e.message)

try {
  // 拷到自己页面时:换成你开启了 WMTS 的地图服务与开启了 OGC API 的服务路径
  const basemap = services['demo/basemap']
  const roads = services['demo/roads']

  // wmtsSource 可以直接传全量构建的 ol 命名空间;ogcApiVectorSource 要传 { VectorSource, GeoJSON } 两个构造函数
  const { collections } = await client.ogcApi.collections(roads)
  const olVector = { VectorSource: ol.source.Vector, GeoJSON: ol.format.GeoJSON }
  const vector = new ol.layer.Vector({
    source: client.openlayers.ogcApiVectorSource(olVector, roads, collections[0].id, { params: { filter: "fclass = 'motorway'" } }),
    style: new ol.style.Style({ stroke: new ol.style.Stroke({ color: '#e8590c', width: 3 }) }),
  })
  const tiles = new ol.layer.Tile()
  const map = new ol.Map({ target: container, layers: [tiles, vector] })

  // 切换矩阵集:视图投影必须与矩阵集坐标系一致,换投影时重建视图并保持中心
  const use = async (matrixSet, projection) => {
    const center = map.getView().getCenter() ? ol.proj.toLonLat(map.getView().getCenter(), map.getView().getProjection()) : [114.17, 22.32]
    tiles.setSource(await client.openlayers.wmtsSource(ol, basemap, { matrixSet }))
    map.setView(new ol.View({ projection, center: ol.proj.fromLonLat(center, projection), zoom: 12 }))
    vector.getSource().refresh()
    console.log(`WMTS 矩阵集 ${matrixSet},视图 ${projection}`)
  }
  await use('WorldCRS84Quad', 'EPSG:4326')
  vector.getSource().on('featuresloadend', (e) => console.log(`OGC API Features:高速公路 ${e.features.length} 段`))

  const panel = document.createElement('div')
  panel.style.cssText = 'position:absolute;top:8px;right:8px;z-index:2;background:#fff;padding:6px 10px;border-radius:6px;box-shadow:0 1px 4px rgba(0,0,0,.3);font:13px/1.9 sans-serif'
  for (const [set, proj, text] of [['WorldCRS84Quad', 'EPSG:4326', '经纬度 WorldCRS84Quad'], ['GoogleMapsCompatible', 'EPSG:3857', 'Web 墨卡托 GoogleMapsCompatible']]) {
    const label = document.createElement('label')
    label.style.display = 'block'
    const radio = document.createElement('input')
    radio.type = 'radio'
    radio.name = 'tms'
    radio.checked = set === 'WorldCRS84Quad'
    radio.onchange = () => use(set, proj).catch((e) => console.error(friendly(e)))
    label.append(radio, ' ' + text)
    panel.appendChild(label)
  }
  container.appendChild(panel)
} catch (e) {
  console.error(friendly(e))
}
在沙箱中打开