Featured image of post CesiumJS 加载 Supermap Rest 服务

CesiumJS 加载 Supermap Rest 服务

# 解析 Supermap Rest 根目录

从 Supermap Rest 服务的根目录出发,第一步是请求 CatalogList。返回的结构大致是这样子的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
[
    {
        "resourceConfigID": "maps",
        "supportedMediaTypes": [
            "application/xml",
            "text/xml",
            "application/json",
            "application/fastjson",
            "application/rjson",
            "text/html",
            ...
        ],
        "path": "http://192.168.31.4:8090/iserver/services/map-China/rest/maps",
        "name": "maps",
        "resourceType": "CatalogList"
    },
    ...
]

我们需要里面的 "path",去请求一个 CatalogList,可以取出对应的 "name",让用户自己去选择请求哪个 CatalogList。

一个简单的 SupermapRestClient 实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
export class SupermapRestClient {
  private _url: string;

  constructor(url: string) {
    this._url = url;
  }

  async doRequest() {
    const resource = new Cesium.Resource(this._url + ".json");
    const json = await resource.fetchJson();
    const resourceList: { name: string; client: SupermapCatalogListClient }[] = [];
    for (const item of json) {
      if (item.resourceType === "CatalogList") {
        resourceList.push({
          name: item.name,
          client: new SupermapCatalogListClient(item.path)
        });
      }
    }
    return resourceList;
  }
}

其中的 SupermapCatalogListClient 在下一小节实现。

# 获取 CatalogList

现在假如用户已经选择需要请求的 CatalogList,再来看以一下 CatalogList 的结构:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
[
    {
        "resourceConfigID": "map",
        "supportedMediaTypes": [
            "application/xml",
            "text/xml",
            "application/json",
            "application/fastjson",
            "application/rjson",
            "text/html",
            ...
        ],
        "path": "http://192.168.31.4:8090/iserver/services/map-China/rest/maps/China",
        "name": "China",
        "resourceType": "StaticResource"
    },
    ...
]

同理,取出 "path""name",一个简单的 SupermapCatalogListClient 实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
export class SupermapCatalogListClient {
  private _url: string;

  constructor(url: string) {
    this._url = url;
  }

  async doRequest() {
    const resource = new Cesium.Resource(this._url + ".json");
    const json = await resource.fetchJson();
    const resourceList: { name: string; client: SupermapRestStaticResourceClient }[] = [];
    for (const item of json) {
      if (item.resourceType === "StaticResource") {
        resourceList.push({
          name: item.name,
          client: new SupermapRestStaticResourceClient(item.path)
        });
      }
    }
    return resourceList;
  }
}

其中的 SupermapRestStaticResourceClient 在下一小节实现。

# 获取 StaticResource

# 获取 Capabilities

在很多地图协议中,地图的 metadata 一般也叫做 Capabilities, StaticResource 请求的就是地图的 metadata,所以一个简单的 SupermapRestStaticResourceClient 实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
export class SupermapRestStaticResourceClient {
  private _url: string;

  constructor(url: string) {
    this._url = url;
  }

  async getCapabilities() {
    const jsonResource = new Cesium.Resource(this._url + ".json");
    return await jsonResource.fetchJson();
  }
}

这个返回的 json 里面有很多信息,但是没有我们最需要的地图瓦片地址,先忽略它,之后再来看。

# 获取地图瓦片

打开 iServer 的 Rest 服务地图列表界面,可以看到有一些可以预览效果的链接。

iServer

通过查看网络请求,可以找到地图瓦片请求,显示最后请求的是一个 tileImage.png 的图片:

tileImage Request

以此,可以为 SupermapRestStaticResourceClient 添加一个新的方法来构造这个请求:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
export class SupermapRestStaticResourceClient {
  ...

  getTileImageResource(tilingScheme: Cesium.TilingScheme) {
    let originX: number;
    let originY: number;
    if (tilingScheme instanceof Cesium.WebMercatorTilingScheme) {
      originX = -20037508.342787;
      originY = 20037508.342787;
    } else {
      originX = -180;
      originY = 90;
    }

    const tileImageResource = new Cesium.Resource(this._url + "/tileImage.png");
    tileImageResource.appendQueryParameters({
      transparent: true,
      cacheEnabled: true,
      width: 256,
      height: 256,
      redirect: false,
      overlapDisplayed: false,
      origin: `{x:${originX},y:${originY}}`,
      x: "{x}",
      y: "{y}",
      scale: "{scale}"
    });

    return tileImageResource;
  }
}

其中,origin 需要根据不同的投影坐标系或者地理坐标系进行修改,x, y, scale 这三个参数交给 Cesium 去填写。

xy 很好理解,就是水平和垂直方向上的瓦片坐标,但是 scale 是什么。

# 计算 scale

在 Supermap 的文档中,可以找到相关的描述:scale

$$ scale = \frac{1}{Resolution \frac{PPI}{0.0254}} $$

  • Resolution 代表每一个像素有多少实际距离
  • PPI 代表了每一英尺有多少个像素,这个数值一般为 96
  • 0.0254 是英尺和米的转换比例

以 WebMercator 为例,它的第 0 级瓦片整张地图实际大小为 40075016.68×40075016.68 米左右,假如向 Supermap 发起一个 256×256 像素大小的图片请求,那么:

$$ \begin{aligned} \cr Resolution &= \frac{40075016.68}{256} = 156543.03\cr scale &= \frac{1}{156543.03 \frac{96}{0.0254}} = 1.6901635718379278e-9 \end{aligned} $$

