【2025最新】ArcGIS for JS二维地图与三维地图的切换
本文适用 ArcGIS JS API 4.28-4.33 版本,是 【2025最新】ArcGIS for JS 街道、卫星、地形地貌底图切换》扩展教程,在实现支持街道图、卫星图、地形地貌图基础上,进一步实现了 3D 地图的切换功能。
文章目录
- [【2025最新】ArcGIS for JS二维地图与三维地图的切换](#【2025最新】ArcGIS for JS二维地图与三维地图的切换)
-
- 效果图
- 一、核心功能实现
-
- [1.1 模块加载与初始化](#1.1 模块加载与初始化)
- [1.2 底图定义](#1.2 底图定义)
-
- [1.2.1 街道图底图](#1.2.1 街道图底图)
- [1.2.2 卫星图底图](#1.2.2 卫星图底图)
- [1.2.3 地形地貌图底图](#1.2.3 地形地貌图底图)
- [1.2.4 3D 场景](#1.2.4 3D 场景)
- [1.3 视图创建](#1.3 视图创建)
- [1.4 底图切换逻辑](#1.4 底图切换逻辑)
-
- [1.4.1 底图更新函数](#1.4.1 底图更新函数)
- [1.4.2 视图切换函数](#1.4.2 视图切换函数)
- [1.5 绑定按钮事件](#1.5 绑定按钮事件)
- 二、全部代码
工具 /插件/系统 名 | 版本 | 说明 |
---|---|---|
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) {
const is3D = appConfig.activeView.type === "3d";
let activeViewpoint, scaleConversionFactor
// 3个2维底图互相切换
if (!is3D && buttonId != 'basemap-3d') {
appConfig.activeView.map.basemap = newBasemap;
return;
}
activeViewpoint = appConfig.activeView.viewpoint && appConfig.activeView.viewpoint.clone();
appConfig.activeView.container = null;
const latitude = appConfig.activeView.center.latitude;
scaleConversionFactor = Math.cos((latitude * Math.PI) / 180.0);
// 从3D切换到2D
if (is3D && buttonId != 'basemap-3d') {
appConfig.mapView.viewpoint = activeViewpoint;
appConfig.mapView.zoom = 8
appConfig.mapView.container = appConfig.container;
appConfig.mapView.map.basemap = newBasemap;
appConfig.activeView = appConfig.mapView;
} else {
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>
<style>
.border1{
border-radius: 100%;
border-width:2px ;
border-style:solid ;
border-color: #d1d5db #83b5ff #83b5ff transparent;
}
</style>
</head>
<body class="bg-gray-100 font-sans overflow-hidden h-screen">
<div class="absolute w-full h-full flex justify-center items-center">
<span class="relative flex size-10 animate-spin">
<span class="border1 size-10"></span>
</span>
</div>
<!-- 主地图容器 -->
<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=" 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="basemap-btn-active 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;
// 3D 图层
const scene = new WebScene({
portalItem: {
// autocasts as new PortalItem()
id: "c8cf26d7acab4e45afcd5e20080983c1",
},
});
appConfig.sceneView = createView("3d");
appConfig.sceneView.map = scene;
appConfig.activeView = appConfig.sceneView;
// 3. 创建地图视图
function createView(type) {
let view;
if (type === "2d") {
view = new MapView({
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";
let activeViewpoint, scaleConversionFactor
// 3个2维底图互相切换
if (!is3D && buttonId != 'basemap-3d') {
appConfig.activeView.map.basemap = newBasemap;
return;
}
activeViewpoint = appConfig.activeView.viewpoint && appConfig.activeView.viewpoint.clone();
appConfig.activeView.container = null;
const latitude = appConfig.activeView.center.latitude;
scaleConversionFactor = Math.cos((latitude * Math.PI) / 180.0);
// 从3D切换到2D
if (is3D && buttonId != 'basemap-3d') {
appConfig.mapView.viewpoint = activeViewpoint;
appConfig.mapView.zoom = 8
appConfig.mapView.container = appConfig.container;
appConfig.mapView.map.basemap = newBasemap;
appConfig.activeView = appConfig.mapView;
} else {
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("天地图公共模块加载异常,请检查依赖和配置");
}
}