ArcGIS JS 基础教程(7):Global与Local场景模式

ArcGIS JS 基础教程(7):Global与Local场景模式

零、写在前面

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

💡 在线示例 :完整可运行的 HTML 示例,无需任何环境配置,可直接在浏览器中打开体验

🗂️ 专栏导航 :收藏 + 关注,专栏文章第一时间送达

❤️ 一键三连:点赞(给教程充电)+ 评论(提问必回)+ 收藏(下次再看)


一、功能介绍

SceneView 支持两种场景模式:Global(全球球面模式)Local(局部平面模式) ,通过 viewingMode 属性控制。

  • Global 模式:在 WGS84 球面坐标系下渲染完整的地球球体,适合展示大范围、跨区域的三维数据。地球曲率真实可见,支持全球尺度漫游。
  • Local 模式:在投影坐标系下将场景渲染为局部平面区域,适合城市级或园区级的精细化展示。平面投影下量算更精准,无地球曲率干扰。

两种模式对高程数据的处理方式也不同:Global 模式下高程贴合球面曲率;Local 模式下高程在投影平面上拉伸,精度更高。根据项目尺度正确选择模式,是三维场景构建的关键第一步。

二、功能实现

SceneView.viewingMode 属性决定场景模式,可选值为 "global"(默认)或 "local"

Global 模式关键配置:

  • 坐标系:WGS84(EPSG:4326),经纬度直接驱动
  • map.ground 加载全球高程服务 world-elevation
  • 相机参数:camera.position 使用 [longitude, latitude, altitude]

Local 模式关键配置:

  • 坐标系:需使用投影坐标系(如 Web Mercator EPSG:3857,或地方投影带)
  • 通过设置 extent 约束场景显示范围,避免平面无限延伸
  • 局部模式下相机 position 的 x/y 为投影平面坐标,z 仍为高程

动态切换注意: Global 与 Local 模式不能在同一个 SceneView 实例中动态切换,需要销毁原有视图、创建新的 SceneView 实例。实际项目中通常在入口页提供模式选择,或维护两个独立页面。

核心代码如下:

  • 创建 Global 模式:viewingMode: "global"(可省略,默认即为 global)
  • 创建 Local 模式:viewingMode: "local",并建议同时设置 constraints.clipToExtent 限制渲染范围
  • Local 模式需确保底图和支持的图层在投影坐标系下可用(Web Mercator 兼容性最好)

三、功能应用

场景 推荐模式 理由
全球物流航线可视化 Global 需要展示跨大洲的球面航线,地球曲率不可忽略
智慧园区精细化管理 Local 园区范围小,平面坐标系下量算和建模更精准
城市数字孪生(区县尺度) Local 城市级场景在平面投影下建筑对齐更自然
气象/海洋全球数据展示 Global 全球尺度的气象云图、洋流需要在球面上连续展示
应急指挥(局部区域) Local 局部区域分析无需地球曲率,平面量算更符合业务习惯
航空航天态势感知 Global 卫星轨道、飞行轨迹跨越全球,球面模式是唯一选择

选择原则:

  • 数据范围 大于 100km (如全省、全国、全球)→ 选 Global
  • 数据范围 小于 50km (如园区、城区)→ 选 Local
  • 介于之间需结合业务:涉及球面几何(大圆航线)选 Global,涉及精密量算选 Local