以此类推,我们可以计算出 WGS84 和 WebMercator 各个级别下的 scale 值,并用一个数组来表示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const WGS84Scales = [
  3.38032714321e-9, 6.76065428641e-9, 1.352130857282e-8, 2.704261714564e-8, 5.408523429128e-8, 1.0817046858257e-7,
  2.1634093716514e-7, 4.3268187433028e-7, 8.6536374866056e-7, 1.73072749732112e-6, 3.46145499464224e-6,
  6.92290998928448e-6, 1.3845819978568952e-5, 2.7691639957137904e-5, 5.538327991427581e-5, 1.1076655982855162e-4,
  2.2153311965710323e-4, 4.4306623931420646e-4, 8.861324786284129e-4, 0.0017722649572568258, 0.0035445299145136517,
  0.007089059829027303
];
const WebmercatorScales = [
  1.6901635716e-9, 3.38032714321e-9, 6.76065428641e-9, 1.352130857282e-8, 2.704261714564e-8, 5.408523429128e-8,
  1.0817046858257e-7, 2.1634093716514e-7, 4.3268187433028e-7, 8.6536374866056e-7, 1.73072749732112e-6,
  3.46145499464224e-6, 6.92290998928448e-6, 1.3845819978568952e-5, 2.7691639957137904e-5, 5.538327991427581e-5,
  1.1076655982855162e-4, 2.2153311965710323e-4, 4.4306623931420646e-4, 8.861324786284129e-4, 0.0017722649572568258,
  0.0035445299145136517, 0.007089059829027303
];

# 获取 Rectangle 和 TilingScheme

Rectangle 就是 Cesium 里面的经纬度范围,TilingScheme 就是 Cesium 里面的投影坐标系和地理坐标系。

这些信息被记录在一开始我们请求的 Capabilities 里面。

为 SupermapRestStaticResourceClient 添加一个新的方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
export class SupermapRestStaticResourceClient {
  ...

  getTilingSchemeFromCapabilities(capabilities: any) {
    let tilingScheme: Cesium.TilingScheme;
    if (capabilities.prjCoordSys.epsgCode === 4326) {
      const rectangle = new Cesium.Rectangle(
        capabilities.bounds.left,
        capabilities.bounds.bottom,
        capabilities.bounds.right,
        capabilities.bounds.top
      );
      tilingScheme = new Cesium.GeographicTilingScheme({
        numberOfLevelZeroTilesX: 2,
        numberOfLevelZeroTilesY: 1,
        rectangle
      });
    } else if (capabilities.prjCoordSys.epsgCode === 3857) {
      const southwest = new Cesium.Cartesian2(capabilities.bounds.left, capabilities.bounds.bottom);
      const northeast = new Cesium.Cartesian2(capabilities.bounds.right, capabilities.bounds.top);
      tilingScheme = new Cesium.WebMercatorTilingScheme({
        numberOfLevelZeroTilesX: 1,
        numberOfLevelZeroTilesY: 1,
        rectangleSouthwestInMeters: southwest,
        rectangleNortheastInMeters: northeast
      });
    } else {
      throw new Error(`espsgCode ${capabilities.prjCoordSys.epsgCode} is not supported.`);
    }
    return tilingScheme;
  }
}

# 构造 SupermapRestImageryProvider

最后使用 SupermapRestStaticResourceClient 去构造一个 SupermapRestImageryProvider,它继承自 Cesium.UrlTemplateImageryProvider:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
export class SupermapRestImageryProvider extends Cesium.UrlTemplateImageryProvider {
  private constructor(options: Cesium.UrlTemplateImageryProvider.ConstructorOptions) {
    super(options);
  }

  static async fromSupermapRestStaticResourceClient(client: SupermapRestStaticResourceClient) {
    const capabilities = await client.getCapabilities();

    const tilingScheme = client.getTilingSchemeFromCapabilities(capabilities);

    const tileImageResource = client.getTileImageResource(tilingScheme);

    return new SupermapRestImageryProvider({
      url: tileImageResource,
      tilingScheme,
      customTags: {
        scale: function (_imageryProvider: Cesium.UrlTemplateImageryProvider, _x: number, _y: number, level: number) {
          if (tilingScheme instanceof Cesium.WebMercatorTilingScheme) {
            return WebmercatorScales[level];
          } else {
            return WGS84Scales[level];
          }
        }
      }
    });
  }
}

其中,使用了 customTags 功能,因为 scale 不是 Cesium.UrlTemplateImageryProvider 默认有的参数,需要我们自定义一个函数返回在不同 level 下的 scale 值。

# 代码调用流程

最后,演示如何去使用上面编写的方法。

# 请求 CatalogList

1
2
3
4
5
6
7
const url = "你的 Supermap Rest 地址";

// 创建 SupermapRestClient
const client = new SupermapRestClient(url);

// 请求 CatalogList。
const catalogList = await client.doRequest();

等待用户选择哪一个 CatalogList。

# 请求 StaticResource

1
2
3
4
5
6
7
8
// 假设用户选择了第一个
const selectId = 0;

// 获取用户选择的 CatalogListClient
const catalogListClient: SupermapCatalogListClient = catalogList[selectId].client;

// 请求 StaticResource
const staticResourceList = await catalogListClient.doRequest();

等待用户选择哪一个 StaticResource。

# 添加 ImageryProvider

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 假设用户选择了第一个
const selectId = 0;

// 获取用户选择的 StaticResourceClient
const staticResourceClient: SupermapRestStaticResourceClient = staticResourceList[selectId].client;

// 创建 ImageryProvider
const provider = await SupermapRestImageryProvider.fromSupermapRestStaticResourceClient(staticResourceClient);

// 添加 ImageryProvider
viewer.imageryLayers.addImageryProvider(provider);

# 完整代码&效果图

完整代码:cesium-supermap-rest-imagery-provider

效果图

# 参考资料

使用 Hugo 构建
主题 StackJimmy 设计