ArcGIS JS 基础教程(30):体元系列 - VoxelSlice 体元切片

作者:非科班Java出身GISer日期:2026/9/5

ArcGIS JS 基础教程(30):体元系列 - VoxelSlice 体元切片

    • 零、写在前面
    • 一、功能介绍
    • 二、功能实现
      • 2.1 创建并添加切片
        • 2.2 切片的三个核心属性
        • 2.3 管理集合(增删改)
    • 三、功能应用
    • 四、核心代码
    • 五、在线示例
    • 六、关键 API 说明
    • 七、系列导航

零、写在前面

📌 本系列教程完整目录ArcGIS JS 系列基础教程(100个项目常用热门功能)

💡 在线示例:完整可运行的 HTML 示例,无需任何环境配置,可直接在浏览器中打开体验
🗂️ 专栏导航:收藏 + 关注,专栏文章第一时间送达
❤️ 一键三连:点赞 + 评论 + 收藏


一、功能介绍

VoxelSlice(体元切片)沿一个无限平面裁剪体数据体积,得到一个可渲染的凸壳(convex shell)。它常用于:

  • 把庞大的三维体数据"切开",只展示某一截面(如某一高度层、某一经度面)的标量场,方便观察内部结构;
  • 定义感兴趣区域(Area of Interest),屏蔽与当前分析无关的体素。

参考:VoxelSlice API | VoxelVolumeStyle API | 官方示例 Create area of interest for VoxelLayer

⚠️ 关键概念VoxelSlicepoint体素空间坐标(基于 VoxelVolume.sizeInVoxels 的索引 [x, y, z]),不是经纬度等地理坐标。这正是它区别于普通 Slice 分析工具的地方。


二、功能实现

2.1 创建并添加切片

切片由 VoxelSlice 构造,最终装入 VoxelVolumeStyle.slices 这个 Collection 集合中:

1const VoxelSlice = await $arcgis.import("@arcgis/core/layers/voxel/VoxelSlice.js");
2
3voxelLayer.when(() => {
4    const vol = voxelLayer.getVolume(null);   // VoxelVolume(只读元信息)
5    const volSize = vol.sizeInVoxels;          // 体素空间尺寸 [x, y, z]
6
7    const volumeStyle = voxelLayer.getVolumeStyle(null); // VoxelVolumeStyle
8    voxelLayer.enableSlices = true;            // 启用切片可视化
9
10    // 水平切片:位于中间高度
11    const hSlice = new VoxelSlice({
12        orientation: 0,
13        tilt: 0,
14        point: [0, 0, Math.floor(volSize[2] / 2)]
15    });
16    volumeStyle.slices.add(hSlice);            // 添加进集合即可渲染
17});
18

2.2 切片的三个核心属性

属性类型说明
orientationnumber切片平面的方向角(单位:度)
tiltnumber切片平面的倾斜角(单位:度)
point[number,number,number]切片平面经过的一个点,以 sizeInVoxels 体素空间坐标 [x, y, z] 指定

此外还有 enabled(是否启用,默认 true)与 label(标签文本)。

2.3 管理集合(增删改)

slices 是一个 Collection<VoxelSlice>,支持标准集合操作:

1// 新增
2volumeStyle.slices.add(new VoxelSlice({ orientation: 270, tilt: 90, point: [midX, 0, 0] }));
3
4// 移除最末
5volumeStyle.slices.removeAt(volumeStyle.slices.length - 1);
6
7// 实时修改已有切片(无需重新加载)
8const s = volumeStyle.slices.getItemAt(0);
9s.orientation = 45;   // 立即生效
10s.tilt = 30;
11s.point = [128, 64, 89];
12

也可整体替换:volumeStyle.slices = [sliceA, sliceB];


三、功能应用

应用场景实现要点
单一高度层展示orientation:0, tilt:0, point:[0,0,z],z 取中间体素索引
东西向剖面orientation:270, tilt:90, point:[midX,0,0]
感兴趣区域裁剪将 point 设在关注体素附近,组合多个切片
交互式探查滑块绑定 getItemAt(i).orientation/tilt/point 实时更新

