首页 > 其他分享 >三十二、openlayers官网示例解析Draw lines rendered with WebGL——使用WebGL动态修改多边形端点类型、连接类型、偏移量、虚线等设置

三十二、openlayers官网示例解析Draw lines rendered with WebGL——使用WebGL动态修改多边形端点类型、连接类型、偏移量、虚线等设置

时间:2024-05-30 17:32:16浏览次数:23  
标签:map style 示例 WebGL Draw source stroke new gl

 

官网demo地址:

Draw lines rendered with WebGL

这个示例展示了如何用webgl渲染矢量图形并动态修改点、线属性。

首先先把基本的地图加载上去

initMap() {
      this.map = new Map({
        layers: [
          new TileLayer({
            source: new XYZ({
              url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
            }),
          }),
        ],
        target: "map",
        view: new View({
          center: fromLonLat([8.43, 46.82]),
          zoom: 7,
        }),
      });
    },

然后是基本的交互, Modify是修改、Snap是捕捉,这三个交互都绑定着this.source

addDrawInteraction() {
      this.draw = new Draw({
        source: this.source,
        type: "LineString",
      });
      this.map.addInteraction(this.draw);
      const modify = new Modify({ source: this.source });
      this.map.addInteraction(modify);
      //捕捉
      let snap = new Snap({ source: this.source });
      this.map.addInteraction(snap);
    },

接下来是重点部分,webgl。 

什么是webgl?

 WebGL 是基于硬件加速的图形库,能够利用 GPU 来进行高效的并行计算。与传统的 2D 绘图方式(如 Canvas 2D API)相比,WebGL 可以处理更多的图形数据、更高的绘图复杂度,并且在高分辨率和高刷新率下保持流畅的性能。这对于需要渲染大量图形元素的应用尤为重要,例如地理信息系统(GIS)、数据可视化和复杂动画。

在需要渲染和处理大量数据点的应用中,如地理信息系统、科学可视化和大数据分析,WebGL 的并行计算能力和高效的渲染管道使其能够在不牺牲性能的情况下处理大规模数据可视化。

在现代地图应用中,WebGL 的应用非常广泛。例如,像 Google Maps 和 Mapbox 这样的地图服务都使用 WebGL 来渲染地图和地理数据。这些应用需要处理大量的地理数据,包括矢量数据和栅格数据,并且需要在用户平移、缩放和旋转地图时保持流畅的交互体验。

webgl不是openlayers中才有的概念,openlayers底层是用的canvas,是因为canvas中有webgl,openlayers才可以使用webgl的。

下面是一个使用canvas+webgl绘制三角形的例子,大家随便看一眼,感受一下webgl就行。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebGL Example</title>
</head>
<body>
    <canvas id="webgl-canvas" width="800" height="600"></canvas>
    <script>
        const canvas = document.getElementById('webgl-canvas');
        const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
        
        if (!gl) {
            console.error("WebGL not supported, falling back on experimental-webgl");
        }
        if (!gl) {
            alert("Your browser does not support WebGL");
        }

        gl.viewport(0, 0, canvas.width, canvas.height);
        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT);

        const vertexShaderSource = `
        attribute vec4 a_Position;
        void main() {
            gl_Position = a_Position;
        }`;
        
        const fragmentShaderSource = `
        void main() {
            gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        }`;
        
        function createShader(gl, type, source) {
            const shader = gl.createShader(type);
            gl.shaderSource(shader, source);
            gl.compileShader(shader);
            if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
                console.error('Error compiling shader:', gl.getShaderInfoLog(shader));
                gl.deleteShader(shader);
                return null;
            }
            return shader;
        }
        
        const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
        const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);

        function createProgram(gl, vertexShader, fragmentShader) {
            const program = gl.createProgram();
            gl.attachShader(program, vertexShader);
            gl.attachShader(program, fragmentShader);
            gl.linkProgram(program);
            if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
                console.error('Error linking program:', gl.getProgramInfoLog(program));
                gl.deleteProgram(program);
                return null;
            }
            return program;
        }
        
        const program = createProgram(gl, vertexShader, fragmentShader);
        gl.useProgram(program);

        const vertices = new Float32Array([
            0.0,  0.5,
           -0.5, -0.5,
            0.5, -0.5
        ]);
        
        const buffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
        
        const a_Position = gl.getAttribLocation(program, 'a_Position');
        gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(a_Position);
        
        gl.clear(gl.COLOR_BUFFER_BIT);
        gl.drawArrays(gl.TRIANGLES, 0, 3);
    </script>
