gis-view-webgl2 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -8
- package/dist/gis-view.css +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1036 -249
- package/dist/index.js +1 -1
- package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +194 -0
- package/docs/GisView.md +9 -8
- package/docs/GisView/345/256/236/347/216/260/345/216/237/347/220/206.md +2 -2
- package/docs/ai/06-/344/274/232/350/257/235/347/264/242/345/274/225/344/270/216/345/244/215/346/237/245/346/226/271/346/263/225.md +172 -0
- package/docs/api/index.html +545 -59
- package/docs/demos/BackendSyncDemo.vue +10 -8
- package/docs/demos/BasicDemo.vue +101 -101
- package/docs/demos/BatchAddDemo.vue +5 -5
- package/docs/demos/ChannelDemo.vue +617 -10
- package/docs/demos/ConnectDemo.vue +392 -0
- package/docs/demos/CopyMergeDemo.vue +980 -929
- package/docs/demos/DecorationDraftDemo.vue +1300 -0
- package/docs/demos/DemoShell.vue +569 -240
- package/docs/demos/DragDropDemo.vue +11 -11
- package/docs/demos/EffectsDemo.vue +6 -6
- package/docs/demos/EventDemo.vue +327 -327
- package/docs/demos/ExtensionDemo.vue +458 -0
- package/docs/demos/GisViewDemo.vue +61 -48
- package/docs/demos/GraphicDemo.vue +338 -0
- package/docs/demos/LabelDemo.vue +553 -497
- package/docs/demos/MinimapDemo.vue +358 -0
- package/docs/demos/PerfDemo.vue +29 -0
- package/docs/demos/SearchFilterDemo.vue +412 -0
- package/docs/demos/StyleDemo.vue +121 -121
- package/docs/demos/api-index.js +98 -77
- package/docs/demos/demos.js +159 -135
- package/docs/demos/shared/demoData.js +107 -107
- package/docs/demos/shared/demoStyle.css +249 -249
- package/docs/demos/shared/fakeBackend.js +93 -6
- package/docs/format/03-/346/213/220/347/202/271/350/243/205/351/245/260/344/270/216/346/240/267/345/274/217.md +28 -8
- package/docs/format/04-/351/200/232/351/201/223/344/270/216/346/240/207/347/255/276.md +5 -4
- package/docs/format/06-/345/257/274/345/205/245/345/257/274/345/207/272/344/270/216/346/240/207/345/207/206/346/226/207/346/241/243.md +8 -6
- package/docs/format/07-/344/272/213/344/273/266/350/275/275/350/215/267/344/270/216/347/244/272/344/276/213/346/225/260/346/215/256.md +6 -6
- package/docs/guide/sample-document.html +4 -4
- package/docs/index.html +180 -9
- package/docs/internals/11-/350/246/206/347/233/226/345/261/202/346/240/207/347/255/276/344/270/216/346/217/220/347/244/272.md +1 -1
- package/docs/internals/13-/351/271/260/347/234/274/345/233/276.md +3 -2
- package/docs/internals/15-03-/351/200/220/350/275/256/350/256/260/345/275/225-/346/200/247/350/203/275/344/270/216/344/277/256/345/244/215-/344/270/213.md +16 -0
- package/docs/internals/16-03-/344/274/230/345/214/226/344/270/216/345/217/226/350/210/215-/344/270/213.md +11 -0
- package/docs/internals/17-/350/260/203/350/257/225/344/270/216/346/211/251/345/261/225/345/273/272/350/256/256.md +1 -1
- package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +18 -4
- package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +110 -25
- package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +13 -8
- package/docs/manual/06-/346/225/260/346/215/256/344/270/216/345/234/272/346/231/257/346/233/264/346/226/260.md +111 -10
- package/docs/manual/07-/346/214/207/351/222/210/344/270/216/350/246/201/347/264/240/347/274/226/350/276/221.md +47 -9
- package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +17 -15
- package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +61 -11
- package/docs/manual/10-/351/200/232/351/201/223/347/274/226/350/276/221/344/270/216/346/226/260/345/242/236.md +74 -7
- package/docs/manual/11-/346/240/267/345/274/217/344/270/216/350/247/204/345/210/231.md +3 -2
- package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +260 -3
- package/docs/manual/13-/345/212/250/346/225/210/344/270/216/345/233/276/345/205/203.md +6 -5
- package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +27 -14
- package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +142 -49
- package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +31 -29
- package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +55 -39
- package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +189 -64
- package/docs/manual/19-/350/257/273/346/225/260/344/270/216/346/213/226/346/213/275/351/242/204/350/247/210.md +4 -4
- package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +42 -2
- package/docs/manual/21-/350/257/264/346/230/216/344/270/216/345/270/270/350/247/201/351/227/256/351/242/230.md +22 -6
- package/docs/playground/index.html +5 -5
- package/docs/steps/README.md +14 -10
- package/docs/steps/check-doc-truth.mjs +1 -1
- package/docs/steps/selftest-actions.html +10 -10
- package/docs/steps/selftest-data.html +96 -14
- package/docs/steps/selftest-decoration.html +577 -0
- package/docs/steps/selftest-demo.html +6 -5
- package/docs/steps/selftest-demos.html +2201 -1612
- package/docs/steps/selftest-docs.html +89 -16
- package/docs/steps/selftest-effects.html +5 -5
- package/docs/steps/selftest-features.html +757 -86
- package/docs/steps/selftest-full.html +583 -583
- package/docs/steps/selftest-labels.html +1034 -652
- package/docs/steps/selftest-perf.html +1899 -1844
- package/docs/steps/selftest-scale.html +117 -7
- package/docs/steps/selftest-select.html +24 -17
- package/docs/steps/step-10-full.js +1 -1
- package/docs//346/216/245/345/217/243/345/215/217/350/256/256.md +167 -17
- package/docs//346/225/260/346/215/256/346/240/274/345/274/217.md +1 -1
- package/package.json +43 -43
- package/docs/demos/HistoryDemo.vue +0 -230
- package/docs/demos/InterfaceDemo.vue +0 -309
- package/docs/demos/SampleDemo.vue +0 -283
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
/**
|
|
3
|
+
* 扩展能力:用同一个视图把五个扩展点串起来 ——
|
|
4
|
+
* 运行时插件(view.use)、自定义图元(registerSymbol)、自定义条件运算符(registerOperator)
|
|
5
|
+
* 自定义导出格式(defineExportFormat)与 WebGL 自定义层(addWebGLLayer)。
|
|
6
|
+
* 每一块都可单独开关,读数里能直接看到注册表与可见数据。
|
|
7
|
+
*/
|
|
8
|
+
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
|
9
|
+
import { createGisView, defineExportFormat } from '../../src/index.js'
|
|
10
|
+
import { buildDemoData } from './shared/demoData.js'
|
|
11
|
+
import './shared/demoStyle.css'
|
|
12
|
+
|
|
13
|
+
const emit = defineEmits(['notice'])
|
|
14
|
+
const container = ref(null)
|
|
15
|
+
const operatorOn = ref(false)
|
|
16
|
+
const symbolOn = ref(false)
|
|
17
|
+
const pluginOn = ref(false)
|
|
18
|
+
const symbolVariant = ref('cross')
|
|
19
|
+
const webglMounted = ref(false)
|
|
20
|
+
const webglEnabled = ref(false)
|
|
21
|
+
const webglOrder = ref(20)
|
|
22
|
+
const webglDrawCount = ref(0)
|
|
23
|
+
const readout = ref('正在创建视图…')
|
|
24
|
+
const exportText = ref('尚未导出。点上面的按钮,会走 defineExportFormat 注册的同步适配器。')
|
|
25
|
+
|
|
26
|
+
const dataset = buildDemoData({ count: 18, channels: 0 })
|
|
27
|
+
const points = dataset.points.map((point, index) => ({ ...point, extension_index: index }))
|
|
28
|
+
const lines = dataset.linesWithoutChannel
|
|
29
|
+
|
|
30
|
+
let view = null
|
|
31
|
+
let disposeOperator = null
|
|
32
|
+
let disposeSymbol = null
|
|
33
|
+
let disposePlugin = null
|
|
34
|
+
let disposeExport = null
|
|
35
|
+
let disposeWebgl = null
|
|
36
|
+
|
|
37
|
+
/** WebGL 自定义层:按 gl 缓存程序 / 缓冲,draw 里只更新 uniform 与画一笔 */
|
|
38
|
+
const webglResources = new WeakMap()
|
|
39
|
+
const ensureWebglResources = (gl) => {
|
|
40
|
+
const cached = webglResources.get(gl)
|
|
41
|
+
if (cached) {
|
|
42
|
+
return cached
|
|
43
|
+
}
|
|
44
|
+
const compile = (type, source) => {
|
|
45
|
+
const shader = gl.createShader(type)
|
|
46
|
+
gl.shaderSource(shader, source)
|
|
47
|
+
gl.compileShader(shader)
|
|
48
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
49
|
+
const reason = gl.getShaderInfoLog(shader)
|
|
50
|
+
gl.deleteShader(shader)
|
|
51
|
+
throw new Error(reason || 'shader 编译失败')
|
|
52
|
+
}
|
|
53
|
+
return shader
|
|
54
|
+
}
|
|
55
|
+
const vertex = compile(gl.VERTEX_SHADER, `
|
|
56
|
+
attribute vec2 a_position;
|
|
57
|
+
uniform vec2 u_center;
|
|
58
|
+
uniform vec2 u_radius;
|
|
59
|
+
varying vec2 v_uv;
|
|
60
|
+
void main() {
|
|
61
|
+
v_uv = a_position;
|
|
62
|
+
gl_Position = vec4(u_center + a_position * u_radius, 0.0, 1.0);
|
|
63
|
+
}
|
|
64
|
+
`)
|
|
65
|
+
const fragment = compile(gl.FRAGMENT_SHADER, `
|
|
66
|
+
precision mediump float;
|
|
67
|
+
varying vec2 v_uv;
|
|
68
|
+
uniform vec4 u_color;
|
|
69
|
+
void main() {
|
|
70
|
+
float d = length(v_uv);
|
|
71
|
+
float ring = smoothstep(0.62, 0.78, d) * (1.0 - smoothstep(0.92, 1.08, d));
|
|
72
|
+
float glow = (1.0 - smoothstep(0.0, 1.0, d)) * 0.18;
|
|
73
|
+
gl_FragColor = vec4(u_color.rgb, u_color.a * (ring + glow));
|
|
74
|
+
}
|
|
75
|
+
`)
|
|
76
|
+
const program = gl.createProgram()
|
|
77
|
+
gl.attachShader(program, vertex)
|
|
78
|
+
gl.attachShader(program, fragment)
|
|
79
|
+
gl.linkProgram(program)
|
|
80
|
+
gl.deleteShader(vertex)
|
|
81
|
+
gl.deleteShader(fragment)
|
|
82
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
83
|
+
const reason = gl.getProgramInfoLog(program)
|
|
84
|
+
gl.deleteProgram(program)
|
|
85
|
+
throw new Error(reason || 'shader program 链接失败')
|
|
86
|
+
}
|
|
87
|
+
const vao = gl.createVertexArray()
|
|
88
|
+
const buffer = gl.createBuffer()
|
|
89
|
+
gl.bindVertexArray(vao)
|
|
90
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
|
|
91
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW)
|
|
92
|
+
const position = gl.getAttribLocation(program, 'a_position')
|
|
93
|
+
gl.enableVertexAttribArray(position)
|
|
94
|
+
gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0)
|
|
95
|
+
gl.bindVertexArray(null)
|
|
96
|
+
const resources = {
|
|
97
|
+
program,
|
|
98
|
+
vao,
|
|
99
|
+
center: gl.getUniformLocation(program, 'u_center'),
|
|
100
|
+
radius: gl.getUniformLocation(program, 'u_radius'),
|
|
101
|
+
color: gl.getUniformLocation(program, 'u_color')
|
|
102
|
+
}
|
|
103
|
+
webglResources.set(gl, resources)
|
|
104
|
+
return resources
|
|
105
|
+
}
|
|
106
|
+
/** 在内置画面之上画一圈跟着第一节点呼吸的自定义光环(完全用自己的 GL 程序) */
|
|
107
|
+
const drawWebglExtension = ({ gl, canvas, viewport, frame, project }) => {
|
|
108
|
+
try {
|
|
109
|
+
const resources = ensureWebglResources(gl)
|
|
110
|
+
const anchor = points[0] ?? { x: 0, y: 0 }
|
|
111
|
+
const [x, y] = project(anchor.x, anchor.y)
|
|
112
|
+
const width = Math.max(viewport.cssWidth, 1)
|
|
113
|
+
const height = Math.max(viewport.cssHeight, 1)
|
|
114
|
+
const radius = 18 + Math.sin(frame * 0.12) * 4
|
|
115
|
+
gl.useProgram(resources.program)
|
|
116
|
+
gl.bindVertexArray(resources.vao)
|
|
117
|
+
gl.uniform2f(resources.center, x / width * 2 - 1, 1 - y / height * 2)
|
|
118
|
+
gl.uniform2f(resources.radius, radius / width, radius / height)
|
|
119
|
+
gl.uniform4f(resources.color, 0.95, 0.45, 0.18, 0.9)
|
|
120
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
|
|
121
|
+
gl.bindVertexArray(null)
|
|
122
|
+
webglDrawCount.value += 1
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
emit('notice', `WebGL 自定义层绘制失败:${error instanceof Error ? error.message : String(error)}`)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** 自定义图元:外圈实心 + 十字,颜色 / 描边 / 尺寸都取组件递进来的读数 */
|
|
130
|
+
const paintSpark = (context, info) => {
|
|
131
|
+
const centerX = info.width / 2
|
|
132
|
+
const centerY = info.height / 2
|
|
133
|
+
const radius = Math.min(info.width, info.height) * 0.31
|
|
134
|
+
const stroke = Math.max(1, info.strokeWidth * info.scale)
|
|
135
|
+
context.clearRect(0, 0, info.width, info.height)
|
|
136
|
+
context.beginPath()
|
|
137
|
+
context.arc(centerX, centerY, radius, 0, Math.PI * 2)
|
|
138
|
+
context.fillStyle = info.color
|
|
139
|
+
context.fill()
|
|
140
|
+
context.lineWidth = stroke
|
|
141
|
+
context.strokeStyle = info.strokeColor
|
|
142
|
+
context.stroke()
|
|
143
|
+
context.beginPath()
|
|
144
|
+
context.moveTo(centerX - radius * 0.52, centerY)
|
|
145
|
+
context.lineTo(centerX + radius * 0.52, centerY)
|
|
146
|
+
context.moveTo(centerX, centerY - radius * 0.52)
|
|
147
|
+
context.lineTo(centerX, centerY + radius * 0.52)
|
|
148
|
+
context.lineWidth = Math.max(1, stroke * 0.72)
|
|
149
|
+
context.strokeStyle = 'rgba(255, 255, 255, 0.92)'
|
|
150
|
+
context.stroke()
|
|
151
|
+
}
|
|
152
|
+
/** 同名覆盖的第二支画笔:菱形,用来演示 registerSymbol 重新注册后贴图与标签锚点一起更新 */
|
|
153
|
+
const paintSparkDiamond = (context, info) => {
|
|
154
|
+
const centerX = info.width / 2
|
|
155
|
+
const centerY = info.height / 2
|
|
156
|
+
const radius = Math.min(info.width, info.height) * 0.42
|
|
157
|
+
context.clearRect(0, 0, info.width, info.height)
|
|
158
|
+
context.beginPath()
|
|
159
|
+
context.moveTo(centerX, centerY - radius)
|
|
160
|
+
context.lineTo(centerX + radius, centerY)
|
|
161
|
+
context.lineTo(centerX, centerY + radius)
|
|
162
|
+
context.lineTo(centerX - radius, centerY)
|
|
163
|
+
context.closePath()
|
|
164
|
+
context.fillStyle = info.color
|
|
165
|
+
context.fill()
|
|
166
|
+
context.lineWidth = Math.max(1, info.strokeWidth * info.scale)
|
|
167
|
+
context.strokeStyle = info.strokeColor
|
|
168
|
+
context.stroke()
|
|
169
|
+
context.beginPath()
|
|
170
|
+
context.arc(centerX, centerY, radius * 0.18, 0, Math.PI * 2)
|
|
171
|
+
context.fillStyle = 'rgba(255, 255, 255, 0.92)'
|
|
172
|
+
context.fill()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const visiblePointCount = () => view?.getVisibleData()?.points?.length ?? points.length
|
|
176
|
+
const refreshReadout = () => {
|
|
177
|
+
if (!view) {
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
const operators = view.listOperators()
|
|
181
|
+
const symbols = view.listSymbols()
|
|
182
|
+
readout.value = [
|
|
183
|
+
`自定义运算符 ${operators.includes('evenIndex') ? 'evenIndex 已注册' : '未注册'}`,
|
|
184
|
+
`筛选 ${operatorOn.value ? '开' : '关'}(可见 ${visiblePointCount()} / ${points.length} 个)`,
|
|
185
|
+
`自定义图元 ${symbols.includes('spark') ? `spark 已注册(${symbolVariant.value === 'diamond' ? '菱形' : '十字'})` : '未注册'}${symbolOn.value ? ' / 使用中' : ''}`,
|
|
186
|
+
`运行时插件 ${pluginOn.value ? '已挂载' : '未挂载'}`,
|
|
187
|
+
`WebGL 上下文 ${view.gl ? '可直接取' : '不可用'}`,
|
|
188
|
+
`WebGL 层 ${webglMounted.value ? `已挂载 · ${webglEnabled.value ? '绘制中' : '已隐藏'} · order ${webglOrder.value} · draw ${webglDrawCount.value} 次` : '未挂载'}`
|
|
189
|
+
].join(' · ')
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const buildOptions = () => ({
|
|
193
|
+
points,
|
|
194
|
+
lines,
|
|
195
|
+
pointStyle: { size: 8, color: '#ffffff', strokeColor: '#3678bf', strokeWidth: 2 },
|
|
196
|
+
lineStyle: { width: 1.5, color: 'rgba(120, 168, 226, 0.62)' },
|
|
197
|
+
showGrid: true,
|
|
198
|
+
gridColor: 'rgba(126, 168, 214, 0.2)',
|
|
199
|
+
fitPadding: 40,
|
|
200
|
+
features: 'view',
|
|
201
|
+
label: { enabled: true, content: '{name}', position: 'top', gap: 7, maxCount: 20, style: { color: '#27405f', fontSize: '11px' } },
|
|
202
|
+
onReady: () => emit('notice', '扩展能力:已挂载 5 个扩展点')
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
const toggleOperator = () => {
|
|
206
|
+
operatorOn.value = !operatorOn.value
|
|
207
|
+
view?.setFilter(operatorOn.value ? { extension_index: { evenIndex: true } } : null)
|
|
208
|
+
refreshReadout()
|
|
209
|
+
emit('notice', operatorOn.value ? '自定义运算符 evenIndex 正在筛选偶数节点' : '已清空自定义筛选')
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const toggleSymbol = () => {
|
|
213
|
+
symbolOn.value = !symbolOn.value
|
|
214
|
+
// 一次批量补丁:只发一条 updatePoints、只记一步历史(与逐个 updatePoint({ symbol }) 的最终数据一致)
|
|
215
|
+
const result = view?.patchPoints(points.map(point => point.id), { symbol: symbolOn.value ? 'spark' : null })
|
|
216
|
+
refreshReadout()
|
|
217
|
+
emit('notice', symbolOn.value ? `已切到自定义图元 spark(${result?.count ?? 0} 个节点)` : '已回到内置圆形图元')
|
|
218
|
+
}
|
|
219
|
+
const cycleSymbolPainter = () => {
|
|
220
|
+
if (!view) {
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
const next = symbolVariant.value === 'diamond' ? 'cross' : 'diamond'
|
|
224
|
+
const previous = disposeSymbol
|
|
225
|
+
disposeSymbol = view.registerSymbol('spark', next === 'diamond' ? paintSparkDiamond : paintSpark)
|
|
226
|
+
previous?.()
|
|
227
|
+
symbolVariant.value = next
|
|
228
|
+
refreshReadout()
|
|
229
|
+
emit('notice', `同名重新注册 spark:画笔已换成${next === 'diamond' ? '菱形' : '十字'},画面当场重烘焙`)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const togglePlugin = () => {
|
|
233
|
+
pluginOn.value = !pluginOn.value
|
|
234
|
+
if (pluginOn.value) {
|
|
235
|
+
disposePlugin = view?.use(instance => {
|
|
236
|
+
const badge = document.createElement('div')
|
|
237
|
+
badge.className = 'demo-extension-badge'
|
|
238
|
+
badge.dataset.demo = 'extension-plugin-badge'
|
|
239
|
+
badge.textContent = 'plugin · view.use()'
|
|
240
|
+
instance.el.appendChild(badge)
|
|
241
|
+
return {
|
|
242
|
+
destroy() {
|
|
243
|
+
badge.remove()
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}) ?? null
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
disposePlugin?.()
|
|
250
|
+
disposePlugin = null
|
|
251
|
+
}
|
|
252
|
+
refreshReadout()
|
|
253
|
+
emit('notice', pluginOn.value ? '插件已挂载:左下角出现徽标' : '插件已卸载')
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** 挂载 / 移除 WebGL 自定义层:层本身不改数据,只在每帧最后叠加一圈光环 */
|
|
257
|
+
const toggleWebgl = () => {
|
|
258
|
+
if (!view) {
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
if (webglMounted.value) {
|
|
262
|
+
view.removeWebGLLayer('extension-webgl')
|
|
263
|
+
disposeWebgl = null
|
|
264
|
+
webglMounted.value = false
|
|
265
|
+
webglEnabled.value = false
|
|
266
|
+
webglDrawCount.value = 0
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
disposeWebgl = view.addWebGLLayer({
|
|
270
|
+
id: 'extension-webgl',
|
|
271
|
+
order: webglOrder.value,
|
|
272
|
+
enabled: true,
|
|
273
|
+
draw: drawWebglExtension
|
|
274
|
+
})
|
|
275
|
+
webglMounted.value = true
|
|
276
|
+
webglEnabled.value = true
|
|
277
|
+
webglDrawCount.value = 0
|
|
278
|
+
}
|
|
279
|
+
refreshReadout()
|
|
280
|
+
emit('notice', webglMounted.value ? 'WebGL 自定义层已挂载:第一个节点外出现自定义光环' : 'WebGL 自定义层已移除')
|
|
281
|
+
}
|
|
282
|
+
/** 只切 enabled:层还留着,跳过 draw 但不丢程序与顺序 */
|
|
283
|
+
const toggleWebglEnabled = () => {
|
|
284
|
+
if (!view || !webglMounted.value) {
|
|
285
|
+
return
|
|
286
|
+
}
|
|
287
|
+
webglEnabled.value = !webglEnabled.value
|
|
288
|
+
view.updateWebGLLayer('extension-webgl', { enabled: webglEnabled.value })
|
|
289
|
+
refreshReadout()
|
|
290
|
+
emit('notice', webglEnabled.value ? 'WebGL 自定义层恢复绘制' : 'WebGL 自定义层已隐藏(enabled: false)')
|
|
291
|
+
}
|
|
292
|
+
/** 换 order:单层时看不出叠放差异,但读数与 getWebGLLayers 的排序会跟着变 */
|
|
293
|
+
const cycleWebglOrder = () => {
|
|
294
|
+
if (!view || !webglMounted.value) {
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
webglOrder.value = webglOrder.value === 20 ? 40 : 20
|
|
298
|
+
view.updateWebGLLayer('extension-webgl', { order: webglOrder.value })
|
|
299
|
+
refreshReadout()
|
|
300
|
+
emit('notice', `WebGL 自定义层 order 改为 ${webglOrder.value}`)
|
|
301
|
+
}
|
|
302
|
+
const requestWebglFrame = () => {
|
|
303
|
+
view?.requestRender()
|
|
304
|
+
refreshReadout()
|
|
305
|
+
emit('notice', '已调用 requestRender():下一帧会重新执行自定义层的 draw')
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const exportSummary = () => {
|
|
309
|
+
if (!view) {
|
|
310
|
+
return
|
|
311
|
+
}
|
|
312
|
+
try {
|
|
313
|
+
const result = view.exportData({ format: 'extension-summary', labels: false })
|
|
314
|
+
exportText.value = JSON.stringify(result, null, 2)
|
|
315
|
+
emit('notice', `自定义导出完成:${result.highLoadCount} 个高负载节点`)
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
exportText.value = `导出失败:${error instanceof Error ? error.message : String(error)}`
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
onMounted(() => {
|
|
323
|
+
view = createGisView(container.value, buildOptions())
|
|
324
|
+
disposeOperator = view.registerOperator('evenIndex', enabled => current => !enabled || Number(current) % 2 === 0)
|
|
325
|
+
disposeSymbol = view.registerSymbol('spark', paintSpark)
|
|
326
|
+
disposeExport = defineExportFormat('extension-summary', ({ document, counts }) => {
|
|
327
|
+
const highLoad = document.points.filter(point => Number(point.max_loading_rate) >= 1)
|
|
328
|
+
return {
|
|
329
|
+
schema: 'gis-view.extension-summary.v1',
|
|
330
|
+
counts,
|
|
331
|
+
highLoadCount: highLoad.length,
|
|
332
|
+
highLoadStations: highLoad.map(point => ({
|
|
333
|
+
id: point.id,
|
|
334
|
+
name: point.name,
|
|
335
|
+
load: point.max_loading_rate
|
|
336
|
+
}))
|
|
337
|
+
}
|
|
338
|
+
})
|
|
339
|
+
refreshReadout()
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
onBeforeUnmount(() => {
|
|
343
|
+
disposePlugin?.()
|
|
344
|
+
disposePlugin = null
|
|
345
|
+
disposeSymbol?.()
|
|
346
|
+
disposeSymbol = null
|
|
347
|
+
disposeOperator?.()
|
|
348
|
+
disposeOperator = null
|
|
349
|
+
disposeExport?.()
|
|
350
|
+
disposeExport = null
|
|
351
|
+
disposeWebgl?.()
|
|
352
|
+
disposeWebgl = null
|
|
353
|
+
view?.destroy()
|
|
354
|
+
view = null
|
|
355
|
+
})
|
|
356
|
+
</script>
|
|
357
|
+
|
|
358
|
+
<template>
|
|
359
|
+
<section class="dv-demo">
|
|
360
|
+
<div class="dv-stage">
|
|
361
|
+
<div ref="container" class="dv-canvas"></div>
|
|
362
|
+
<div class="dv-toolbar">
|
|
363
|
+
<button type="button" @click="view?.fitView(40)">适应全部</button>
|
|
364
|
+
</div>
|
|
365
|
+
</div>
|
|
366
|
+
<aside class="dv-panel">
|
|
367
|
+
<div class="dv-group">
|
|
368
|
+
<h3>五个扩展点</h3>
|
|
369
|
+
<div class="dv-row">
|
|
370
|
+
<button
|
|
371
|
+
type="button"
|
|
372
|
+
class="dv-button"
|
|
373
|
+
:class="{ 'dv-button--primary': operatorOn }"
|
|
374
|
+
:aria-pressed="operatorOn"
|
|
375
|
+
data-demo="extension-operator"
|
|
376
|
+
@click="toggleOperator"
|
|
377
|
+
>{{ operatorOn ? '关闭自定义筛选' : '自定义运算符筛选' }}</button>
|
|
378
|
+
</div>
|
|
379
|
+
<div class="dv-row">
|
|
380
|
+
<button
|
|
381
|
+
type="button"
|
|
382
|
+
class="dv-button"
|
|
383
|
+
:class="{ 'dv-button--primary': symbolOn }"
|
|
384
|
+
:aria-pressed="symbolOn"
|
|
385
|
+
data-demo="extension-symbol"
|
|
386
|
+
@click="toggleSymbol"
|
|
387
|
+
>{{ symbolOn ? '换回内置圆形' : '使用自定义图元' }}</button>
|
|
388
|
+
<button type="button" class="dv-button" :disabled="!symbolOn" data-demo="extension-symbol-variant" @click="cycleSymbolPainter">同名换画笔</button>
|
|
389
|
+
</div>
|
|
390
|
+
<div class="dv-row">
|
|
391
|
+
<button
|
|
392
|
+
type="button"
|
|
393
|
+
class="dv-button"
|
|
394
|
+
:class="{ 'dv-button--primary': pluginOn }"
|
|
395
|
+
:aria-pressed="pluginOn"
|
|
396
|
+
data-demo="extension-plugin"
|
|
397
|
+
@click="togglePlugin"
|
|
398
|
+
>{{ pluginOn ? '卸载运行时插件' : '挂载运行时插件' }}</button>
|
|
399
|
+
</div>
|
|
400
|
+
<div class="dv-row">
|
|
401
|
+
<button type="button" class="dv-button dv-button--primary" data-demo="extension-export" @click="exportSummary">自定义格式导出</button>
|
|
402
|
+
</div>
|
|
403
|
+
<div class="dv-row">
|
|
404
|
+
<button
|
|
405
|
+
type="button"
|
|
406
|
+
class="dv-button"
|
|
407
|
+
:class="{ 'dv-button--primary': webglMounted }"
|
|
408
|
+
:aria-pressed="webglMounted"
|
|
409
|
+
data-demo="extension-webgl"
|
|
410
|
+
@click="toggleWebgl"
|
|
411
|
+
>{{ webglMounted ? '移除 WebGL 自定义层' : '挂载 WebGL 自定义层' }}</button>
|
|
412
|
+
<button type="button" class="dv-button" :disabled="!webglMounted" data-demo="extension-webgl-enabled" @click="toggleWebglEnabled">{{ webglEnabled ? '隐藏层' : '显示层' }}</button>
|
|
413
|
+
</div>
|
|
414
|
+
<div class="dv-row">
|
|
415
|
+
<button type="button" class="dv-button" :disabled="!webglMounted" data-demo="extension-webgl-order" @click="cycleWebglOrder">order {{ webglOrder }}(切换)</button>
|
|
416
|
+
<button type="button" class="dv-button" :disabled="!webglMounted" data-demo="extension-webgl-request" @click="requestWebglFrame">requestRender()</button>
|
|
417
|
+
</div>
|
|
418
|
+
<p class="dv-hint">运算符注册进条件语言,图元注册进符号表,插件挂在实例上,导出格式挂在模块注册表,WebGL 层直接拿 <code>gl</code> 在内置内容之后画自己的东西;五者都不需要改组件源码。图元切换走一次 <code>patchPoints</code> 批量补丁,WebGL 层可用 <code>enabled</code> / <code>order</code> 原地维护。</p>
|
|
419
|
+
</div>
|
|
420
|
+
<div class="dv-group">
|
|
421
|
+
<h3>注册表读数</h3>
|
|
422
|
+
<p class="dv-readout" data-demo="extension-readout" aria-live="polite">{{ readout }}</p>
|
|
423
|
+
</div>
|
|
424
|
+
<div class="dv-group">
|
|
425
|
+
<h3>导出结果</h3>
|
|
426
|
+
<pre class="dv-readout demo-extension-export" data-demo="extension-export-text">{{ exportText }}</pre>
|
|
427
|
+
</div>
|
|
428
|
+
</aside>
|
|
429
|
+
</section>
|
|
430
|
+
</template>
|
|
431
|
+
|
|
432
|
+
<style scoped>
|
|
433
|
+
.demo-extension-export {
|
|
434
|
+
max-height: 190px;
|
|
435
|
+
margin: 0;
|
|
436
|
+
overflow: auto;
|
|
437
|
+
white-space: pre-wrap;
|
|
438
|
+
word-break: break-all;
|
|
439
|
+
font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
|
|
440
|
+
font-size: 11px;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
:global(.demo-extension-badge) {
|
|
444
|
+
position: absolute;
|
|
445
|
+
left: 10px;
|
|
446
|
+
bottom: 10px;
|
|
447
|
+
z-index: 4;
|
|
448
|
+
padding: 5px 9px;
|
|
449
|
+
border: 1px solid rgba(54, 120, 191, 0.35);
|
|
450
|
+
border-radius: 999px;
|
|
451
|
+
background: rgba(255, 255, 255, 0.9);
|
|
452
|
+
color: #2f5e94;
|
|
453
|
+
font-size: 11px;
|
|
454
|
+
line-height: 1.4;
|
|
455
|
+
pointer-events: none;
|
|
456
|
+
box-shadow: 0 4px 14px rgba(31, 62, 105, 0.14);
|
|
457
|
+
}
|
|
458
|
+
</style>
|