Openlayers 自定义地图瓦片加载(一)

一、概要

在 OpenLayers 中,TileLayer 用于显示地图瓦片,以不同缩放级别和地理区域切分管理地图各部分的图片。本文对使用固定的 URL 模板自定义瓦片网格两种方式对Openlayers 瓦片请求行为进行探究,并列举自定义瓦片网格的使用场景。

二、 瓦片请求探究

  • 使用固定 URL 模板加载谷歌影像:
  const map = new Map({
    target,
    layers: [
      new TileLayer({
        source: new XYZ({
          url: 'https://www.google.com/maps/vt?lyrs=y&gl=cn&x={x}&y={y}&z={z}',
          crossOrigin: 'anonymous',
          wrapX: true
        })
      })
    ],
    view: new View({
      projection: 'EPSG:3857', // 坐标系EPSG:4326或EPSG:3857
      zoom: 0, // 打开页面时默认地图缩放级别
      center: fromLonLat([121.5, 25]), // 转到墨卡托坐标系
    })
  })

加载结果:
请添加图片描述
url参数中, 地址字符串内{x}、{y} 和 {z} 是占位符
当加载特定切片时,会根据当前视图的 x、y 和 z 值替换占位符,生成对应切片的 URL

  • x={x}: 占位符 {x} 会被切片的水平索引替换。
  • y={y}: 占位符 {y} 会被切片的垂直索引替换。
  • z={z}: 占位符 {z} 会被缩放级别替换。

除使用固定的 URL 模板外,地图数据源 (new XYZ) 还接受自定义瓦片网格的回调函数( tileUrlFunction),tileUrlFunction 接受一个三维坐标参数,可动态生成返回每个切片的 URL,用于处理特殊的瓦片请求或自定义加载逻辑

  • 使用自定义瓦片网格加载谷歌影像:
  const map = new Map({
    target,
    layers: [
      new TileLayer({
        source: new XYZ({
          tileUrlFunction,
          //url: 'https://www.google.com/maps/vt?lyrs=y&gl=cn&x={x}&y={y}&z={z}',
          crossOrigin: 'anonymous',
          wrapX: true
        })
      })
    ],
    view: new View({
      projection: 'EPSG:3857', // 坐标系EPSG:4326或EPSG:3857
      zoom: 0, // 打开页面时默认地图缩放级别
      center: fromLonLat([121.5, 25]), // 转到墨卡托坐标系
    })
  })
/**
 * @description 自定义瓦片网格回调函数,作为 XYZ数据源 tileUrlFunction 的参数
 * @param {number[]} zxy 瓦片坐标 [z, x, y]
 * @returns {string} 生成的瓦片 URL
 */
function tileUrlFunction(zxy: number[]): string {
  const [z, x, y] = zxy
    return `https://www.google.com/maps/vt?lyrs=y&gl=cn&x=${x}&y=${y}&z=${z}`
}

加载结果:
(和使用固定的 URL 模板加载的执行结果是一样的)
请添加图片描述
参数解析:
tileUrlFunction 的参数 zxy ,是瓦片网格坐标系的三维坐标数组
其中,z 表示缩放级别, x 和 y 表示切片的行列索引
经测试,具体关系如下:

  • 在特定的 z 级别下,x 和 y 确定具体的切片位置。x 从左到右递增,y 从上到下递增。
  • x 和 y 的取值范围是从 0 到 2^z - 1
    请添加图片描述

根据其特性,就可以花式加载瓦片了。


三、自定义瓦片网格的使用场景

  • 根据比例尺缩放加载不同底图(当然也可以通过监听地图缩放事件实现):
  const map = new Map({
    target,
    layers: [
      new TileLayer({
        source: new XYZ({
          tileUrlFunction,
          //url: 'https://www.google.com/maps/vt?lyrs=y&gl=cn&x={x}&y={y}&z={z}',
          crossOrigin: 'anonymous',
          wrapX: true
        })
      })
    ],
    view: new View({
      projection: 'EPSG:3857', // 坐标系EPSG:4326或EPSG:3857
      zoom: 0, // 打开页面时默认地图缩放级别
      center: fromLonLat([121.5, 25]), // 转到墨卡托坐标系
    })
  })
/**
 * @description 根据瓦片坐标生成瓦片的 URL
 * @param {number[]} zxy 瓦片坐标 [z, x, y]
 * @returns {string} 生成的瓦片 URL
 */
function tileUrlFunction(zxy: number[]): string {
  const [z, x, y] = zxy;
  if (z > 4) {
    // 缩放级别大于4
    return `http://t0.tianditu.gov.cn/DataServer?T=vec_w&x=${x}&y=${y}&l=${z}&tk=99a8ea4a53c8553f6f3c565f7ffc15ec`;
  } else {
    // 缩放级别小于等于4
    return `https://www.google.com/maps/vt?lyrs=y&gl=cn&x=${x}&y=${y}&z=${z}`;
  }
}

效果:
请添加图片描述

  • 根据圈定范围加载不同底图:
    (小范围内加载的精细影像,全球范围加载低层级影像,当然也可以通过图层叠加方式实现)