</body>
</html>

总的来说,这玩意写起来比canvas费劲多了。。。但。。。。它性能好。。。 

要在openlayers中使用webgl,首先需要重写一下createRenderer方法,使用WebGLVectorLayerRenderer方法。

import WebGLVectorLayerRenderer from "ol/renderer/webgl/VectorLayer.js";
import Layer from "ol/layer/Layer.js";
/**
     * @type {import('ol/style/webgl.js').WebGLStyle}
     */

class WebGLLayer extends Layer {
  constructor(options) {
    super(options)
    this.style = options.style
  }
  createRenderer() {
    return new WebGLVectorLayerRenderer(this, {
      className: this.getClassName(),
      style: this.style,
    });
  }
}

 然后实例化WebGLLayer类

 this.source = new VectorSource({
      url: "data/geojson/switzerland.geojson",
      format: new GeoJSON(),
    });

    this.style = this.getStyle(false, false);
    console.log("this.style", this.style);
    this.vectorlayer = new WebGLLayer({
      source: this.source,
      style: this.style,
    });

这里的style和普通矢量图层传递的style大不一样,先来看看结构。

variables中的属性offset是通过 "stroke-offset": ["var", "offset"]定义的。  

 

 同时页面有一个name=offset的input与之对应。

    

 滑块改变时会获取input的值,并通过variables[name]找到对应的属性把input的值赋值给它。然后调render()方法更新地图。

inputListener(event) {
      const variables = this.style.variables;
      const variableName = event.target.name;
      if (event.target.type === "radio") {
        variables[variableName] = event.target.value;
      } else {
        variables[variableName] = parseFloat(event.target.value);
      }
      const valueSpan = document.getElementById(`value-${variableName}`);
      if (valueSpan) {
        valueSpan.textContent = variables[variableName];
      }
      console.log("this.style", this.style);
      this.map.render();
    },

同理,虚线和图像模式的设置也是这样。

rebuildStyle() {
      this.style = this.getStyle(this.dashValue, this.patternValue);
      this.map.removeLayer(this.vectorlayer);
      this.vectorlayer = new WebGLLayer({
        source: this.source,
        style: this.style,
      });
      this.map.addLayer(this.vectorlayer);
    },

如果有虚线的设置,就在之前的style对象上增加几个属性。 

 

 看是看懂了,但为啥要这样写呢?我不使用variables定义变量行不行?

直接写。

this.vectorlayer = new WebGLLayer({
      source: this.source,
      style: {
        "stroke-width": 3,
        "stroke-color": "rgba(124,21,104,0.9)",
        "stroke-miter-limit": 10,
        "stroke-line-cap": 'butt',
        "stroke-line-join": 'round',
        "stroke-offset":10
      },
    });

 大部分样式是可以出来的。但是这样写了之后,如果需要动态修改样式,就需要重新layer.setStyle(style),而通过variables声明变量的方式只需要修改变量后调用 this.map.render()就可以更新地图了。而且直接写样式有的属性是会报错的,比如"stroke-line-dash": [26, 15, 15, 15]

那既然变量样式这么好用,那在普通图层中能不能写呢?

 this.vectorlayer = new VectorLayer({
      source: this.source,
      style: {
        variables: {
          width: 12,
          offset: 0,
          capType: "butt",
          joinType: "miter",
          miterLimit: 10, // ratio
          dashLength1: 25,
          dashLength2: 15,
          dashLength3: 15,
          dashLength4: 15,
          dashOffset: 0,
          patternSpacing: 0,
        },
        "stroke-width": 12,
        "stroke-color": "rgba(124,21,104,0.9)",
        "stroke-offset": ["var", "offset"],
        "stroke-miter-limit": ["var", "miterLimit"],
        "stroke-line-cap": ["var", "capType"],
        "stroke-line-join": ["var", "joinType"],
      },
    });

答案是不行的。直接就报错了。

完整代码:


<template>
  <div class="box">
    <h1>Draw lines rendered with WebGL</h1>
    <div id="map"></div>
    <form>
      <div class="form-group">
        端点类型&nbsp;
        <label class="radio-inline">
          <input
            type="radio"
            class="uniform"
            name="stroke-line-cap"
            @input="inputListener"
            value="butt"
            checked
          />
          <code>butt</code>&nbsp;
        </label>
        <label class="radio-inline">
          <input
            type="radio"
            class="uniform"
            @input="inputListener"
            name="stroke-line-cap"
            value="round"
          />
          <code>round</code>&nbsp;
        </label>
        <label class="radio-inline">
          <input
            type="radio"
            class="uniform"
            @input="inputListener"
            name="stroke-line-cap"
            value="square"
          />
          <code>square</code>&nbsp;
        </label>
      </div>
      <div class="form-group">
        连接类型 &nbsp;
        <label class="radio-inline">
          <input
            type="radio"
            class="uniform"
            @input="inputListener"
            name="joinType"
            value="miter"
            checked
          />
          <code>miter</code>&nbsp;
        </label>
        <label class="radio-inline">
          <input
            type="radio"
            class="uniform"
            @input="inputListener"
            name="joinType"
            value="round"
          />
          <code>round</code>&nbsp;
        </label>
        <label class="radio-inline">
          <input
            type="radio"
            class="uniform"
            @input="inputListener"
            name="joinType"
            value="bevel"
          />
          <code>bevel</code>&nbsp;
        </label>
      </div>
      <div class="form-group">
        <label>
          线宽 (pixels)
          <input
            type="range"
            class="uniform"
            @input="inputListener"
            name="widths"
            min="1"
            max="40"
            value="8"
          />
        </label>
        <span id="value-width">12</span>
      </div>
      <div class="form-group">
        <label>
          斜接限制 (ratio)
          <input
            type="range"
            class="uniform"
            @input="inputListener"
            name="miterLimit"
            min="1"
            max="20"
            value="10"
            step="0.1"
          />
        </label>
        <span id="value-miterLimit">10</span>
      </div>
      <div class="form-group">
        <label>
          线条偏移 (pixels)
          <input
            type="range"
            class="uniform"
            @input="inputListener"
            name="offset"
            min="-40"
            max="40"
            value="0"
          />
        </label>
        <span id="value-offset">0</span>
      </div>
      <div class="form-group">
        <label>
          <input
            type="checkbox"
            class="rebuild"
            @change="rebuildStyle"
            id="dashEnable"
            v-model="dashValue"
          />
          启用虚线
        </label>
      </div>
      <div class="form-group" style="margin-left: 18px">
        <label>
          虚线模式 (px)
          <input
            type="number"
            class="uniform"
            @input="inputListener"
            name="dashLength1"
            min="0"
            max="100"
            value="25"
          />
          <input
            type="number"
            class="uniform"
            @input="inputListener"
            name="dashLength2"
            min="0"
            max="100"
            value="15"
          />
          <input
            type="number"
            class="uniform"
            @input="inputListener"
            name="dashLength3"
            min="0"
            max="100"
            value="15"
          />
          <input
            type="number"
            class="uniform"
            @input="inputListener"
            name="dashLength4"
            min="0"
            max="100"
            value="15"
          />
        </label>
      </div>
      <div class="form-group" style="margin-left: 18px">
        <label>
          虚线偏移 (pixels)
          <input
            type="range"
            class="uniform"
            @input="inputListener"
            name="dashOffset"
            min="-200"
            max="200"
            value="0"
          />
        </label>
        <span id="value-dashOffset">0</span>
      </div>
      <div class="form-group">
        <label>
          <input
            type="checkbox"
            class="rebuild"
            @change="rebuildStyle"
            id="patternEnable"
            v-model="patternValue"
          />
          启用图像模式
        </label>
      </div>
      <div class="form-group" style="margin-left: 18px">
        <label>
          图案间距 (pixels)
          <input
            type="range"
            class="uniform"
            @input="inputListener"
            name="patternSpacing"
            min="0"
            max="64"
            value="0"
          />
        </label>
        <span id="value-patternSpacing">0</span>
      </div>
    </form>
  </div>
</template>
 
<script>
import GeoJSON from "ol/format/GeoJSON.js";
import Layer from "ol/layer/Layer.js";
import Map from "ol/Map.js";
import View from "ol/View.js";
import WebGLVectorLayerRenderer from "ol/renderer/webgl/VectorLayer.js";
import { Draw, Modify, Snap } from "ol/interaction.js";
import { OSM, Vector as VectorSource } from "ol/source.js";
import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer.js";
import { fromLonLat } from "ol/proj.js";
import { Style, Stroke, Fill, Circle as CircleStyle } from "ol/style";
import XYZ from "ol/source/XYZ";
import WebGLLayer from "@/utils/WebGLLayer.js";
export default {
  data() {
    return {
      map: null,
      source: null,
      draw: null,
      snap: null,
      dashValue: false,
      patternValue: false,
      vectorlayer: null,
      style: null,
    };
  },
  methods: {
    initMap() {
      this.map = new Map({
        layers: [
          new TileLayer({
            source: new XYZ({
              url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
            }),
          }),
        ],
        target: "map",
        view: new View({
          center: fromLonLat([8.43, 46.82]),
          zoom: 7,
        }),
      });
    },
    addDrawInteraction() {
      this.draw = new Draw({
        source: this.source,
        type: "LineString",
      });
      this.map.addInteraction(this.draw);
      const modify = new Modify({ source: this.source });
      this.map.addInteraction(modify);
      //捕捉
      let snap = new Snap({ source: this.source });
      this.map.addInteraction(snap);
    },
    getStyle(dash, pattern) {
      let newStyle = {
        variables: this.style
          ? this.style.variables
          : {
              widths: 12,
              offset: 0,
              capType: "butt",
              joinType: "miter",
              miterLimit: 10, // ratio
              dashLength1: 25,
              dashLength2: 15,
              dashLength3: 15,
              dashLength4: 15,
              dashOffset: 0,
              patternSpacing: 0,
            },
        "stroke-width": ["var", "widths"],
        "stroke-color": "rgba(124,21,104,0.9)",
        "stroke-offset": ["var", "offset"],
        "stroke-miter-limit": ["var", "miterLimit"],
        "stroke-line-cap": ["var", "capType"],
        "stroke-line-join": ["var", "joinType"],
      };
      if (dash) {
        newStyle = {
          ...newStyle,
          "stroke-line-dash": [
            ["var", "dashLength1"],
            ["var", "dashLength2"],
            ["var", "dashLength3"],
            ["var", "dashLength4"],
          ],
          "stroke-line-dash-offset": ["var", "dashOffset"],
        };
      }
      if (pattern) {
        delete newStyle["stroke-color"];
        newStyle = {
          ...newStyle,
          "stroke-pattern-src": "data/dot.svg",
          "stroke-pattern-spacing": ["var", "patternSpacing"],
        };
      }
      return newStyle;
    },
    rebuildStyle() {
      this.style = this.getStyle(this.dashValue, this.patternValue);
      this.map.removeLayer(this.vectorlayer);
      this.vectorlayer = new WebGLLayer({
        source: this.source,
        style: this.style,
      });
      this.map.addLayer(this.vectorlayer);
    },
    inputListener(event) {
      const variables = this.style.variables;
      const variableName = event.target.name;
      if (event.target.type === "radio") {
        variables[variableName] = event.target.value;
      } else {
        variables[variableName] = parseFloat(event.target.value);
      }
      const valueSpan = document.getElementById(`value-${variableName}`);
      if (valueSpan) {
        valueSpan.textContent = variables[variableName];
      }
      console.log("this.style", this.style);
      this.map.render();
    },
  },
  mounted() {
    let this_ = this;
    this.initMap();
    this.source = new VectorSource({
      url: "data/geojson/switzerland.geojson",
      format: new GeoJSON(),
    });

    this.style = this.getStyle(false, false);
    //定义普通矢量图层便于测试对比
    // this.vectorlayer = new VectorLayer({
    // source: this.source,
    // style: new Style({
    //   stroke: new Stroke({
    //     color: "#fff",
    //     width: 12,
    //     lineCap: "butt",
    //     lineJoin: "miter",
    //     miterLimit: 10,
    //     lineDash: [26, 15, 15, 15],
    //   }),
    //   fill: new Fill({
    //     color: "transparent",
    //   }),
    // }),
    //这样写报错
    // style: {
    //   variables: {
    //     width: 12,
    //     offset: 0,
    //     capType: "butt",
    //     joinType: "miter",
    //     miterLimit: 10, // ratio
    //     dashLength1: 25,
    //     dashLength2: 15,
    //     dashLength3: 15,
    //     dashLength4: 15,
    //     dashOffset: 0,
    //     patternSpacing: 0,
    //   },
    //   "stroke-width": 12,
    //   "stroke-color": "rgba(124,21,104,0.9)",
    //   "stroke-offset": ["var", "offset"],
    //   "stroke-miter-limit": ["var", "miterLimit"],
    //   "stroke-line-cap": ["var", "capType"],
    //   "stroke-line-join": ["var", "joinType"],
    // },
    // });
    this.vectorlayer = new WebGLLayer({
      source: this.source,
      style: this.style,
      //测试代码
      // style: {
      //   "stroke-width": 10,
      //   "stroke-color": "rgba(124,21,104,0.9)",
      //   "stroke-miter-limit": 10,
      //   "stroke-line-cap": "butt",
      //   "stroke-line-join": "round",
      //   "stroke-offset": 10,
      //   // "stroke-line-dash": [26, 15, 15, 15],
      // },
    });
    this.map.addLayer(this.vectorlayer);
    this.addDrawInteraction();
  },
};
</script>
<style scoped>
#map {
  width: 100%;
  height: 500px;
}
.box {
  height: 100%;
}
.map .ol-rotate {
  left: 0.5em;
  bottom: 0.5em;
  top: auto;
  right: auto;
}
.map:-webkit-full-screen {
  height: 100%;
  margin: 0;
}
.map:fullscreen {
  height: 100%;
}
</style>

