基于CesiumJS的影像图层

979 阅读8分钟

原文地址:cesium.com/learn/cesiu…

本教程将介绍影像图层的基本概念及相关的 CesiumJS API。

CesiumJS 支持从多个服务获取、绘制和叠加高分辨率影像图层,包括 Cesium ion。借助 Cesium ion,您可以通过网络实时加载和展示精选的高分辨率影像,或者将自己的栅格数据作为影像图层嵌入到 CesiumJS 应用程序中。这些影像图层可以排序和混合在一起,并且每个图层的亮度、对比度、伽马值、色调和饱和度都可以动态调整。

如果您有自己的影像数据,可以参考「无人机或卫星影像切片教程」。

快速开始

在 Sandcastle 中打开 Hello World 示例。该示例创建了一个 Viewer 小部件,并使用 Cesium ion 提供的 Bing Maps 航拍影像作为底图进行渲染。通过向 Viewer 构造函数提供额外的参数,可以指定不同的底图。下面我们使用带标签的 Bing Maps 影像:

// 您的访问令牌可以在 https://ion.cesium.com/tokens 上找到。
// 将 'your_access_token' 替换为您的 Cesium ion 访问令牌。

Cesium.Ion.defaultAccessToken = "your_access_token";

const viewer = new Cesium.Viewer("cesiumContainer", {
  baseLayer: Cesium.ImageryLayer.fromWorldImagery({
    style: Cesium.IonWorldImageryStyle.AERIAL_WITH_LABELS,
  }),
  baseLayerPicker: false,
});

创建 Cesium ion 账户

本教程将使用来自 Cesium ion 的影像数据。请创建一个账户以获取您的访问令牌,这样您就可以在本教程中使用这些影像数据。点击这里注册,并在上面的代码中使用您的令牌。如果您已经有账户,请点击这里登录

在修改示例后,按 F8 运行它。

随着您放大或缩小,图层将根据需要动态加载和显示相应分辨率的影像数据。

添加另一个 Cesium ion 影像图层: 地球之夜

首先需要在 Asset Depot 中搜索并添加 "Earth at Night" 图层。

添加完成后,前往 My Assets 查看并记下对应的资产 ID。如下图所示,资产 ID 为 3812。

在上述示例代码的基础上,添加以下代码以加载并显示 "Earth at Night" 图层:

const blackMarble = Cesium.ImageryLayer.fromProviderAsync(
  Cesium.IonImageryProvider.fromAssetId(3812)
);
layers.add(blackMarble);

由于“地球之夜”图层是最后添加的,并且覆盖了全球范围,因此它遮挡了 Bing 图层。我们可以使用 layers.lower(blackMarble); 将地球之夜图层移到底部,还可以通过设置其透明度,将它与 Bing 图层混合,以更好地展示两个图层之间的关系:

blackMarble.alpha = 0.5; // 0.0 是透明的,1.0 是不透明的。

接下来,为了让夜间灯光更清晰,我们可以使用 brightness 属性来提高图层的亮度。

blackMarble.brightness = 2.0; // > 1.0 增加亮度,< 1.0 减少亮度。

最后,我们将添加第三个图层:在特定范围内绘制一张图片。

const cesiumLogo = Cesium.ImageryLayer.fromProviderAsync(
  Cesium.SingleTileImageryProvider.fromUrl(
    "../images/Cesium_Logo_overlay.png",
    {
      rectangle: Cesium.Rectangle.fromDegrees(
        -75.0,
        28.0,
        -67.0,
        29.75
      ),
    }
  )
);
layers.add(cesiumLogo);

效果如下图所示:

以下是完整的示例代码:

Cesium.Ion.defaultAccessToken = "your_access_token";
const viewer = new Cesium.Viewer("cesiumContainer", {
  baseLayer: Cesium.ImageryLayer.fromWorldImagery({
    style: Cesium.IonWorldImageryStyle.AERIAL_WITH_LABELS,
  }),
  baseLayerPicker: false,
});
const layers = viewer.scene.imageryLayers;
const blackMarble = Cesium.ImageryLayer.fromProviderAsync(
  Cesium.IonImageryProvider.fromAssetId(3812)
);
blackMarble.alpha = 0.5;
blackMarble.brightness = 2.0;
layers.add(blackMarble);
const cesiumLogo = Cesium.ImageryLayer.fromProviderAsync(
  Cesium.SingleTileImageryProvider.fromUrl(
    "../images/Cesium_Logo_overlay.png",
    {
      rectangle: Cesium.Rectangle.fromDegrees(
        -75.0,
        28.0,
        -67.0,
        29.75
      ),
    }
  )
);
layers.add(cesiumLogo);

随时可用的影像

