代码改变世界

【2025最新】ArcGIS for JS二维底图与三维地图的切换 - 指南

2025-10-25 18:40  tlnshuju  阅读(4)  评论(0)    收藏  举报

【2025最新】ArcGIS for JS二维地图与三维地图的切换

本文适用 ArcGIS JS API 4.28-4.33 版本,是 【2025最新】ArcGIS for JS 街道、卫星、地形地貌底图切换》扩展教程,在实现支持街道图、卫星图、地形地貌图基础上,进一步实现了 3D 地图的切换功能。

工具 /插件/系统 名版本说明
ArcGIS JS API4.28~4.33地图核心能力(底图加载、视图渲染)
Tailwind CSS最新快速构建高颜值 UI,无需手写复杂 CSS
Font Awesome4.7.0提供地图图标,增强视觉效果
天地图服务-提供街道、卫星、地形等底图数据源

效果图

效果图
在这里插入图片描述

一、核心功能实现

针对基础引入过程,本文将不过多赘述,有需要的,可自行查阅 【2025最新】ArcGIS for JS 街道、卫星、地形地貌底图切换》

1.1 模块加载与初始化

首先导入 ArcGIS 核心模块,并初始化应用配置对象:

// 导入天地图加载工具(需自行实现tiandituLoader.js)
import { loadTiandituBasemap } from './js/tiandituLoader.js';
// 加载ArcGIS核心模块
const  [
   Map, MapView, SceneView, SceneLayer, WebMap, WebScene
] = await \$arcgis.import( [
   '@arcgis/core/Map.js',        // 2D地图核心类
   '@arcgis/core/views/MapView.js', // 2D地图视图
   "@arcgis/core/views/SceneView.js", // 3D地图视图
   "@arcgis/core/layers/SceneLayer.js", // 3D场景图层
   "@arcgis/core/WebMap.js",     // Web地图类
   "@arcgis/core/WebScene.js"    // Web场景类
])
// 应用配置对象,管理地图视图状态
const appConfig = {
   mapView: null,     // 2D视图实例
   sceneView: null,   // 3D视图实例
   activeView: null,  // 当前活跃视图
   container: "viewDiv", // 地图容器ID
};
// 加载天地图配置信息
const { tileInfo, config, getUrlTemplate, tiandituBasemap, Basemap, WebTileLayer } = await loadTiandituBasemap();

1.2 底图定义

定义三种 2D 底图(街道图、卫星图、地形地貌图)和一种 3D 场景:

1.2.1 街道图底图

直接使用天地图加载工具返回的街道图底图:

const streetsBasemap = tiandituBasemap; // 天地图街道图
1.2.2 卫星图底图

由卫星影像层和标注层组成:

const satelliteBasemap = new Basemap({
   baseLayers:  [
       // 卫星影像层
       new WebTileLayer({
           urlTemplate: getUrlTemplate('img'), // 天地图卫星影像URL模板
           subDomains: config.subDomains,       // 子域名
           copyright: "天地图 © 国家地理信息公共服务平台",
           spatialReference: config.spatialReference, // 空间参考(默认Web Mercator)
           tileInfo: tileInfo                   // 瓦片信息
       }),
       // 卫星图标注层
       new WebTileLayer({
           urlTemplate: getUrlTemplate('cia'), // 天地图卫星标注URL模板
           subDomains: config.subDomains,
           copyright: "天地图 © 国家地理信息公共服务平台",
           spatialReference: config.spatialReference,
           tileInfo: tileInfo
       })
   ],
   title: "卫星图",
   id: "satellite"
});
1.2.3 地形地貌图底图

类似卫星图结构,由地形层和标注层组成:

const terrainBasemap = new Basemap({
   baseLayers:  [
       // 地形层
       new WebTileLayer({
           urlTemplate: getUrlTemplate('ter'), // 天地图地形URL模板
           subDomains: config.subDomains,
           copyright: "天地图 © 国家地理信息公共服务平台",
           spatialReference: config.spatialReference,
           tileInfo: tileInfo
       }),
       // 地形标注层
       new WebTileLayer({
           urlTemplate: getUrlTemplate('cta'), // 天地图地形标注URL模板
           subDomains: config.subDomains,
           copyright: "天地图 © 国家地理信息公共服务平台",
           spatialReference: config.spatialReference,
           tileInfo: tileInfo
       })
   ],
   title: "地形地貌图", // 注意:原代码此处标题错误,已修正
   id: "terrain"       // 注意:原代码此处ID错误,已修正
});
1.2.4 3D 场景

使用 ArcGIS Online 上的公共 3D 场景:

const scene = new WebScene({
   portalItem: {
       id: "c8cf26d7acab4e45afcd5e20080983c1", // ArcGIS Online场景ID
   },
});

1.3 视图创建

实现通用的视图创建函数,支持 2D 和 3D 视图:

function createView(type) {
   let view;
   if (type === "2d") {
       // 创建2D视图
       view = new MapView({
           container: appConfig.container,
           map: map,
           center:  [116.39, 39.9], // 默认北京坐标
           zoom: 12,               // 默认缩放级别
           // 环境配置,添加背景色
           environment: {
               background: {
                   type: "color",
                   color:  [240, 240, 240]
               }
           }
       });
       // 监听2D视图鼠标移动事件,显示坐标
       view.on("pointer-move", (event) => {
           if (view.interacting) return; // 交互中不更新坐标
           const point = view.toMap(event); // 转换屏幕坐标为地图坐标
           document.getElementById("coordinates").textContent = 
               \`经度: \${point.longitude.toFixed(6)} , 纬度: \${point.latitude.toFixed(6)}\`;
       });
   } else {
       // 创建3D视图
       view = new SceneView({
           zoom: 12,
           center:  [-122.43759993450347, 37.772798684981126], // 默认旧金山坐标
           // 初始不设置容器,切换时再绑定
       });
       // 监听3D视图鼠标移动事件,显示坐标
       view.on("pointer-move", (event) => {
           if (view.interacting) return;
           const point = view.toMap(event);
           document.getElementById("coordinates").textContent = 
               \`经度: \${point.longitude.toFixed(6)} , 纬度: \${point.latitude.toFixed(6)}\`;
       });
   }
   return view;
}
// 初始化2D和3D视图
const map = new Map({
   basemap: streetsBasemap // 默认2D底图
});
appConfig.mapView = createView("2d");
appConfig.sceneView = createView("3d");
appConfig.activeView = appConfig.mapView; // 默认激活2D视图

1.4 底图切换逻辑

实现底图切换的核心功能,包括按钮状态更新、视图切换和底图更新:

1.4.1 底图更新函数
const updateBasemap = (newBasemap, buttonId) => {
   // 1. 移除所有按钮的激活状态
   document.querySelectorAll(' [id^="basemap-"]').forEach(btn => {
       btn.classList.remove('basemap-btn-active');
   });
   // 2. 设置当前按钮为激活状态
   document.getElementById(buttonId).classList.add('basemap-btn-active');
   // 3. 更新信息面板中的当前底图名称
   const basemapTitle = buttonId === 'basemap-3d' ? '3D图' : newBasemap.title;
   document.getElementById("current-basemap").textContent = basemapTitle;
   // 4. 执行视图切换
   switchView(newBasemap, buttonId);
};
1.4.2 视图切换函数

处理 2D 和 3D 视图的切换,保持视图位置和缩放级别一致性:

function switchView(newBasemap, buttonId) {
   // 1. 保存当前视图的视角信息
   const activeViewpoint = appConfig.activeView.viewpoint.clone();
   
   // 2. 计算缩放级别转换因子(解决纬度对距离的影响)
   const latitude = appConfig.activeView.center.latitude;
   const scaleConversionFactor = Math.cos((latitude * Math.PI) / 180.0);
   activeViewpoint.scale *= scaleConversionFactor; // 调整缩放级别
   // 3. 解绑当前视图的容器
   appConfig.activeView.container = null;
   // 4. 切换到目标视图
   if (buttonId !== 'basemap-3d') {
       // 切换到2D视图
       appConfig.mapView.viewpoint = activeViewpoint; // 应用保存的视角
       appConfig.mapView.container = appConfig.container; // 绑定容器
       appConfig.mapView.map.basemap = newBasemap; // 更新2D底图
       appConfig.activeView = appConfig.mapView; // 更新活跃视图
   } else {
       // 切换到3D视图
       appConfig.sceneView.viewpoint = activeViewpoint; // 应用保存的视角
       appConfig.sceneView.container = appConfig.container; // 绑定容器
       appConfig.activeView = appConfig.sceneView; // 更新活跃视图
   }
}

1.5 绑定按钮事件

为每个底图切换按钮绑定点击事件:

// 街道图切换
document.getElementById("basemap-streets").addEventListener("click", () => {
   updateBasemap(streetsBasemap, "basemap-streets");
});
// 卫星图切换
document.getElementById("basemap-satellite").addEventListener("click", () => {
   updateBasemap(satelliteBasemap, "basemap-satellite");
});
// 地形地貌图切换
document.getElementById("basemap-terrain").addEventListener("click", () => {
   updateBasemap(terrainBasemap, "basemap-terrain");
});
// 3D图切换
document.getElementById("basemap-3d").addEventListener("click", () => {
   updateBasemap(null, "basemap-3d");
});

二、全部代码

index.html




    
    
    ArcGIS 底图切换示例
    
    
    
    
    
    <script src="https://js.arcgis.com/4.33/"></script>
    
    <script src="https://cdn.tailwindcss.com"></script>
    
    
    
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        primary: '#0079c1',
                        secondary: '#5cc0ff',
                        dark: '#1e293b',
                        light: '#f8fafc'
                    },
                    fontFamily: {
                        sans: ['Inter', 'system-ui', 'sans-serif'],
                    },
                }
            }
        }
    </script>
    


    
    

底图切换

地图信息

当前底图: 街道图

坐标: 经度: -- , 纬度: --

<script type="module"> import { loadTiandituBasemap } from './js/tiandituLoader.js'; // 加载 ArcGIS 模块 const [ Map, MapView, SceneView, SceneLayer, WebMap, WebScene ] = await $arcgis.import([ '@arcgis/core/Map.js', '@arcgis/core/views/MapView.js', "@arcgis/core/views/SceneView.js", "@arcgis/core/layers/SceneLayer.js", "@arcgis/core/WebMap.js", "@arcgis/core/WebScene.js", ]) const appConfig = { mapView: null, sceneView: null, activeView: null, container: "viewDiv", // use same container for views }; const { tileInfo, config, getUrlTemplate, tiandituBasemap, Basemap, WebTileLayer } = await loadTiandituBasemap(); // 1. 定义三个不同的底图 // 街道图底图 const streetsBasemap = tiandituBasemap // 卫星图底图 const satelliteBasemap = new Basemap({ baseLayers: [ new WebTileLayer({ urlTemplate: getUrlTemplate('img'), subDomains: config.subDomains, copyright: "天地图 © 国家地理信息公共服务平台", spatialReference: config.spatialReference, tileInfo: tileInfo }), new WebTileLayer({ urlTemplate: getUrlTemplate('cia'), subDomains: config.subDomains, copyright: "天地图 © 国家地理信息公共服务平台", spatialReference: config.spatialReference, tileInfo: tileInfo }) ], title: "卫星图", id: "satellite" }); // 地形地貌图底图 const terrainBasemap = new Basemap({ baseLayers: [ new WebTileLayer({ urlTemplate: getUrlTemplate('ter'), subDomains: config.subDomains, copyright: "天地图 © 国家地理信息公共服务平台", spatialReference: config.spatialReference, tileInfo: tileInfo }), new WebTileLayer({ urlTemplate: getUrlTemplate('cta'), subDomains: config.subDomains, copyright: "天地图 © 国家地理信息公共服务平台", spatialReference: config.spatialReference, tileInfo: tileInfo }) ], title: "卫星图", id: "satellite" }); // 2. 创建地图实例,默认使用街道图 // 2d图层 const map = new Map({ basemap: streetsBasemap }); appConfig.mapView = createView("2d"); appConfig.mapView.map = map; appConfig.activeView = appConfig.mapView; // 3D 图层 const scene = new WebScene({ portalItem: { // autocasts as new PortalItem() id: "c8cf26d7acab4e45afcd5e20080983c1", }, }); appConfig.sceneView = createView("3d"); appConfig.sceneView.map = scene; // 3. 创建地图视图 function createView(type) { let view; if (type === "2d") { view = new MapView({ container: appConfig.container, map: map, center: [116.39, 39.9], // 北京坐标 zoom: 12, // 添加淡入淡出过渡效果 environment: { background: { type: "color", color: [240, 240, 240] } } }); } else { view = new SceneView({ zoom: 12, center: [-122.43759993450347, 37.772798684981126], // container: appConfig.container, }); } return view; } // 4. 底图切换逻辑 const updateBasemap = (newBasemap, buttonId) => { // 移除所有按钮的活跃状态 document.querySelectorAll('[id^="basemap-"]').forEach(btn => { btn.classList.remove('basemap-btn-active'); }); // 设置当前按钮为活跃状态 document.getElementById(buttonId).classList.add('basemap-btn-active'); // 更新当前底图显示文本 document.getElementById("current-basemap").textContent = newBasemap && newBasemap.title || "图"; switchView(newBasemap, buttonId) }; // 切换 function switchView(newBasemap, buttonId) { // const is3D = appConfig.activeView.type === "3d"; const activeViewpoint = appConfig.activeView.viewpoint.clone(); // Compute scale conversion factor with cosine of latitude to account for distance distortion as latitude moves away from the equator const latitude = appConfig.activeView.center.latitude; const scaleConversionFactor = Math.cos((latitude * Math.PI) / 180.0); appConfig.activeView.container = null; if (buttonId != 'basemap-3d') { const is3D = appConfig.activeView.type === "3d"; activeViewpoint.scale = scaleConversionFactor; appConfig.mapView.viewpoint = activeViewpoint; appConfig.mapView.container = appConfig.container; appConfig.mapView.map.basemap = newBasemap; appConfig.activeView = appConfig.mapView; } else { activeViewpoint.scale = scaleConversionFactor; appConfig.sceneView.viewpoint = activeViewpoint; appConfig.sceneView.container = appConfig.container; appConfig.activeView = appConfig.sceneView; } } // 5. 绑定按钮事件 document.getElementById("basemap-streets").addEventListener("click", () => { updateBasemap(streetsBasemap, "basemap-streets"); }); document.getElementById("basemap-satellite").addEventListener("click", () => { updateBasemap(satelliteBasemap, "basemap-satellite"); }); document.getElementById("basemap-terrain").addEventListener("click", () => { updateBasemap(terrainBasemap, "basemap-terrain"); }); document.getElementById("basemap-3d").addEventListener("click", () => { updateBasemap(null, "basemap-3d"); }); </script>

tiandituLoader.js

文件放置/js/tiandituLoader.js ,代码详细解释在渲染天地图全攻略(点击直达)有兴趣的可阅读

/**
 * 天地图加载公共模块
 * 功能:封装天地图底图加载逻辑,返回配置好的Basemap实例
 * 依赖:ArcGIS API 4.x
 */
export async function loadTiandituBasemap() {
    try {
        // 1. 按需导入ArcGIS核心模块
        const [
            WebTileLayer,
            Basemap,
            TileInfo
        ] = await $arcgis.import([
            "@arcgis/core/layers/WebTileLayer",
            "@arcgis/core/Basemap",
            "@arcgis/core/layers/support/TileInfo",
        ]);
        // 2. 配置参数(可根据需求调整)
        const config = {
            tk:你的天地图密钥, // 天地图密钥
            spatialReference: { wkid: 4326 },       // 目标坐标系(WGS84)
            subDomains: ["0", "1", "2", "3", "4", "5", "6", "7"], // 多子域名
            tileMatrixSet: "c",                     // 天地图瓦片矩阵集
            layerType: {
                vec: "vec", // 矢量底图
                cva: "cva"  // 矢量注记
            }
        };
        // 3. 定义瓦片信息(匹配WGS84坐标系的瓦片规则)
        const tileInfo = new TileInfo({
            dpi: 90.71428571427429,
            rows: 256,
            cols: 256,
            compressionQuality: 0,
            origin: { x: -180, y: 90 },
            spatialReference: config.spatialReference,
            lods: [
                { level: 2, levelValue: 2, resolution: 0.3515625, scale: 147748796.52937502 },
                { level: 3, levelValue: 3, resolution: 0.17578125, scale: 73874398.264687508 },
                { level: 4, levelValue: 4, resolution: 0.087890625, scale: 36937199.132343754 },
                { level: 5, levelValue: 5, resolution: 0.0439453125, scale: 18468599.566171877 },
                { level: 6, levelValue: 6, resolution: 0.02197265625, scale: 9234299.7830859385 },
                { level: 7, levelValue: 7, resolution: 0.010986328125, scale: 4617149.8915429693 },
                { level: 8, levelValue: 8, resolution: 0.0054931640625, scale: 2308574.9457714846 },
                { level: 9, levelValue: 9, resolution: 0.00274658203125, scale: 1154287.4728857423 },
                { level: 10, levelValue: 10, resolution: 0.001373291015625, scale: 577143.73644287116 },
                { level: 11, levelValue: 11, resolution: 0.0006866455078125, scale: 288571.86822143558 },
                { level: 12, levelValue: 12, resolution: 0.00034332275390625, scale: 144285.93411071779 },
                { level: 13, levelValue: 13, resolution: 0.000171661376953125, scale: 72142.967055358895 },
                { level: 14, levelValue: 14, resolution: 8.58306884765625e-005, scale: 36071.483527679447 },
                { level: 15, levelValue: 15, resolution: 4.291534423828125e-005, scale: 18035.741763839724 },
                { level: 16, levelValue: 16, resolution: 2.1457672119140625e-005, scale: 9017.8708819198619 },
                { level: 17, levelValue: 17, resolution: 1.0728836059570313e-005, scale: 4508.9354409599309 },
                { level: 18, levelValue: 18, resolution: 5.3644180297851563e-006, scale: 2254.4677204799655 },
                { level: 19, levelValue: 19, resolution: 2.68220901489257815e-006, scale: 1127.23386023998275 },
                { level: 20, levelValue: 20, resolution: 1.341104507446289075e-006, scale: 563.616930119991375 }
            ]
        });
        // 4. 构建天地图URL模板(支持多子域名)
        const getUrlTemplate = (layer) => {
            return `http://t0.tianditu.gov.cn/${layer}_${config.tileMatrixSet}/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=${layer}&STYLE=default&TILEMATRIXSET=${config.tileMatrixSet}&TILEMATRIX={level}&TILEROW={row}&TILECOL={col}&FORMAT=tiles&tk=${config.tk}`;
        };
        // 5. 创建矢量底图图层
        const vecLayer = new WebTileLayer({
            urlTemplate: getUrlTemplate(config.layerType.vec),
            subDomains: config.subDomains,
            copyright: "天地图 © 国家地理信息公共服务平台",
            spatialReference: config.spatialReference,
            tileInfo: tileInfo
        });
        // 6. 创建矢量注记图层
        const cvaLayer = new WebTileLayer({
            urlTemplate: getUrlTemplate(config.layerType.cva),
            subDomains: config.subDomains,
            copyright: "天地图 © 国家地理信息公共服务平台",
            spatialReference: config.spatialReference,
            tileInfo: tileInfo
        });
        // 7. 创建自定义底图并返回
        const tiandituBasemap = new Basemap({
            baseLayers: [vecLayer],
            referenceLayers: [cvaLayer],
            title: "天地图矢量图(WGS84)",
            id: "tianditu-vector-wgs84"
        });
        return {
            tileInfo,
            config,
            getUrlTemplate,
            tiandituBasemap,
            WebTileLayer,
            Basemap
        };
    } catch (error) {
        console.error("天地图加载失败:", error);
        throw new Error("天地图公共模块加载异常,请检查依赖和配置");
    }
}