标签:map,style,示例,WebGL,Draw,source,stroke,new,gl
From: https://blog.csdn.net/aaa_div/article/details/139289825

相关文章

  • CSS3媒体查询与页面自适应示例
    CSS3媒体查询(MediaQueries)是CSS的一个强大功能,它允许你根据设备的特性(如视口宽度、分辨率等)来应用不同的样式。这在创建响应式网站(即能自动适应不同屏幕尺寸和设备的网站)时非常有用。以下是一个简单的CSS3媒体查询和页面自适应的示例:首先,我们假设有一个简单的HTML结构:<!DOCTY......
  • AnyCAD中的Editor示例代码学习1
    AnyCADRapidSDK(ARS)是一个包含三维几何造型、图形显示、数据管理等模块综合三维图形平台,支持Windows、Linux、MacOS多操作系统,支持.NET、Python、Java多开发语言,可以用于开发CAD/CAE/CAM/SIM应用程序,用于机械、建筑、电力、教育、机器人、科学计算等领域。目前计划基于Anyc......
  • .NET|--WPF|--如何使用LINQPad创建一个WPF示例
    1.安装包管理器#搜索框内需要填入↓"id=Microsoft.NETCore.App""id=Microsoft.WindowsDesktop.App.Ref"2.代码voidMain(){ varapp=newSystem.Windows.Application(); varmainWindow=newSystem.Windows.Window { Title="SimpleWPFProgra......
  • golang reflect 常见示例
    reflect是golang中元编程的能力体现。需要注意的是,reflect尽量不用,有性能问题,也有避免滥用的考虑。packagemainimport( "log" "reflect")typeAstruct{ aint bstring cbool}//实验reflect的相关函数funcmain(){ typeValue() callFunc()}funcother......
  • 通栏中不定数量的图片/轮播自适应宽高的简单示例
    最近接到一个需求,在一个页面会有多个通栏,每个通栏中会有不固定数量的图片或轮播图,要求各图片/轮播要同等比例自适应宽高,写成通用代码。示意图:光是图片好说,其中有swiper就会比较麻烦。代码:<divclass="container"><divclass="zt_banner"><divclass="swiper">......
  • Python-使用OpenCV(二)_第一个示例程序
    1、创建项目2、创建代码importcv2#加载图片image=cv2.imread("C:\\Users\\Administrator\\Pictures\\Screenshots\\20240311220733.png")#显示图片cv2.imshow("Image",image)#等待任意键被敲击cv2.waitKey(0)#关闭所有窗口cv2.destroyAllWindows()3、结......
  • YAML示例:创建daemonset
    apiVersion:apps/v1kind:DaemonSetmetadata:name:test-ds-1namespace:sjlabels:k8s-app:arksec-ds-1spec:selector:matchLabels:name:arksec-ds-1template:metadata:labels:name:arksec-ds-1spec:nodeSelector:#只让它在指定hostname的节点上创建,注意,......
  • Excalidraw画板调研-二次开发
    最近刚入职一家公司,主管让我研究一下Excalidraw。有一个需求需要用到画板,Excalidraw是开源的,某些功能如果通过传参无法做到,就需要二次开发。目前遇到几个困难:1.如果通过传参来实现,Excalidraw是基于react的,而我们的项目是VUE框架,就无法直接使用基于react的Excalidraw。虽然后面......
  • css通过子元素选择父元素的实现示例
    在CSS中,直接通过子元素选择其父元素并不直接支持,因为CSS的选择器是从上到下(从父元素到子元素)进行选择的,而不是相反。但是,你可以使用其他方法或技术来间接实现这一效果,比如使用JavaScript、jQuery或其他脚本语言,或者通过调整你的HTML结构和CSS样式来达到类似的效果。不过,我可以给......
  • WPF DrawingContext DrawingVisual OnRender()
    usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;usingSystem.Windows;usingSystem.Windows.Controls;usingSystem.Windows.Data;usingSystem.Windows.Documents;usingSystem.Windows.Input;......