<template>
  <!--地图-->
  <div ref="mapContainer" class="mapContainer" id="mapContainer"></div>
</template>
<script lang="ts" setup>
import { onMounted, shallowRef } from 'vue'
import { View, Map } from "ol"
import { fromLonLat } from 'ol/proj'
import TileLayer from 'ol/layer/Tile'
import { XYZ } from 'ol/source'
import { intersects } from 'ol/extent'

// 地图容器
const mapContainer = shallowRef<HTMLDivElement>()
// 地图对象
const map = shallowRef<Map>()
// 范围
const chinaExtent = [74.69154385120196, -0.000001, 137.8205912282765, 55.456454556548]
/**
 * @description 创建地图实例
 * @param {Document | DocumentId} target 地图容器
 * @returns 地图对象
 */

const createMap = function (target: HTMLElement | string,): Map {
  // 创建地图
  const map = new Map({
    target,
    layers: [
      new TileLayer({
        source: new XYZ({
          tileUrlFunction,
          //url: 'https://www.google.com/maps/vt?lyrs=y&gl=cn&x={x}&y={y}&z={z}',
          crossOrigin: 'anonymous',
          wrapX: true
        })
      })
    ],
     view: new View({
      projection: 'EPSG:3857', // 坐标系EPSG:4326或EPSG:3857
      zoom: 0, // 打开页面时默认地图缩放级别
      center: fromLonLat([121.5, 25]), // 转到墨卡托坐标系
    })
  })
  return map
}
/**
 * @description 确认瓦片是否与指定边界范围相交
 * @param {number[]} zxy 瓦片坐标 [z, x, y]
 * @returns {boolean} 相交判定
 */
function isIntersect(zxy: number[]): boolean {
  // 获取瓦片的地理坐标范围
  const extent = getExtent(zxy);
  return intersects(extent, chinaExtent);
}
/**
 * @description 将瓦片坐标转换为地理坐标范围
 * @param {number[]} zxy 瓦片坐标 [z, x, y]
 * @returns {number[]} 瓦片的地理坐标范围 [minX, minY, maxX, maxY]
 */
function getExtent(zxy: number[]): number[] {
  const [z, x, y] = zxy;
  // 计算当前缩放级别的比例因子 2^z
  const scale = Math.pow(2, z);
  // 计算瓦片在经度方向的范围
  const minX = (x / scale) * 360 - 180;
  const maxX = ((x + 1) / scale) * 360 - 180;
  // 计算瓦片在纬度方向的范围
  const minY = 90 - (y / scale) * 180;
  const maxY = 90 - ((y + 1) / scale) * 180;
  // 返回地理坐标范围
  return [minX, minY, maxX, maxY];
}
/**
 * @description 根据瓦片坐标生成瓦片的 URL
 * @param {number[]} zxy - 瓦片坐标 [z, x, y]
 * @returns {string} - 生成的瓦片 URL
 */
function tileUrlFunction(zxy: number[]): string {
  const [z, x, y] = zxy;
  // 判断瓦片是否与边界范围相交(这里也可以同时加入缩放比例尺判定)
  if (isIntersect(zxy)) {
    // 瓦片与边界相交,使用天地图 URL
    return `http://t0.tianditu.gov.cn/DataServer?T=vec_w&x=${x}&y=${y}&l=${z}&tk=99a8ea4a53c8553f6f3c565f7ffc15ec`;
  } else {
    // 瓦片不与边界相交,使用 Google URL
    return `https://www.google.com/maps/vt?lyrs=y&gl=cn&x=${x}&y=${y}&z=${z}`;
  }
}
onMounted(async () => {
  map.value = createMap(mapContainer.value!);
})

</script>
<style lang="scss">
#mapContainer {
  position: absolute;
  top: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
}
</style>

效果:请添加图片描述

  • 适应自定义瓦片切片规则

一般瓦片的切片文件夹组织结构是类似这样的↓
请添加图片描述
这种情况使用固定 URL 模板加载是没有问题的:
‘https://www.google.com/maps/vt?lyrs=y&gl=cn&x={x}&y={y}&z={z}’

但如果瓦片切片是使用16进制命名组织的:

请添加图片描述
请添加图片描述
此时无法直接使用URL模板加载,需换算zxy至16进制并补0,需要使用自定义瓦片网格方法:

/**
 * @description 将数字转换为 8 位 16 进制字符串
 * @param {number} num 要转换的数字
 * @returns {string} 8 位 16 进制字符串
 */
function toHex8(num: number): string {
  return num.toString(16).padStart(8, '0'); // 转换为 16 进制并补齐到 8 位
}

/**
 * @description 根据瓦片坐标生成自定义的瓦片 URL
 * @param {number[]} zxy 瓦片坐标 [z, x, y]
 * @returns {string} 生成的瓦片 URL
 */
function tileUrlFunction(zxy: number[]): string {
  const [z, x, y] = zxy;

  // 将瓦片坐标转换为 8 位 16 进制路径
  const hexX = toHex8(x);
  const hexY = toHex8(y);

  // 构建瓦片 URL
  return `https://tileserver.com/${z}/${hexX}/${hexY}.png`;
}

也可以花里胡哨:请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

無可言喻

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值