从零开始:React + Cesium + 天地图打造国产清晰三维地图应用

1,041 阅读5分钟

Cesium‌ 是一个开源的 JavaScript 库,用于构建高性能的 3D 地球和地理空间可视化应用,无需插件即可在现代浏览器中运行。它由美国空间技术公司 AGI 于 2011 年启动,2012 年开源发布,现已成为地理信息系统(GIS)、智慧城市、国防等领域的重要开发工具。对于我们开发人员最实际的也是免费+酷炫。

天地图(MAPWORLD) ‌是由中国自然资源部主导建设的国家地理信息公共服务平台,于‌2011年1月18日正式上线‌。作为政府公益性网站,它旨在向社会提供权威、标准、统一的在线地理信息服务,推动地理信息数据开放共享,对我们开发人员最实际的就是免费+卫星图高清晰

使用前注意

本身cesium有自己的地图瓦块,由于我们要使用的是天地图关于国内地图卫星地图的高清晰度,需要替换为天地图的瓦片,但是天地图自身支持的cesium有版本要求,当前文章引入cesium还是使用天地图的cdn,具体可根据天地图文档进行调整。 三维服务 - 天地图 帮助文档

安装

引入组件

<script type="text/javascript" cesium="true" src="https://api.tianditu.gov.cn/cdn/demo/sanwei/static/cesium/Cesium.js"></script>
<script type="text/javascript" cesium="true" src="https://api.tianditu.gov.cn/cdn/plugins/cesium/Cesium_ext_min.js"></script>
<script type="text/javascript" cesium="true" src="https://api.tianditu.gov.cn/cdn/plugins/cesium/long.min.js"></script>
<script type="text/javascript" cesium="true" src="https://api.tianditu.gov.cn/cdn/plugins/cesium/bytebuffer.min.js"></script>
<script type="text/javascript" cesium="true" src="https://api.tianditu.gov.cn/cdn/plugins/cesium/protobuf.min.js"></script>
<link rel="stylesheet" cesium="true" href="https://api.tianditu.gov.cn/cdn/demo/sanwei/static/cesium/Widgets/widgets.css">  

挂载全局Cesium对象

创建global.d.ts,将Cesium挂载在window对象上

declare global {
    interface Window {
      Cesium: any;
    }
  }

代码

初始化位置添加地图加载初始化,添加组件绑定关系,完整代码如下:

import React, { useEffect, useRef, useState } from "react";

const TIANDITU_KEY = "xxxxx";
var tdtUrl = "https://t{s}.tianditu.gov.cn/";
const SUBDOMAINS = ["0", "1", "2", "3", "4", "5", "6", "7"];

