OpenLayers 加载 nginx 托管的 ArcGIS 切片文件

OpenLayers 加载 nginx 托管的 ArcGIS 切片文件

一、背景

最近遇到一个项目有大约 1000 个切片地图服务要发。然而根据经验,ArcGIS Server 发布的服务超过 100 个之后,稳定性和性能都要受到影响了。项目经费很有限,再加购 ArcGIS Server 有点困难。于是我就想到了一个方法:把地图服务切片做成松散型,然后使用 nginx 托管,前端以 restful 风格去请求切片文件。

二、实现思路

  1. OpenLayers 支持使用模板链接加载地图服务。(参考示例 XYZ Esri
  2. ArcGIS 发布的服务支持使用"服务地址/z/y/x"( restful 风格)的形式去请求切片。
  3. ArcGIS 发布服务后存储的切片是按照层级存放的。(参考ArcGIS 切片文件结构解析
  4. 将"服务地址/z/y/x"指向切片文件的路径即可取到切片文件来显示地图。

三、实现步骤

1. 先实现加载自己发布的服务。

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" href="//cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.15.1/css/ol.min.css" type="text/css">
    <script src="//cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.15.1/build/ol.min.js"></script>
    <style>
      .map {
        height: 400px;
        width: 100%;
      }
    </style>
    
    <!-- 引入坐标转换库 -->
    <script src="//cdn.bootcdn.net/ajax/libs/proj4js/2.7.4/proj4.min.js"></script>
    <title>加载一张地图</title>
  </head>
  <body>
    <div id="map" class="map"></div>
    <script type="text/javascript">

        // 定义坐标系, 这里定义之后后续代码中的坐标系代码 4490 才会有效
        proj4.defs("EPSG:4490","+proj=longlat +ellps=GRS80 +no_defs");
        ol.proj.proj4.register(proj4);

        // 计算各个缩放级别的分辨率
        const maxZoom = 9; // 从服务说明中获取
        const minResolution = 0.7039130078552128; // 从服务说明中获取
        // 计算
        const resolutions = [];
        const matrixIds = [];

        for (let z = 0; z < maxZoom; ++z) {
            resolutions[z] = minResolution / Math.pow(2, z);
            matrixIds[z] = z;
        }

        // 此处用的是笔者自己本机的服务地址。测试此代码时需改为自己的服务地址。
        const mapServerBasePath = 'http://localhost:6080/arcgis/rest/services/EarthG11/MapServer';
        const map = new ol.Map({
            target: 'map',
            layers: [
                new ol.layer.Tile({
                    source: new ol.source.XYZ({
                        url:  mapServerBasePath +'/tile/{z}/{y}/{x}',
                        tileGrid: new ol.tilegrid.WMTS({
                            origin: [-180, 90],// 从服务说明中获取
                            resolutions: resolutions,
                            matrixIds: matrixIds,
                        }),
                        wrapX: true,
                        projection: 'EPSG:4490'
                    })
                })
            ],
            view: new ol.View({
              projection: 'EPSG:4490',
              center: [106, 27],
              zoom: 7
            })
        });
    </script>
  </body>
</html>

示例 XYZ Esri 的代码很简单,但是自己发的服务坐标系和切片方案都比较特别,所以需要额外补充一些代码。

2. 将切片文件使用 nginx 托管。

操作步骤略

3. 基于以上代码,改为调用 nginx 托管的切片文件。

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" href="//cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.15.1/css/ol.min.css" type="text/css">
    <script src="//cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.15.1/build/ol.min.js"></script>
    <style>
      .map {
        height: 400px;
        width: 100%;
      }
    </style>
    
    <!-- 引入坐标转换库 -->
    <script src="//cdn.bootcdn.net/ajax/libs/proj4js/2.7.4/proj4.min.js"></script>
    <title>加载一张地图</title>
  </head>
  <body>
    <div id="map" class="map"></div>
    <script type="text/javascript">

        // 定义坐标系, 这里定义之后后续代码中的坐标系代码 4490 才会有效
        proj4.defs("EPSG:4490","+proj=longlat +ellps=GRS80 +no_defs");
        ol.proj.proj4.register(proj4);

        // 计算各个缩放级别的分辨率
        const maxZoom = 9; // 从服务说明中获取
        const minResolution = 0.7039130078552128; // 从服务说明中获取
        // 计算
        const resolutions = [];
        const matrixIds = [];

        for (let z = 0; z < maxZoom; ++z) {
            resolutions[z] = minResolution / Math.pow(2, z);
            matrixIds[z] = z;
        }

        /**
         * 转换数字长度
         * @param {number} num 十进制数
         * @param {number} radix 要转换的进制基数
         * @param {number} length 返回的字符串长度
         */
        function formatNumberLength (num, radix = 10, length = 0) {
            let str = num.toString(radix)
            if (length === 0) {
                return str
            }
            while (str.length < length) {
                str = '0' + str
            }
            return str
        }

        // 此处用的是笔者自己本机的服务地址。测试此代码时需改为自己的服务地址。
        const mapServerBasePath = 'http://localhost:8888/EarthG11/图层/_alllayers';
        const map = new ol.Map({
            target: 'map',
            layers: [
                new ol.layer.Tile({
                    source: new ol.source.XYZ({
                        url:  mapServerBasePath +'/{z}/{y}/{x}',
                        tileGrid: new ol.tilegrid.WMTS({
                            origin: [-180, 90],// 从服务说明中获取
                            resolutions: resolutions,
                            matrixIds: matrixIds,
                        }),
                        wrapX: true,
                        projection: 'EPSG:4490',
                        // URL 重新构建
                        tileLoadFunction: (imageTile, src) => {
                            const Z = 'L' + formatNumberLength(imageTile.tileCoord[0], 10, 2)
                            const X = 'C' + formatNumberLength(imageTile.tileCoord[1], 16, 8)
                            const Y = 'R' + formatNumberLength(imageTile.tileCoord[2], 16, 8)
                            imageTile.getImage().src = mapServerBasePath + '/' + Z + '/' + Y + '/' + X + '.png'
                        }
                    })
                })
            ],
            view: new ol.View({
              projection: 'EPSG:4490',
              center: [106, 27],
              zoom: 7
            })
        });
    </script>
  </body>
</html>

以上代码关键之处在于

(1)服务地址换成了静态文件路径 http://localhost:8888/EarthG11/图层/_alllayers

(2)添加了用于将十进制数转为固定长度的字符串的函数 formatNumberLength。根据文章 ArcGIS 切片文件结构解析,切片的层级文件夹名为 L + "2位十进制数";切片的行号文件夹为 R + "8位16进制数";切片的列号为 C + "8位16进制数";

(3)ol.source.XYZ 的参数中添加了 tileLoadFunction,用于重新构建切片请求路径。

(完)

相关推荐
疯狂学习GIS2 天前
Landsat遥感影像分幅条带介绍与矢量下载:WRS的Path与Row
gis·rs·gis数据·遥感数据·landsat
希艾席蒂恩2 天前
四款GIS工具箱软件解析:满足企业多样化空间数据需求
信息可视化·gis·webgl·数字孪生·可视化大屏
GISBox3 天前
高斯泼溅文件如何转换成3DTiles?GISBox帮你轻松实现在Cesium上的应用
gis·cesium
GISBox8 天前
3D Gaussian Splatting文件如何转换成3DTiles?这款免费GIS工具箱能帮你轻松解决问题
3d·gis·cesium·倾斜摄影·切片
高堂明镜悲白发8 天前
在 SQLite 中使用 SpatiaLite 实现地理空间数据自动化读写
数据库·sqlite·自动化·gis
落霞的思绪9 天前
关于网页地图的坐标系
gis
GIS好难学10 天前
考研出分24小时,人类精神状态图鉴
前端·考研·gis·gis开发·webgis·地信
GIS追梦人14 天前
Cesium 入门之 Entity API
gis·cesium
安大桃子16 天前
Cesium实现深色地图效果
前端·gis·cesium
图导物联17 天前
基于WebGIS技术的校园地图导航系统架构与核心功能设计
系统架构·智慧校园·gis·webgl·地图导航·电子地图·校园地图导航