【2025最新】ArcGIS for JS二维底图与三维地图的切换
【2025最新】ArcGIS for JS二维地图与三维地图的切换
本文适用 ArcGIS JS API 4.28-4.33 版本,是 【2025最新】ArcGIS for JS 街道、卫星、地形地貌底图切换》扩展教程,在实现支持街道图、卫星图、地形地貌图基础上,进一步实现了 3D 地图的切换功能。
文章目录
- 【2025最新】ArcGIS for JS二维地图与三维地图的切换
- 效果图
- 一、核心功能实现
- 1.1 模块加载与初始化
- 1.2 底图定义
- 1.2.1 街道图底图
- 1.2.2 卫星图底图
- 1.2.3 地形地貌图底图
- 1.2.4 3D 场景
- 1.3 视图创建
- 1.4 底图切换逻辑
- 1.4.1 底图更新函数
- 1.4.2 视图切换函数
- 1.5 绑定按钮事件
- 二、全部代码
- index.html
- tiandituLoader.js
工具 /插件/系统 名 | 版本 | 说明 |
---|---|---|
ArcGIS JS API | 4.28~4.33 | 地图核心能力(底图加载、视图渲染) |
Tailwind CSS | 最新 | 快速构建高颜值 UI,无需手写复杂 CSS |
Font Awesome | 4.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
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>ArcGIS 底图切换示例</title><!-- 引入 ArcGIS JS API --><!-- <link rel="stylesheet" href="https://js.arcgis.com/4.28/esri/themes/light/main.css"><script src="https://js.arcgis.com/4.28/"></script> --><!-- 引入ArcGIS API --><link rel="stylesheet" href="https://js.arcgis.com/4.33/esri/themes/light/main.css" /><!-- 引入 ArcGIS JS API --><script src="https://js.arcgis.com/4.33/"></script><!-- 引入 Tailwind CSS --><script src="https://cdn.tailwindcss.com"></script><!-- 引入 Font Awesome --><link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"><!-- Tailwind 配置 --><script>tailwind.config = {theme: {extend: {colors: {primary: '#0079c1',secondary: '#5cc0ff',dark: '#1e293b',light: '#f8fafc'},fontFamily: {sans: ['Inter', 'system-ui', 'sans-serif'],},}}}</script><style type="text/tailwindcss">@layer utilities {.content-auto {content-visibility: auto;}.basemap-btn-active {@apply bg-primary text-white border-primary;}.map-control {@apply fixed z-10 bg-white/90 backdrop-blur-sm rounded-lg shadow-lg border border-gray-200 transition-all duration-300;}.basemap-thumbnail {@apply w-full h-16 object-cover rounded-md mb-1 border border-gray-200;}}</style>
</head><body class="bg-gray-100 font-sans overflow-hidden h-screen"><!-- 主地图容器 --><div id="viewDiv" class="absolute inset-0"></div><!-- 底图切换控件 --><div class="map-control bottom-6 right-6 p-4 w-64"><div class="flex justify-between items-center mb-3"><h3 class="font-semibold text-gray-800">底图切换</h3><i class="fa fa-map text-primary"></i></div><div class="space-y-3"><!-- 底图选项 1 - 街道图 --><button id="basemap-streets"class="basemap-btn-active w-full p-2 rounded-md border transition-all duration-200 flex flex-col items-center hover:shadow-md"><img src="https://picsum.photos/id/1015/300/150" alt="街道图预览" class="basemap-thumbnail"><span class="text-sm font-medium">街道图</span></button><!-- 底图选项 2 - 卫星图 --><button id="basemap-satellite"class="w-full p-2 rounded-md border border-gray-200 transition-all duration-200 flex flex-col items-center hover:shadow-md"><img src="https://picsum.photos/id/1016/300/150" alt="卫星图预览" class="basemap-thumbnail"><span class="text-sm font-medium">卫星图</span></button><!-- 底图选项 3 - 地形地貌图 --><button id="basemap-terrain"class="w-full p-2 rounded-md border border-gray-200 transition-all duration-200 flex flex-col items-center hover:shadow-md"><img src="https://picsum.photos/id/1018/300/150" alt="地形地貌图预览" class="basemap-thumbnail"><span class="text-sm font-medium">地形地貌图</span></button><!-- 3d 图 --><button id="basemap-3d"class="w-full p-2 rounded-md border border-gray-200 transition-all duration-200 flex flex-col items-center hover:shadow-md"><img src="https://picsum.photos/id/1020/300/150" alt="3D 图" class="basemap-thumbnail"><span class="text-sm font-medium">3D 图</span></button></div></div><!-- 地图信息面板 --><div class="map-control bottom-20 left-6 p-4 max-w-xs"><h3 class="font-semibold text-gray-800 mb-2">地图信息</h3><div class="text-xs text-gray-500"><p>当前底图: <span id="current-basemap" class="font-medium text-primary">街道图</span></p><p>坐标: <span id="coordinates">经度: -- , 纬度: --</span></p></div></div><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 equatorconst 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>
</body></html>
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("天地图公共模块加载异常,请检查依赖和配置");}
}