四、核心代码

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>第6课:Global与Local场景模式</title>
    <link rel="stylesheet" href="https://js.arcgis.com/5.0/esri/themes/light/main.css">
    <script type="module" src="https://js.arcgis.com/5.0/"></script>
    <style>
        body { margin: 0; padding: 0; font-family: "Microsoft YaHei", sans-serif; }
        #mapContainer { width: 100vw; height: 100vh; }
        .page-title {
            position: absolute;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(255,255,255,0.95);
            padding: 10px 24px;
            border-radius: 6px;
            font-size: 18px;
            font-weight: bold;
            z-index: 100;
            box-shadow: 0 2px 8px rgba(0,0,0,0.15);
        }
        .control-panel {
            position: absolute;
            top: 80px;
            left: 20px;
            background: rgba(255,255,255,0.95);
            padding: 16px;
            border-radius: 8px;
            box-shadow: 0 2px 12px rgba(0,0,0,0.15);
            z-index: 100;
            min-width: 220px;
        }
        .mode-badge {
            display: inline-block;
            padding: 4px 12px;
            border-radius: 12px;
            font-size: 13px;
            font-weight: bold;
            margin-bottom: 12px;
        }
        .badge-global { background: #1890ff; color: white; }
        .badge-local  { background: #52c41a; color: white; }
        .btn-group { display: flex; flex-direction: column; gap: 8px; }
        .btn-group button {
            padding: 10px 16px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 13px;
            font-weight: 500;
        }
        .btn-global { background: #1890ff; color: white; }
        .btn-local  { background: #52c41a; color: white; }
        .btn-compare { background: #722ed1; color: white; }
        .btn-group button:hover { opacity: 0.85; }
        .info-box {
            position: absolute;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(255,255,255,0.95);
            padding: 12px 20px;
            border-radius: 8px;
            font-size: 13px;
            line-height: 1.8;
            z-index: 100;
            box-shadow: 0 2px 12px rgba(0,0,0,0.15);
            max-width: 600px;
        }
        .info-box code { background: #f0f0f0; padding: 1px 4px; border-radius: 3px; }
    </style>
</head>
<body>
<h1 class="page-title">第7课:Global 与 Local 场景模式</h1>

<div class="control-panel">
    <div id="modeBadge" class="mode-badge badge-global">当前模式:GLOBAL</div>
    <div class="btn-group">
        <button class="btn-global" id="btnGlobal">切换为 Global 模式</button>
        <button class="btn-local"  id="btnLocal">切换为 Local 模式</button>
        <button class="btn-compare" id="btnCompare">对比:北京 vs 小范围园区</button>
    </div>
</div>

<div class="info-box">
    🌐 <b>Global 模式</b>:球面坐标系(WGS84),适合<code>全省/全国/全球</code>尺度<br>
    📐 <b>Local 模式</b>:平面投影坐标系,适合<code>园区/城区</code>尺度,量算更精准
</div>

<div id="mapContainer"></div>

<script type="module">
    const Map = await $arcgis.import("@arcgis/core/Map.js");
    const SceneView = await $arcgis.import("@arcgis/core/views/SceneView.js");
    const getTianditu = await $arcgis.import("https://openlayers.vip/examples/resources/tianditu.js");

    // ========== 天地图图层 ==========
    const vecLayers = getTianditu.default({ type: "vec_w" });

    // ========== 创建 Map ==========
    const map = new Map({
        basemap: { baseLayers: [vecLayers.base, vecLayers.anno] },
        ground: {
            surface: {
                elevationLayers: [{
                    url: "https://www.geosceneonline.cn/image/rest/services/OpenData/ChinaTerrain3D/ImageServer/"
                }]
            }
        }
    });

    // ========== 初始为 Global 模式 ==========
    let currentMode = "global";

    window.view = new SceneView({
        container: "mapContainer",
        map: map,
        viewingMode: "global",       // 关键属性:global | local
        center: [116.39, 39.9],
        zoom: 10,
        tilt: 60,
        constraints: {
            altitude: { max: 20000000 }  // 限制最大高度,避免缩太小
        }
    });

    // ========== 更新 UI 徽章 ==========
    function updateBadge(mode) {
        const badge = document.getElementById("modeBadge");
        if (mode === "global") {
            badge.className = "mode-badge badge-global";
            badge.textContent = "当前模式:GLOBAL(球面)";
        } else {
            badge.className = "mode-badge badge-local";
            badge.textContent = "当前模式:LOCAL(平面)";
        }
    }

    // ========== 切换为 Global 模式 ==========
    window.switchToGlobal = () => {
        if (currentMode === "global") return;
        currentMode = "global";
        // 销毁旧视图,创建新视图(两模式无法动态切换同一实例)
        view.destroy();
        window.view = new SceneView({
            container: "mapContainer",
            map: map,
            viewingMode: "global",
            center: [116.39, 39.9],
            zoom: 10,
            tilt: 60
        });
        updateBadge("global");
    };

    // ========== 切换为 Local 模式 ==========
    window.switchToLocal = () => {
        if (currentMode === "local") return;
        currentMode = "local";
        view.destroy();
        window.view = new SceneView({
            container: "mapContainer",
            map: map,
            viewingMode: "local",        // 关键:切换为局部平面模式
            center: [116.39, 39.9],
            zoom: 14,
            tilt: 55,
            constraints: {
                clipToExtent: true,       // 限制仅渲染 extent 范围内
                altitude: { max: 50000 }
            }
        });
        updateBadge("local");
    };

    // ========== 对比演示:分别定位到全球视角和局部园区 ==========
    window.compareModes = () => {
        if (currentMode === "global") {
            // 当前是 Global → 飞到北京上空(展示球面效果)
            view.goTo({
                position: [116.39, 39.9, 2500000],
                tilt: 0,
                heading: 0
            });
        } else {
            // 当前是 Local → 定位到小规模园区范围
            view.goTo({
                position: [116.39, 39.9, 2000],
                tilt: 55,
                heading: 0
            });
        }
    };

    document.getElementById("btnGlobal").onclick = switchToGlobal;
    document.getElementById("btnLocal").onclick  = switchToLocal;
    document.getElementById("btnCompare").onclick = compareModes;

    updateBadge("global");
    view.when(() => console.log("Global/Local 场景模式示例加载完成!"));
</script>
</body>
</html>

五、在线示例

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

操作说明

  1. 点击「切换为 Local 模式」按钮,观察地球从球面变为平面的视觉效果
  2. 在 Global 模式下大幅缩小,可看到完整地球球体
  3. 在 Local 模式下放大,建筑与地面贴合更自然,无球面扭曲

六、关键API说明

API 说明
SceneView.viewingMode 场景模式:"global"(球面,默认)/ "local"(平面)
SceneView.constraints.clipToExtent Local 模式下是否仅渲染 extent 范围内(避免平面无限延伸)
SceneView.constraints.altitude.max 限制最大视角高度,Global 模式下防止缩太小看不到地球
camera.position Global 模式为 [lng, lat, alt];Local 模式 x/y 为投影坐标
map.ground 两种模式下高程数据的处理和显示方式不同,Local 精度更高
view.destroy() 切换模式时需先销毁旧视图实例,再创建新模式视图

两种模式核心差异对比

对比维度 Global 模式 Local 模式
坐标系 WGS84(球面) 投影坐标系(平面)
地球曲率 有(真实球面) 无(平面投影)
适用尺度 全省/全国/全球 园区/城区
量算精度 球面距离(大范围准确) 平面距离(小范围更精准)
高程处理 贴合球面 平面拉伸,精度更高
性能建议 注意全球数据加载性能 可加载更高精度局部数据

七、系列导航

⬅️ 上一篇ArcGIS JS 基础教程(6):地图弹窗信息窗口


💡 小贴士:如果你在 Local 模式下使用自定义切片底图,请确保切片服务的坐标系为投影坐标系(如 EPSG:3857),否则会出现图像偏移或无法显示的问题。

相关推荐
中科GIS地理信息培训2 小时前
【ArcGIS Pro 3.7新增功能2】新型高光谱图像工具:连续谱去除、PCA与 MNF 降低数据复杂性、使用波长直接计算、支持STAC等
人工智能·arcgis·目标跟踪
杨超越luckly4 小时前
HTML应用指南:利用GET请求获取智己汽车门店位置信息
python·arcgis·html·汽车·数据可视化
弹简特1 天前
【Vue3速成】01-npm+vue初体验+vite构建vue工程化
vue.js·arcgis·npm
梦想的初衷~1 天前
AI辅助下基于ArcGIS Pro的SWAT模型全流程高效建模实践与深度进阶应用
人工智能·arcgis·气候·水文·地理信息·环境科学
安迁岚3 天前
基于珠三角城市热岛热点核心中心点的等级化点格局分析
人工智能·arcgis·信息可视化·数据挖掘·数据分析·地统计
赵钰老师3 天前
地理信息系统(ArcGIS)在水文水资源、水环境中的应用
arcgis·数据分析
wand codemonkey6 天前
【第四步+前后分离调】用VS Code工具写Vue3项目需要写哪些写哪些文件才能实现联调
arcgis
三*一7 天前
Mapbox GL JS 自研面要素整形工具开发实录
开发语言·javascript·arcgis·ecmascript
qq_381338507 天前
前端状态管理新范式:Zustand、Jotai 与 Preact Signals 深度对比
前端·arcgis