export default function MapPage() {
  const cesiumContainer = useRef<HTMLDivElement>(null);
  const viewerRef = useRef<any>(null);

  useEffect(() => {
    if (!cesiumContainer.current) return;

    if (typeof window.Cesium === "undefined") {
      console.error("Cesium not loaded.");
      return;
    }
    const Cesium = window.Cesium;

    // 初始化地图
    const viewer = new Cesium.Map(cesiumContainer.current, {
      shouldAnimate: true,
      selectionIndicator: false,
      baseLayerPicker: false,
      fullscreenButton: false,
      geocoder: false,
      homeButton: false,
      infoBox: false,
      sceneModePicker: false,
      timeline: false,
      navigationHelpButton: false,
      navigationInstructionsInitiallyVisible: false,
      showRenderLoopErrors: false,
      shadows: false,
      imageryProvider: false,
    });
    viewerRef.current = viewer;

    // 水印取消
    viewer.cesiumWidget.creditContainer.style.display = "none";
    
    // sps显示
    viewer.scene.debugShowFramesPerSecond = true;

    viewer.scene.fxaa = true;
    viewer.scene.postProcessStages.fxaa.enabled = false;

    // 添加底图
    const imgMap = new Cesium.UrlTemplateImageryProvider({
      url:
        tdtUrl +
        "img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=" +
        TIANDITU_KEY,
      subdomains: SUBDOMAINS,
      tilingScheme: new Cesium.WebMercatorTilingScheme(),
      maximumLevel: 16,
      preloadAncestors: 1, // 预加载1级父级瓦片(平衡速度与内存)
      preloadSiblings: 1, // 预加载1级同级瓦片
      tileMatrixSetID: "GoogleMapsCompatible",
    });
    viewer.imageryLayers.addImageryProvider(imgMap);

    // 添加第二层底图
    const ciaMap = new Cesium.UrlTemplateImageryProvider({
      url:
        tdtUrl +
        "cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=" +
        TIANDITU_KEY,
      subdomains: SUBDOMAINS,
      tilingScheme: new Cesium.WebMercatorTilingScheme(),
      maximumLevel: 16,
      preloadAncestors: 1, 
      preloadSiblings: 1,
      tileMatrixSetID: "GoogleMapsCompatible",
    });
    viewer.imageryLayers.addImageryProvider(ciaMap);
    
    // 添加第三层底图
    const iboMap = new Cesium.UrlTemplateImageryProvider({
      url:
        tdtUrl +
        "ibo_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ibo&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=" +
        TIANDITU_KEY,
      subdomains: SUBDOMAINS,
      tilingScheme: new Cesium.WebMercatorTilingScheme(),
      maximumLevel: 16,
      preloadAncestors: 1,
      preloadSiblings: 1,
      tileMatrixSetID: "GoogleMapsCompatible",
    });
    viewer.imageryLayers.addImageryProvider(iboMap);

    // 聚焦到北京
    viewer.camera.flyTo({
      destination: Cesium.Cartesian3.fromDegrees(103.84, 31.15, 2000000),
      orientation: {
        heading: Cesium.Math.toRadians(0),
        pitch: Cesium.Math.toRadians(-70),
        roll: Cesium.Math.toRadians(0),
      },
    });

    return () => {
      if (viewerRef.current) {
        viewerRef.current.destroy();
        viewerRef.current = null;
      }
    };
  }, []);

  return (
    <div>
      <div
        ref={cesiumContainer}
        style={{
          width: "100%",
          height: "100vh",
          position: "fixed",
          top: 0,
          left: 0,
        }}
      />
    </div>
  );
}

image.png

怎么调参数

参数名类型默认值说明
容器与场景
containerstring/Element-地图渲染容器的 ID 或 DOM 元素
sceneModeSceneModeSCENE3D场景模式:SCENE3D(3D)/SCENE2D(2D)/COLUMBUS_VIEW(2.5D)
globeObject-地球配置(椭球体、地形开关等)
mapProjectionObjectWGS84地图投影方式(2D/Columbus View 模式下使用)
地图与地形
imageryProviderImageryProviderBing Maps底图服务(如 ArcGisMapServer/BingMaps
terrainProviderTerrainProvider-地形服务(如 Cesium.createWorldTerrain()
交互控件
animationbooleantrue是否显示时间轴动画控件
baseLayerPickerbooleantrue是否显示底图切换器
geocoderbooleantrue是否显示地理编码搜索框
sceneModePickerbooleantrue是否显示 2D/3D 切换按钮
timelinebooleantrue是否显示时间轴控件
fullscreenButtonbooleantrue是否显示全屏按钮
渲染与性能
shadowsbooleanfalse是否启用阴影效果(需配合光源)
requestRenderModebooleanfalse启用按需渲染(节省性能)
maximumScreenSpaceErrornumber2模型 LOD 细节级别(值越低越精细)
其他参数
infoBoxbooleantrue是否显示实体信息弹窗
skyBoxObject-自定义天空盒纹理
creditContainerstring/Element-指定版权信息显示容器
vrButtonbooleanfalse是否启用 VR 模式

image.png

怎么加标记和柱状图

假设我们有mapItems列表,作为标记的数据集。

const markers: Cesium.Entity[] = [];
mapItems.forEach((item) => {
  const marker = viewer.entities.add({
    name: item.id.toString(),
    position: Cesium.Cartesian3.fromDegrees(
      item.coordinate_y,
      item.coordinate_x,
      1000
    ),
    // 优化标签样式(新增背景、阴影、字体调整)
    label: {
      text: item.name,
      font: "18px 'Microsoft YaHei', sans-serif", // 更易读的字体
      fillColor: Cesium.Color.WHITE, // 白色文字
      outlineColor: Cesium.Color.fromCssColorString("#2c3e50"), // 深灰色轮廓
      outlineWidth: 3,
      style: Cesium.LabelStyle.FILL_AND_OUTLINE,
      pixelOffset: new Cesium.Cartesian2(0, -40), // 向上偏移更多避免遮挡
      // 新增背景框(半透明圆角)
      backgroundColor: new Cesium.Color(0.1, 0.3, 0.6, 0.8), // 蓝灰色半透明
      backgroundPadding: new Cesium.Cartesian2(8, 4), // 文字与背景边距
      cornerRadius: 6, // 圆角
      // 新增阴影效果
      showBackground: true,
      disableDepthTestDistance: Number.POSITIVE_INFINITY, // 避免被地形遮挡
    },
    // 优化图标样式(使用自定义图标+动态缩放)
    billboard: {
      // image: "@/asserts/images/direction-marker-arrow-down.png", // 替换为项目内图标(需将图片放入 public/markers 目录)
      scale: 0.9, // 适当放大图标
      verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 底部对齐坐标点
      horizontalOrigin: Cesium.HorizontalOrigin.CENTER, // 水平居中
      // 新增鼠标悬停缩放效果
      scaleByDistance: new Cesium.NearFarScalar(1000, 1.2, 100000, 0.8), // 近大远小
      // 新增透明度渐变(避免远处过亮)
      translucencyByDistance: new Cesium.NearFarScalar(
        1000,
        1.0,
        100000,
        0.7
      ),
    },
  });

  // 添加3D柱状图(假设item包含:total, online, active, alert)
  const barChartOrigin = Cesium.Cartesian3.fromDegrees(
    item.coordinate_y + 0.003, // 增加经度偏移避免重叠
    item.coordinate_x,
    3000 // 提升基准高度
  );

  // 总设备数(蓝色柱)
  const totalColumn = viewer.entities.add({
    position: Cesium.Cartesian3.fromDegrees(
      item.coordinate_y + 0.003,
      item.coordinate_x,
      3000
    ),
    cylinder: {
      length: item.total * 100 + 5000, // 增加基础高度1000米
      topRadius: 10000, // 增大半径
      bottomRadius: 10000,
      material: Cesium.Color.BLUE.withAlpha(0.9),
    },
    label: {
      text: `${item.total}`,
      font: '16px "Microsoft YaHei"',
      fillColor: Cesium.Color.WHITE,
      outlineColor: Cesium.Color.BLACK,
      outlineWidth: 2,
      style: Cesium.LabelStyle.FILL_AND_OUTLINE,
      verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
      pixelOffset: new Cesium.Cartesian2(0, -50), // 下移标签
    },
  });

  const onlineColumn = viewer.entities.add({
    position: Cesium.Cartesian3.fromDegrees(
      item.coordinate_y,
      item.coordinate_x,
      3000
    ),
    cylinder: {
      length: item.online * 100 + 5000, // 增加基础高度1000米
      topRadius: 10000, // 增大半径
      bottomRadius: 10000,
      material: Cesium.Color.GREEN.withAlpha(0.9),
    },
    label: {
      text: `${item.online}`,
      font: '16px "Microsoft YaHei"',
      fillColor: Cesium.Color.WHITE,
      outlineColor: Cesium.Color.BLACK,
      outlineWidth: 2,
      style: Cesium.LabelStyle.FILL_AND_OUTLINE,
      verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
      pixelOffset: new Cesium.Cartesian2(0, -50), // 下移标签
    },
  });

  markers.push(marker);
});