四、核心代码

📦 完整代码 已保存至 sample/lesson32_voxel_slice.html,可直接在浏览器打开。

1<!DOCTYPE html>
2<html lang="zh-CN">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title>第32课:VoxelSlice 体元切片</title>
7    <link rel="stylesheet" href="https://js.arcgis.com/5.0/esri/themes/light/main.css">
8    <script type="module" src="https://js.arcgis.com/5.0/"></script>
9    <style>
10        * { margin: 0; padding: 0; box-sizing: border-box; }
11        body { font-family: "Microsoft YaHei", sans-serif; }
12        #mapContainer { width: 100vw; height: 100vh; }
13        .page-title {
14            position: absolute; top: 20px; left: 50%; transform: translateX(-50%);
15            background: rgba(255,255,255,0.95); padding: 10px 24px; border-radius: 6px;
16            font-size: 18px; font-weight: bold; z-index: 100;
17            box-shadow: 0 2px 8px rgba(0,0,0,0.15);
18        }
19        .control-panel {
20            position: absolute; top: 80px; right: 20px;
21            background: rgba(255,255,255,0.95); padding: 16px; border-radius: 8px;
22            box-shadow: 0 2px 12px rgba(0,0,0,0.15);
23            z-index: 100; min-width: 320px;
24        }
25        .control-panel h3 { margin: 0 0 8px 0; font-size: 14px; color: #333; }
26        .section { margin-bottom: 12px; padding-bottom: 10px; border-bottom: 1px solid #eee; }
27        .section:last-child { border-bottom: none; margin-bottom: 0; }
28        .btn-row { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 6px; }
29        .btn-row button {
30            flex: 1; min-width: 60px; padding: 6px 0;
31            border: 1px solid #d9d9d9; border-radius: 4px;
32            background: white; cursor: pointer; font-size: 12px;
33        }
34        .btn-row button:hover { border-color: #1890ff; color: #1890ff; }
35        .btn-row button.on { background: #1890ff; color: white; border-color: #1890ff; }
36        .slider-row { margin-top: 8px; font-size: 12px; color: #333; }
37        .slider-row label { display: flex; justify-content: space-between; margin-bottom: 2px; }
38        .slider-row .v { color: #1890ff; font-weight: bold; }
39        .slider-row input[type=range] { width: 100%; }
40        .info-card {
41            margin-top: 10px; padding: 10px 12px;
42            background: #f0f5ff; border-radius: 6px;
43            border-left: 3px solid #1890ff; font-size: 12px; line-height: 1.6;
44        }
45        .info-card .val { font-weight: bold; color: #1890ff; }
46        .status-text {
47            position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%);
48            background: rgba(0,0,0,0.7); color: white; padding: 8px 20px;
49            border-radius: 20px; font-size: 13px; z-index: 100; pointer-events: none;
50            white-space: nowrap;
51        }
52    </style>
53</head>
54<body>
55<h1 class="page-title">第32课:VoxelSlice 体元切片</h1>
56
57<div class="control-panel">
58    <div class="section">
59        <h3> 添加切片(VoxelSlice)</h3>
60        <div class="btn-row">
61            <button id="btnAddH">水平切片</button>
62            <button id="btnAddV">垂直切片</button>
63            <button id="btnRemove">移除最末</button>
64        </div>
65    </div>
66    <div class="section">
67        <h3>🎯 选中切片</h3>
68        <div class="btn-row">
69            <button id="btnPrev">上一个</button>
70            <button id="btnNext">下一个</button>
71            <button id="btnEnable" class="on">切片已启用</button>
72        </div>
73    </div>
74    <div class="section">
75        <h3>⚙️ 切片参数(选中项)</h3>
76        <div class="slider-row">
77            <label>方向 orientation<span class="v" id="valOri"></span></label>
78            <input type="range" id="rngOri" min="0" max="360" value="0">
79        </div>
80        <div class="slider-row">
81            <label>倾斜 tilt<span class="v" id="valTilt"></span></label>
82            <input type="range" id="rngTilt" min="0" max="180" value="0">
83        </div>
84        <div class="slider-row">
85            <label>位置 point.x<span class="v" id="valPos">0</span></label>
86            <input type="range" id="rngPos" min="0" max="100" value="0">
87        </div>
88    </div>
89    <div class="info-card">
90        <div>体素尺寸:<span class="val" id="volSize">加载中...</span></div>
91        <div>切片数量:<span class="val" id="sliceCount">0</span></div>
92        <div>选中索引:<span class="val" id="activeIdx">-</span></div>
93    </div>
94</div>
95
96<div class="status-text" id="statusText">VoxelSlice | 沿无限平面裁剪体数据</div>
97
98<div id="mapContainer"></div>
99
100<script type="module">
101    const Map = await $arcgis.import("@arcgis/core/Map.js");
102    const SceneView = await $arcgis.import("@arcgis/core/views/SceneView.js");
103    const VoxelLayer = await $arcgis.import("@arcgis/core/layers/VoxelLayer.js");
104    const VoxelSlice = await $arcgis.import("@arcgis/core/layers/voxel/VoxelSlice.js");
105    const getTianditu = await $arcgis.import("https://openlayers.vip/examples/resources/tianditu.js");
106
107    const vecLayers = getTianditu.default({ type: "vec_w" });
108    const map = new Map({ basemap: { baseLayers: [vecLayers.base, vecLayers.anno] } });
109
110    const view = new SceneView({
111        container: "mapContainer", map: map,
112        viewingMode: "local",
113        camera: { position: { longitude: -70, latitude: 18, z: 800000 }, heading: 0, tilt: 60 }
114    });
115    window.view = view;
116
117    const voxelLayer = new VoxelLayer({
118        url: "https://gs3d.geosceneonline.cn/server/rest/services/Hosted/VoxelPM10/SceneServer"
119    });
120    map.add(voxelLayer);
121
122    let volumeStyle = null;   // VoxelVolumeStyle
123    let volSize = null;       // sizeInVoxels: [x, y, z]
124    let activeIndex = 0;      // 当前选中的切片索引
125
126    view.when(() => {
127        voxelLayer.when(() => {
128            const vol = voxelLayer.getVolume(null); // VoxelVolume(只读元信息)
129            volSize = vol.sizeInVoxels;             // 体素空间尺寸
130            document.getElementById("volSize").textContent = volSize.join(" × ");
131
132            // 样式集合:切片通过 VoxelVolumeStyle.slices 管理
133            volumeStyle = voxelLayer.getVolumeStyle(null);
134            voxelLayer.enableSlices = true;         // 启用切片可视化
135
136            // 默认添加一条水平切片(位于中间高度)
137            addSlice({ orientation: 0, tilt: 0, point: [0, 0, Math.floor(volSize[2] / 2)] });
138            // 再添加一条垂直切片(东西向,位于中间经度)
139            addSlice({ orientation: 270, tilt: 90, point: [Math.floor(volSize[0] / 2), 0, 0] });
140
141            view.goTo(voxelLayer.fullExtent, { duration: 2000 });
142        }).catch(err => {
143            document.getElementById("statusText").textContent = "加载失败";
144            console.error(err);
145        });
146    });
147
148    // 在体素空间内添加切片;point 坐标为 [x, y, z](体素索引,非地理坐标)
149    function addSlice(props) {
150        const slice = new VoxelSlice(props);
151        volumeStyle.slices.add(slice);
152        activeIndex = volumeStyle.slices.length - 1;
153        syncControlsToActive();
154        updateInfo();
155    }
156
157    function updateInfo() {
158        document.getElementById("sliceCount").textContent = volumeStyle.slices.length;
159        document.getElementById("activeIdx").textContent =
160            volumeStyle.slices.length ? activeIndex : "-";
161    }
162
163    // 将选中切片的属性同步到滑块显示
164    function syncControlsToActive() {
165        if (!volumeStyle.slices.length) return;
166        const s = volumeStyle.slices.getItemAt(activeIndex);
167        document.getElementById("rngOri").value = s.orientation;
168        document.getElementById("rngTilt").value = s.tilt;
169        document.getElementById("rngPos").value = s.point[0];
170        document.getElementById("valOri").textContent = s.orientation + "°";
171        document.getElementById("valTilt").textContent = s.tilt + "°";
172        document.getElementById("valPos").textContent = s.point[0];
173        updateInfo();
174    }
175
176    document.getElementById("btnAddH").addEventListener("click", function () {
177        if (!volSize) return;
178        addSlice({ orientation: 0, tilt: 0, point: [0, 0, Math.floor(volSize[2] / 2)] });
179        document.getElementById("statusText").textContent = "已添加水平切片";
180    });
181
182    document.getElementById("btnAddV").addEventListener("click", function () {
183        if (!volSize) return;
184        addSlice({ orientation: 270, tilt: 90, point: [Math.floor(volSize[0] / 2), 0, 0] });
185        document.getElementById("statusText").textContent = "已添加垂直切片";
186    });
187
188    document.getElementById("btnRemove").addEventListener("click", function () {
189        const n = volumeStyle.slices.length;
190        if (n === 0) return;
191        volumeStyle.slices.removeAt(n - 1);          // 移除最末切片
192        if (activeIndex >= volumeStyle.slices.length) {
193            activeIndex = Math.max(0, volumeStyle.slices.length - 1);
194        }
195        syncControlsToActive();
196        document.getElementById("statusText").textContent = "已移除最末切片";
197    });
198
199    document.getElementById("btnPrev").addEventListener("click", function () {
200        if (volumeStyle.slices.length === 0) return;
201        activeIndex = (activeIndex - 1 + volumeStyle.slices.length) % volumeStyle.slices.length;
202        syncControlsToActive();
203    });
204
205    document.getElementById("btnNext").addEventListener("click", function () {
206        if (volumeStyle.slices.length === 0) return;
207        activeIndex = (activeIndex + 1) % volumeStyle.slices.length;
208        syncControlsToActive();
209    });
210
211    document.getElementById("btnEnable").addEventListener("click", function () {
212        voxelLayer.enableSlices = !voxelLayer.enableSlices;
213        this.classList.toggle("on", voxelLayer.enableSlices);
214        this.textContent = voxelLayer.enableSlices ? "切片已启用" : "切片已禁用";
215    });
216
217    // 实时修改选中切片的属性:方向
218    document.getElementById("rngOri").addEventListener("input", function () {
219        if (!volumeStyle.slices.length) return;
220        const s = volumeStyle.slices.getItemAt(activeIndex);
221        s.orientation = Number(this.value);
222        document.getElementById("valOri").textContent = this.value + "°";
223    });
224
225    document.getElementById("rngTilt").addEventListener("input", function () {
226        if (!volumeStyle.slices.length) return;
227        const s = volumeStyle.slices.getItemAt(activeIndex);
228        s.tilt = Number(this.value);
229        document.getElementById("valTilt").textContent = this.value + "°";
230    });
231
232    // 移动选中切片在 x 轴上的位置(体素空间)
233    document.getElementById("rngPos").addEventListener("input", function () {
234        if (!volumeStyle.slices.length) return;
235        const s = volumeStyle.slices.getItemAt(activeIndex);
236        const p = s.point.slice();
237        p[0] = Number(this.value) * Math.floor(volSize[0] / 100);
238        s.point = p;
239        document.getElementById("valPos").textContent = p[0];
240    });
241</script>
242</body>
243</html>
244

五、在线示例

🔗 在线体验https://southjor.github.io/arcgis-examples/lessons/lesson32.html

操作说明

  1. 场景加载后默认添加一条水平切片与一条垂直切片。
  2. 点击 水平切片 / 垂直切片 可追加切片,移除最末 删除最后一条。
  3. 通过 上一个 / 下一个 选择切片,拖动滑块实时改变其 orientation / tilt / point.x,观察截面变化。
  4. 切片已启用 切换 voxelLayer.enableSlices,可整体开关切片渲染。

六、关键 API 说明

API说明
new VoxelSlice(properties)构造单个切片,point 为体素空间坐标 [x,y,z]
voxelLayer.getVolume(null)返回 VoxelVolume(只读元信息,含 sizeInVoxels)
voxelLayer.getVolumeStyle(null)返回 VoxelVolumeStyle,其 slices 为 Collection<VoxelSlice>
volumeStyle.slices.add(slice)向集合添加切片并立即渲染
volumeStyle.slices.removeAt(i) / getItemAt(i)集合删除 / 读取,修改属性实时生效
voxelLayer.enableSlices是否启用切片可视化(默认 true)

参考链接: VoxelSlice | VoxelVolumeStyle | 官方示例


七、系列导航

⬅️ 上一篇ArcGIS JS 基础教程(31):VoxelVariable 与 VoxelVolume

➡️ 下一篇ArcGIS JS 基础教程(33):VoxelDynamicSection 动态剖面


💡 小贴士VoxelSlicepoint体素索引而非地理坐标——先用 getVolume(null).sizeInVoxels 拿到体素尺寸,再以 [x,y,z] 取其中点,就能稳定切在体积中心。另外,切片在 renderMode: "volume" 下也有效,不局限于 surfaces 模式。


ArcGIS JS 基础教程(30):体元系列 - VoxelSlice 体元切片》 是转载文章,点击查看原文


相关推荐


0823Keepalived + LVS(DR) + MariaDB主主 小白完整实战教程
Void 出逃2026/8/28

文章目录 Keepalived + LVS(DR) + MariaDB主主 小白完整实战教程一、业务背景与架构选型数据库业务四大刚性需求传统架构痛点本套方案组件分工项目预期收益 二、MariaDB主从/主主复制原理核心日志与线程完整同步流程 三、实验环境规划四、基础环境初始化(所有机器)1. 修改主机名、网卡IP2. router路由器开启IP转发 五、部署MariaDB,配置主主双向复制db1配置db2配置第一步:db2作为从库,同步db1(db1→db2)第二步:db1作


【系列:TDengine 工业物联网实战:从零搭起可运行系统 · 第 7 篇】
jufeng13072026/8/20

第 4 篇我们搭好了写入管线,背压、攒批、并发 worker 都就位了。可管线再高效,也得有数据往里喂。今天回到源头:数据从哪来?两条路——模拟器直接造,采集器从三源收,最终都汇入同一条 pipeline。这篇就讲四件事:物理模型、确定性 seed、归一化漏斗、坏数据去向。读完你能回答:模拟器的物理模型为什么这么设计?三源各适合什么场景?坏数据去哪了? 两条路径,一个入口 先看全局。整个系统的数据入口分成两条路,但最终汇聚到同一个 WritePipeline——就是第 4 篇讲背压、攒批、并发


uni-app 生命周期深度解析(iOS / Android / 鸿蒙 / Vue3 四端对照)
90后晨仔2026/8/7

📌 本文定位: 面向同时具备 iOS、Android、鸿蒙原生开发经验,正在转向 uni-app 的资深工程师。所有技术点均基于 uni-app 官方文档 及 Vue3 官方文档整理。 一、先搞懂一个 JS 语法问题:为什么说"与 data/methods 平级的叫生命周期"? 很多原生工程师初学 uni-app 时会对这句话感到困惑。我们从 JavaScript 语言层面彻底讲清楚。 1.1 Options API 的本质是一个"配置对象" 在 Vue2/Vue3 的 Options A


GPT 5.6 的真正变革:从"最强模型"到"最合适模型",AI 工程范式正在重置
浮生望2026/7/29

摘要: GPT 5.6以Sol、Terra、Luna三层架构重新定义AI使用方式。跑分不再决定竞争力,真正的差距在于把什么任务分给什么模型,用更少Token和更少返工完成更高质量交付。AI正从顾问变成协作者。 每次新模型发布,人们都在问同一个问题,但那个问题已经过时了 GPT 5.6 发布后,讨论一如既往地集中在几个老问题上:跑分涨了多少?代码能力有没有超过 Claude?谁又成了"最强模型"? 这些问题很重要,但如果你只盯着它们,就错过了 GPT 5.6 真正改变游戏规则的地方。 Open


影像创作领域的GitHub!深度体验了这个国产AI,我卸了3个AI视频工具!
程序员X小鹿2026/7/21

大家好,我是X小鹿,今天分享一个不一样的 AI 视频工具。 以前做一条 AI 短片,从剧本创作、脚本编写、到角色定妆、场景道具生成、再到分镜生成、视频生成,以及最后的剪辑成片,每一步都需要用到单独的工具。 生成一条短片,需要在多个工具间来回切换。 后来也陆续出现了一些 AI 视频工具,把上面的很多流程都集成在了一个平台上,用起来确实比之前方便了不少。 最近用了 AI 视频创作平台 updream ,发现它虽然也是将很多功能集成在了一起,但和之前接触的一些 AI 视频工具又不太一样。 体验下来,


图像的分辨率
元來2026/7/12

一、什么是分辨率 图像分辨率表示: 图像能够表示多少空间细节,或者图像在水平和垂直方向上包含多少个像素。 日常最常见的分辨率写法是: 宽度 × 高度 例如: 1920 × 1080 4096 × 4096 6144 × 6144 其中: 1920表示图像水平方向有1920个像素;1080表示图像垂直方向有1080个像素。 因此一张1920 × 1080的图像,像素总数为:1920×1080=2,073,600 即大约207万个像素,也可以称为约200万像素图像。 二、


OpenCV-Python实战(31)——实时面部情绪检测与识别系统
盼小辉丶2026/7/4

OpenCV-Python实战(31)——实时面部情绪检测与识别系统 0. 前言1. 规划应用程序1. 人脸检测1.1 基于 Haar 的级联分类器1.2 预训练的级联分类器1.3 使用预训练的级联分类器1.4 FaceDetector 类 2. 收集数据2.1 构建训练数据集2.2 运行应用程序2.3 实现数据收集器 GUI 3. 面部情绪识别3.1 处理数据集3.2 多层感知机3.3 构建 MLP用于面部表情识别 4. 整合所有内容小结系列链接 0. 前言 我


2026年6月远程控制软件横评:UU远程、ToDesk、向日葵全方位对比
凤年徐2026/6/26

2026远程控制软件横评:UU远程、ToDesk、向日葵全方位对比 远程控制早已不是“应急连一下电脑”那么简单了。开发者用它连服务器改代码,设计师用它调家里的渲染机,留学生用它操作国内的网盘和银行App,甚至游戏玩家也用它“云挂机”。市面上主流的三款工具——UU远程、ToDesk、向日葵——各有拥趸,但到底谁在哪个场景下更顺手?这篇横评不吹不黑,直接把你最关心的七个核心能力摆在一起,逐项对比。 一、终端能力:谁能让开发者真正扔掉SSH客户端? 对于开发者、运维和AI训练者来说,远程命令行是最


JavaScript 函数性能优化:配置驱动 + 按需计算实战指南
m0_733915432026/6/17

JavaScript 函数优化是前端性能调优的核心环节。当业务逻辑中存在多层条件分支与重复计算时,代码不仅执行效率低下,还难以维护和扩展。本文将基于真实业务场景,提供一套配置驱动的优化方案,通过规则表集中管理 + 按需计算策略,帮助你提升函数执行效率 80% 以上。 为什么传统条件分支会拖累性能? 理解问题根源是有效优化的前提。传统 if-else 嵌套方案存在三大性能瓶颈: 问题类型 具体表现 性能影响 重复计算


PySide6 + Qt Designer + PyCharm 完整开发流程
资深流水灯工程师2026/6/10

PyCharm 对 Python 桌面开发有更完善的支持,包括智能代码补全、断点调试、集成终端、版本控制等功能,结合 Qt Designer 的可视化 UI 设计,是工业级上位机开发的首选组合。以下是完全适配 PyCharm 的标准化开发流程。 一、环境准备与 PyCharm 配置 1. 创建项目并配置虚拟环境(必做) PyCharm 强烈推荐使用虚拟环境隔离项目依赖,避免版本冲突: 打开 PyCharm → 新建项目 (New Project)选择项目位置,命名为test_equipm

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读