Sandcastle 中的 ion Assets 标签包含很多由 Cesium ion 托管的影像瓦片集,CesiumJS 集成了 Cesium ion,使得开发者可以非常方便地将这些托管在 Cesium ion 上的瓦片集添加到他们的应用中。许多瓦片集也可以在部署在本地环境使用

更多影像提供程序

像示例中的前两个图层那样的高分辨率影像数据通常太大,无法完全载入内存或单个磁盘,因此这些影像就会被分割成较小的图块,称为瓦片。这些瓦片可以根据视图的需要逐步传输到客户端(即流式传输)。Cesium 支持多种不同的协议和标准,允许用户从不同的影像提供者那里请求地图瓦片(tiles)。大多数影像提供者提供基于 HTTP 的 REST 接口 请求瓦片,但不同的影像提供者在请求的格式和瓦片的组织方式上有所不同。Cesium 支持以下几种影像提供者:

跨域资源共享

作为一种安全措施,Web 浏览器会阻止 JavaScript 代码访问来自不同站点的图像数据(即跨域)。对于 WebGL 应用程序,如 CesiumJS,如果图像(在此情况下为影像瓦片)来自不同的主机名或端口,而服务器没有明确允许跨域访问的话,这些图像是不能作为纹理使用的。具体来说,服务器需要在 HTTP 响应中包含 跨域资源共享(CORS)头,以向浏览器声明这些图像可以被共享,允许其他站点读取其像素。

不幸的是,并非所有影像服务都支持 CORS。对于那些不支持 CORS 的服务,必须使用代理服务器,该代理服务器与托管您的应用程序在同一来源。通过使用代理服务器,Web 浏览器和 CesiumJS 客户端会认为这些瓦片来自同一个来源。要在影像提供程序中使用代理,可以在构造影像提供程序时使用 proxy 属性。

layers.addImageryProvider(
  new Cesium.WebMapServiceImageryProvider({
    url: new Cesium.Resource({
      url: "/path/to/imagery",
      proxy: new Cesium.DefaultProxy("/proxy/"),
    }),
    layers: [...]
  })
);

如果您在托管公开可访问的影像数据,我们鼓励您按照 这里 的说明,启用 CORS 配置。

影像提供程序 vs. 影像图层

到目前为止,我们还没有明确区分影像提供程序和影像图层的区别。影像提供程序负责向特定的地图服务请求瓦片数据,而图层负责显示这些瓦片。也就是说:

  • 影像提供程序(Imagery Provider):负责从特定的在线地图服务(例如 WMS、TMS、WMTS 或其他地图源)请求影像瓦片。它只是一个数据请求的接口,不负责数据的显示。
  • 影像图层(Imagery Layer):负责将影像提供程序请求到的瓦片实际显示在地图上。图层可以用来调整显示效果,例如设置透明度、亮度、对比度等。
const layer = layers.addImageryProvider(imageryProvider);

是以下代码的简写形式:

const layer = new Cesium.ImageryLayer(imageryProvider);
layers.add(layer);

通常情况下,我们创建影像提供程序的主要目的是为了生成图层,以便能够在地图上显示影像瓦片,同时我们也可以使用图层的属性(如 showalphabrightnesscontrast)来改变其外观(详细信息请参见 ImageryLayer)。而将影像提供程序和图层分离,我们可以实现职责分离。让影像提供程序只需关注数据的请求逻辑,而图层负责数据的显示逻辑。这种分离方式不仅提高了系统的可维护性,还使得编写新的影像提供程序更加简便高效。

影像图层集合(即上述示例中的 layers)决定了图层的绘制顺序。图层根据添加的先后顺序从下到上进行绘制。影像图层集合的操作类似于 Cesium 中的其他集合,使用函数如 addremoveget。除此之外,还可以使用 raiseraiseToToplowerlowerToBottom 来重新排序图层,详细信息请参见 ImageryLayerCollection

异步影像提供程序和错误处理

一些影像提供程序,例如 IonImageryProviderBingMapsImageryProvider,需要通过异步 Web 请求来进行初始化。处理这个问题有两种方式:

第一种方法我们已经在上述所有示例中使用过。ImageryLayer.fromProviderAsync 是一个帮助函数,它在后台处理所有异步操作,使你的应用代码看起来和同步一样。

然而,如果你需要更细粒度的控制和更灵活的错误处理,可以使用 fromUrl 工厂函数来创建异步影像提供程序,例如:

let imageryProvider;
try {
  // Blue Marble Next Generation July, 2004 imagery from NASA
  imageryProvider = await Cesium.IonImageryProvider.fromAssetId(3845);
} catch(error) {
  console.log(`There was an error while creating the imagery provider: ${error}`);
}
const imageryLayer = new Cesium.ImageryLayer(imageryProvider);
const viewer = new Cesium.Viewer("cesiumContainer", {
  baseLayer: imageryLayer
});

资源

你可以在 Sandcastle 中查看关于影像图层的示例:

此外,还可以查看以下参考文档: