在 Cesium 里画 3D 地球,光有底图和地形还不够,很多时候你还需要在上面叠行政区边界、业务区域、多边形分析结果…… 这些东西通常以 GeoJSON 提供,有时也会是 TopoJSON

好消息是:Cesium 自带了一个 GeoJsonDataSource,帮你把这两种格式统一处理成 Cesium 的实体(Entity),不用自己去解析。

这篇文章就围绕一个简单问题展开: 我有一份 GeoJSON 文件,怎么把它加到 Cesium 里,并且控制样式、贴地、访问实体?

下面是一步步的教程。

准备一个基础 Viewer

先假设你已经把 Cesium 引入页面,并在 HTML 里有一个容器:

<div id="cesiumContainer"></div>

JavaScript 里创建一个最基本的 Viewer:

var viewer = new Cesium.Viewer("cesiumContainer");

之后所有的矢量数据(包括 GeoJSON)都会挂在 viewer.dataSources 上。

用 GeoJsonDataSource 加载 GeoJSON / TopoJSON

GeoJsonDataSource 是一个能同时处理 GeoJSONTopoJSON 的数据源。 你可以直接把 load 的结果加入到 viewer.dataSources 里,一行搞定:

viewer.dataSources.add(
  Cesium.GeoJsonDataSource.load("../../SampleData/ne_10m_us_states.topojson", {
    stroke: Cesium.Color.HOTPINK,
    fill: Cesium.Color.PINK,
    strokeWidth: 3,
    markerSymbol: "?",
  })
);

这里有几个点值得关注:

  • ne_10m_us_states.topojson:示例里用的是 TopoJSON 文件,换成 .geojson 也同样适用。
  • load(url, options)

    • url 是你的 GeoJSON / TopoJSON 文件路径。
    • options 用来控制样式,后面单独展开说。
  • viewer.dataSources.add(...):把这个数据源挂到 Viewer 上,Cesium 会自动把里面的 feature 转成实体并渲染出来。

如果你只是想简单加载加个样式,这种“链式写法”就够用了。

常用样式参数

上面的代码里顺便演示了几种常见的样式参数:

{
  stroke: Cesium.Color.HOTPINK, // 线和多边形边界颜色
  fill: Cesium.Color.PINK,      // 多边形填充颜色
  strokeWidth: 3,               // 线宽
  markerSymbol: '?'             // 点要素使用的标记符号(比如 '?'、'!' 等)
}

大致规则可以这么理解:

  • 线 / 多边形边界strokestrokeWidth 控制。如果你的 GeoJSON 里有 LineStringPolygon,这些样式就会生效。

  • 多边形填充 fill 决定多边形内部的颜色。

  • 点要素 如果是 PointmarkerSymbol 可以设置点的图标符号(具体样式会和 Cesium 默认的样式有关)。

如果后面你想对单个实体做更细的控制,也可以在数据加载完成后对 dataSource.entities 逐个修改,这里先用全局样式把它跑起来。

贴地显示

默认情况下,GeoJSON 里的几何要素会悬浮在地球上(跟 height 数据有关)。 如果你希望它紧贴地形,比如行政区边界贴在地表,就可以用 clampToGround 选项:

var geojsonOptions = {
  clampToGround: true,
};

然后在加载时传入:

var neighborhoodsPromise = Cesium.GeoJsonDataSource.load(
  "./Source/SampleData/neighborhoods.geojson",
  geojsonOptions
);

clampToGround: true 时,Cesium 会尝试根据地形(Terrain)把几何“压”到地面上,适合用来画各种边界、路径等贴地图层。

注意:

  • 要让贴地效果真正好看,最好启用真实地形(而不是默认的椭球体)。
  • 贴地会有一定性能开销,大量要素时要注意评估性能。

在数据加载完成后做事

GeoJsonDataSource.load 返回的是一个 Promise,而不是立即可用的对象。 上面的示例里,先把 Promise 保存下来:

var neighborhoodsPromise = Cesium.GeoJsonDataSource.load(
  "./Source/SampleData/neighborhoods.geojson",
  geojsonOptions
);

var neighborhoods;

neighborhoodsPromise.then(function (dataSource) {
  // 把数据源加到 viewer 中
  viewer.dataSources.add(dataSource);

  // 如果你想后面单独访问这些实体,可以在这里保存下来
  neighborhoods = dataSource.entities;
});

这一段的使用思路是:

  1. load,得到一个 Promise。
  2. 等数据真正加载好 (then 回调被触发) 后,把 dataSource 加入 viewer
  3. 同时把 dataSource.entities 存到一个变量里,方便后面做高亮、筛选、点击交互等操作。

比如后续你可以做这样的事:

// 简单示例:把所有实体边界改成蓝色
neighborhoods.values.forEach(function (entity) {
  if (entity.polygon) {
    entity.polygon.outlineColor = Cesium.Color.BLUE;
  }
});

和 KML 的关系顺便一提

如果要加载 KML 格式的矢量数据,将代码中的 GeoJsonDataSource.load(...) 改为 KmlDataSource.load(...) 即可。

可以理解为:在 Cesium 里,KML 和 GeoJSON 是两条不同的加载路径

  • 加载 KML:用 KmlDataSource
  • 加载 GeoJSON / TopoJSON:用 GeoJsonDataSource

两者在 Cesium 中最终都会变成一堆实体(Entity),只是输入格式和使用的 DataSource 不一样而已。 如果你的数据来源从 KML 迁移到 GeoJSON,只需要:

  • 换一个 DataSource(KmlDataSourceGeoJsonDataSource
  • 换一下文件路径和参数

整体使用方式是类似的。