openlayers 自定义legend(geoserver)

注释:经过实践WMS 服务GetLegendGraphic更好用官网使用文档

我使用的过的配置

/**
       * dx 为右边文字左右margin距离
       * bgcolor 为图例的背景颜色 TRANSPARENT=true表示背景颜色透明
       */
?request=GetLegendGraphic&format=image/png&legend_options=fontColor:0x333333;bgcolor:0xFFFFFF;fontSize:12;dpi:100;dx:8;fontStyle:bold;&TRANSPARENT=true&layer=layer名字

前言

  • WMS 服务。openlayers提供了一个GetLegendGraphic的方法获取图例的URL,这个URL是一张图片。如果只是简单的显示可以满足我们的要求,但图片的缺点也很明显。文字较小,图片较小,放大又会失真,而且图片背景色为白色,对于有其他需求功能不太能符合。(也有可能我现在没找到设置的方法,我看到官网上有params设置,但又没看到params的说明。。。)
  • WFS服务。根据style属性自定义设置。我使用的是geoserver,这个应用提供了一些公开接口(地址是:点击这里),通过/rest/workspaces/{workspace}/styles/{style}这个接口我们可以获取到对应的样式设置。代码进行处理就可以了

实现

  1. 设置图例对象格式。这里我设置的是:
interface CustomStyle {
    fill?: string,
    name: string
    stroke?: string
}
/* 如果是点的图例的话如下
* {point: [{name: '大于7',fill: '#10F500'}]
**、
  1. 通过接口获取sld,并处理获取的xml,将格式组合成图例格式(下面该方法可以共用,但可能也有小小的问题,因为我只测了两个sld,可以自行修改)
// cb为回调函数
export async function getSldXml(url, cb) {
  // 获取xml
    const data = await getSld(url); 
    const legendObj = {};
    const result = data.replaceAll("sld:", "");
    const xmlStyle = new DOMParser().parseFromString(result, "text/xml");
    const userStyleDom = xmlStyle.getElementsByTagName("UserStyle").item(0);
    const featureTypeStyleDom =
        userStyleDom?.getElementsByTagName("FeatureTypeStyle");
    for (let i = 0, l = featureTypeStyleDom?.length || 0; i < l; i++) {
        const rulesDom = featureTypeStyleDom
            ?.item(i)
            ?.getElementsByTagName("Rule");
        for (let q = 0, rl = rulesDom?.length || 0; q < rl; q++) {
            const name = rulesDom
                ?.item(q)
                ?.getElementsByTagName("Name")
                .item(0)?.innerHTML;
            const pointBl = rulesDom
                ?.item(q)
                ?.getElementsByTagName("PointSymbolizer");
            let type = "", targetDom;
            if (pointBl && pointBl.length > 0) { type = "point"; targetDom = pointBl }
            else {
                const lineBl = rulesDom
                    ?.item(q)
                    ?.getElementsByTagName("LineSymbolizer");
                if (lineBl && lineBl.length > 0) { type = "line"; targetDom = lineBl }
                else {
                    const polygonBl = rulesDom
                        ?.item(q)
                        ?.getElementsByTagName("PolygonSymbolizer");
                    if (polygonBl && polygonBl.length > 0) { type = "polygon"; targetDom = polygonBl }
                    else break;
                }
            }
            const fillDom = targetDom?.item(0)?.getElementsByTagName("Fill");
            const strokeDom = targetDom?.item(0)?.getElementsByTagName("Stroke");
            legendObj[type] = legendObj[type] || [];
            // 填充
            let cssDoms = fillDom?.item(0)?.children || [];
            const nameObj = { name: name };
            for (const cssDom of cssDoms) {
                if (cssDom.getAttribute("name") === "fill") {
                    nameObj["fill"] = cssDom.innerHTML;
                    break;
                }
            }
            // 线条
            cssDoms = strokeDom?.item(0)?.children || [];
            for (const cssDom of cssDoms) {
                if (cssDom.getAttribute("name") === "stroke") {
                    nameObj["stroke"] = cssDom.innerHTML;
                    break;
                }
            }
            legendObj[type].push(nameObj);
        }
    }
    cb(legendObj);
}
  1. 获取图例格式后,自定义LegendControl控件类。
import { Control } from 'ol/control';
import BaseLayer from 'ol/layer/Base'
interface CustomLayer {
    layer: BaseLayer,
    type: string,
    legendConfig: object
}
interface CustomStyle {
    fill?: string,
    name: string
    stroke?: string
}
const SVG_NS = 'http://www.w3.org/2000/svg'
export class LegendControl extends Control {
    legendDiv: HTMLElement
    layerArr: CustomLayer[]

    constructor(layers, options = {}) {
        options = options || {}

        super(options)
        this.layerArr = layers
    }
    setMap() {
        this.render()
    }
    render() {
        if (!this.legendDiv) {
            this.legendDiv = document.createElement("div")
            this.legendDiv.id = "legendSub"
            this.legendDiv.className = 'legend_sub'
            this.element.appendChild(this.legendDiv)

            for (let i = 0, len = this.layerArr.length; i < len; i++) {
                const layer: BaseLayer = this.layerArr[i].layer
                if (!layer.getVisible()) continue
                const labelLyr = document.createElement('h4')
                labelLyr.innerHTML = layer.getProperties()['name']
                this.legendDiv.appendChild(labelLyr)

                const tableDiv = document.createElement('table')
                this.legendDiv.appendChild(tableDiv)

                for (const key in this.layerArr[i].legendConfig) {
                    if (key === 'point') { // 点
                        this.createPointLegend(tableDiv, this.layerArr[i].legendConfig[key])
                    } else if (key === 'line') { // 线
                        this.createLineLegend(tableDiv, this.layerArr[i].legendConfig[key])
                    } else { // 面
                        this.createPolygonLegend(tableDiv, this.layerArr[i].legendConfig[key])
                    }
                }
            }
        }
    }
    createPointLegend(tableDiv: HTMLElement, legendConfig: CustomStyle[]) {
        for (const legend of legendConfig) {
            const trDiv = document.createElement('tr')

            const colorTd = document.createElement('td')
            trDiv.appendChild(colorTd)
            const svg = document.createElementNS(SVG_NS, 'svg')
            svg.setAttribute('style', "width:36px;height:36px;");
            const tag = document.createElementNS(SVG_NS, 'circle')
            tag.setAttribute('cx', "18")
            tag.setAttribute('cy', "18")
            tag.setAttribute('r', "3")
            if (legend.fill) tag.setAttribute('fill', legend.fill)
            let borderColor = "none"
            if (legend.stroke) borderColor = legend.stroke
            tag.setAttribute('stroke', borderColor)
            tag.setAttribute('stroke-width', "1")
            svg.appendChild(tag)
            colorTd.appendChild(svg)

            const labelTd = document.createElement('td')
            trDiv.appendChild(labelTd)
            const itemLabel = document.createElement('label')
            itemLabel.setAttribute('style', 'margin-left:5px;position:relative;top:3px;')
            itemLabel.innerHTML = legend.name
            labelTd.appendChild(itemLabel)

            tableDiv.appendChild(trDiv)

        }
    }
    createPolygonLegend(tableDiv: HTMLElement, legendConfig: CustomStyle[]) {
        // const border = legendConfig.border
        for (const legend of legendConfig) {
            const trDiv = document.createElement('tr')

            const colorTd = document.createElement('td')
            trDiv.appendChild(colorTd)
            const svg = document.createElementNS(SVG_NS, 'svg')
            let fillColor = "none"
            if (legend.fill) fillColor = legend.fill
            svg.setAttribute('style', "width:36px;height:36px;fill:" + fillColor);
            const tag = document.createElementNS(SVG_NS, 'polygon')
            tag.setAttribute('points', "16,4 32,4 32,32 4,32")
            if (legend.stroke) tag.setAttribute('stroke', legend.stroke)
            tag.setAttribute('stroke-width', "1")
            svg.appendChild(tag)
            colorTd.appendChild(svg)

            const labelTd = document.createElement('td')
            trDiv.appendChild(labelTd)
            const itemLabel = document.createElement('label')
            itemLabel.setAttribute('style', 'margin-left:5px;position:relative;top:3px;')
            itemLabel.innerHTML = legend.name
            labelTd.appendChild(itemLabel)
            tableDiv.appendChild(trDiv)

        }
    }

    createLineLegend(tableDiv: HTMLElement, legendConfig: CustomStyle[]) {
        for (const legend of legendConfig) {
            const trDiv = document.createElement('tr')

            const colorTd = document.createElement('td')
            trDiv.appendChild(colorTd)
            const svg = document.createElementNS(SVG_NS, 'svg')

            svg.setAttribute('style', "width:36px;height:36px;");
            const tag = document.createElementNS(SVG_NS, 'line')
            if (legend.stroke) tag.setAttribute('stroke', legend.stroke)
            tag.setAttribute('stroke-width', "1")
            tag.setAttribute('x1', "4")
            tag.setAttribute('y1', "34")
            tag.setAttribute('x2', "34")
            tag.setAttribute('y2', "4")
            svg.appendChild(tag)
            colorTd.appendChild(svg)

            const labelTd = document.createElement('td')
            trDiv.appendChild(labelTd)
            const itemLabel = document.createElement('label')
            itemLabel.setAttribute('style', 'margin-left:5px;position:relative;top:3px;')
            itemLabel.innerHTML = legend.name
            labelTd.appendChild(itemLabel)
            tableDiv.appendChild(trDiv)

        }
    }
}
  1. new Map中调用我们刚刚自定义的LegendControl控件
new Map({
        target: "map",
        controls: defaultControls().extend([
          new LegendControl(
            [
              // state.legendObj,state.xzqLegendObj 为调用getSldXml函数返回的值
              { layer: pointVector, legendConfig: state.legendObj }, // state.legendObj 为调用getSldXml函数返回的值
              { layer: untiled, legendConfig: state.xzqLegendObj },
            ],
            {
              element: document.getElementById("legend"),
            }
          ),
        ]),
        // ....... 这里省略了一些
      });
  1. 写css稍微美化一下
.legend {
  position: absolute;
  right: 10px;
  bottom: 10px;
  width: 200px;
  max-height: 700px;
  overflow-y: scroll;
  z-index: 10;
  background: rgba(65, 184, 131, 0.5);
  box-sizing: border-box;
  padding: 10px 16px 20px 16px;
  h4 {
    font-weight: bold;
    margin: 10px 0;
  }
  table {
    border: 0;
    border-collapse: collapse;
    border-spacing: 0;
    line-height: 0;
    width: 100%;
    td {
      text-align: left;
    }
    tr {
      td:first-child {
        width: 36px;
        height: 36px;
      }
    }
  }
}
::-webkit-scrollbar {
  width: 0px;
}
  1. 基本就完成了

结果