官网demo地址:
这个示例展示了如何用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">
端点类型
<label class="radio-inline">
<input
type="radio"
class="uniform"
name="stroke-line-cap"
@input="inputListener"
value="butt"
checked
/>
<code>butt</code>
</label>
<label class="radio-inline">
<input
type="radio"
class="uniform"
@input="inputListener"
name="stroke-line-cap"
value="round"
/>
<code>round</code>
</label>
<label class="radio-inline">
<input
type="radio"
class="uniform"
@input="inputListener"
name="stroke-line-cap"
value="square"
/>
<code>square</code>
</label>
</div>
<div class="form-group">
连接类型
<label class="radio-inline">
<input
type="radio"
class="uniform"
@input="inputListener"
name="joinType"
value="miter"
checked
/>
<code>miter</code>
</label>
<label class="radio-inline">
<input
type="radio"
class="uniform"
@input="inputListener"
name="joinType"
value="round"
/>
<code>round</code>
</label>
<label class="radio-inline">
<input
type="radio"
class="uniform"
@input="inputListener"
name="joinType"
value="bevel"
/>
<code>bevel</code>
</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