Appearance
测试 API 之加载历史影像
演示视频
注意事项
在实际的项目开发中,影像、地形是所有地理信息相关可视化的基础数据。 TileSer 离线地图服务系统,可以用于在内网发布地图影像、历史影像、地形高程、3D Tiles 模型、Osgb 模型、街景等。 其他内容,详见 API 文档。 本文将以 Cesium 为例,讲解如何调用调用无水印历史高清影像。
支持平台
三维引擎:Cesium 所有版本
游戏引擎:Unreal
其他:其他二维码等平台,目前没有主动适配(用处较小,接受定制)
代码示例和注释
本示例将展示 Cesium 如何添加历史影像服务。
说明:最新影像和无水印默认影像有何区别? 最新影像,默认加载的是最新影像。不考虑清晰度、云层等遮挡情况。 默认影像,会考虑清晰度、云层遮挡,默认选择最高清和优质的影像。
HTML
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XbsjTileserImageryProvider示例</title>
<script type="text/javascript" src="https://cesium.com/downloads/cesiumjs/releases/1.112/Build/Cesium/Cesium.js"></script>
<script src="./tileserHis.js"></script>
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/cesium/1.109.0/Widgets/widgets.css">
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
.dateDropdownContainer {
position: absolute;
z-index: 99999;
top: 60px;
/* 调整这个值以避免与按钮组重叠 */
left: 20px;
}
</style>
</head>
Javscript
javascript
<body>
<div id="cesiumContainer">
<div id="dateDropdownContainer" class="dateDropdownContainer" style="display: none;">
<select id="dateDropdown"></select>
</div>
</div>
<script>
const viewer = new Cesium.Viewer('cesiumContainer', {});
// 全局变量,存储当前选中的时间
let currentSelectedTime;
// 初始化地图和监听相机移动事件
updateImageryLayer(0); // 初始化图层
getAndDisplayInitialTimeData(); // 获取初始时间并显示
let isCameraMoving = false;
viewer.camera.moveStart.addEventListener(() => {
isCameraMoving = true;
});
viewer.camera.moveEnd.addEventListener(async () => {
if (isCameraMoving) {
isCameraMoving = false;
const allTimes = await getCurrentTileCoordinates(viewer);
displayDatesInDropdown(allTimes);
}
});
// 更新图层的函数
function updateImageryLayer(decimalTime) {
viewer.imageryLayers.removeAll(); // 移除所有现有的图层
var imgLayer = new XbsjTileserHisImageryProvider({
indexTime: decimalTime
});
viewer.imageryLayers.addImageryProvider(imgLayer);
}
// 显示下拉菜单的函数
function displayDatesInDropdown(allTimes) {
const dropdown = document.getElementById('dateDropdown');
dropdown.innerHTML = ''; // 清空现有选项
let closestIndex = -1;
let closestDiff = Infinity;
// 函数:计算两个日期字符串之间的天数差异
function getDaysDifference(dateStr1, dateStr2) {
const date1 = new Date(dateStr1);
const date2 = new Date(dateStr2);
const diffTime = Math.abs(date2 - date1);
return diffTime / (1000 * 60 * 60 * 24);
}
allTimes.forEach((time, index) => {
const option = document.createElement('option');
option.value = time;
option.textContent = time;
dropdown.appendChild(option);
// 寻找与 currentSelectedTime 最接近的时间
const diff = getDaysDifference(time, currentSelectedTime);
if (diff < closestDiff) {
closestDiff = diff;
closestIndex = index;
}
});
// 如果找到最接近的时间,则选择它
if (closestIndex !== -1) {
dropdown.selectedIndex = closestIndex;// 更新当前选中的时间
}
if (allTimes.length > 0) {
document.getElementById('dateDropdownContainer').style.display = 'block';
} else {
document.getElementById('dateDropdownContainer').style.display = 'none';
}
dropdown.onchange = function () {
currentSelectedTime = dropdown.value; // 更新当前选中的时间
updateImageryLayer(currentSelectedTime);
};
}
// 获取初始时间并显示的函数
async function getAndDisplayInitialTimeData() {
const initialTimes = await getCurrentTileCoordinates(viewer); // 获取时间数据
displayDatesInDropdown(initialTimes); // 显示时间数据
}
</script>
</body>
历史影像效果图
核心代码说明
获取视角的时间表
javascript
//根据瓦片xyz值,获取瓦片的时间信息
async function xyzToAllInfo(x, y, z) {
const response = await fetch(`https://tileser.giiiis.com/xyzinfo/${z}/${x}/${y}`);
const jsonData = response.json();
return jsonData;
}
以 https://tileser.giiiis.com/xyzinfo/14/27317/5444 为例,获取所有时间信息。
获取视角中心瓦片的时间信息
结合上面的方法
javascript
//获取cesium视口范围的信息。
async function getCurrentTileCoordinates(viewer) {
const scene = viewer.scene;
const ellipsoid = scene.globe.ellipsoid;
const camera = scene.camera;
// 获取相机的经纬度
const cameraPositionCartographic = ellipsoid.cartesianToCartographic(camera.position);
const longitude = Cesium.Math.toDegrees(cameraPositionCartographic.longitude);
const latitude = Cesium.Math.toDegrees(cameraPositionCartographic.latitude);
// 计算缩放级别 (Z)
const cameraHeight = cameraPositionCartographic.height;
const level = altToZoom(cameraHeight);
// 转换经纬度为瓦片坐标 (X, Y)
let x = Math.floor((longitude + 180) / 360 * Math.pow(2, level + 1));
let y = Math.floor((90 - latitude) / 180 * Math.pow(2, level));
//获取信息XYZ信息,查看时间进行返回
const allTimes = await xyzToAllInfo(x, y, level);
return allTimes;
}
//3D级数转为2D高,根据
function altToZoom(cameraHeight) {
const levels = [
{ maxAlt: 250000000, level: 0 },
{ maxAlt: 25000000, level: 1 },
{ maxAlt: 9000000, level: 2 },
{ maxAlt: 7000000, level: 3 },
{ maxAlt: 4400000, level: 4 },
{ maxAlt: 2000000, level: 5 },
{ maxAlt: 1000000, level: 6 },
{ maxAlt: 493977, level: 7 },
{ maxAlt: 218047, level: 8 },
{ maxAlt: 124961, level: 9 },
{ maxAlt: 56110, level: 10 },
{ maxAlt: 40000, level: 11 },
{ maxAlt: 13222, level: 12 },
{ maxAlt: 7000, level: 13 },
{ maxAlt: 4000, level: 14 },
{ maxAlt: 2500, level: 15 },
{ maxAlt: 1500, level: 16 },
{ maxAlt: 600, level: 17 },
{ maxAlt: 250, level: 18 },
{ maxAlt: 150, level: 19 },
{ maxAlt: 50, level: 20 }
];
for (const { maxAlt, level } of levels) {
if (cameraHeight >= maxAlt) {
return level;
}
}
return 20; // 默认级别
}
封装历史影像 ImageryProvider
继承 UrlTemplateImageryProvider 主要是为了可以方便的改时间信息。如果不封装这个。直接修改 url 地址也是一样的。
javascript
class XbsjTileserHisImageryProvider extends Cesium.UrlTemplateImageryProvider {
constructor(options) {
// 设置默认的 minimumLevel 和 tilingScheme
options.url = "https://tileser.giiiis.com/timetile/";
options.minimumLevel = 1;
options.maximumLevel = 18;
options.tilingScheme = new Cesium.GeographicTilingScheme();
super(options);
// 初始化 indexTime 属性
this.indexTime = options.indexTime || 0; // 默认值为0
}
async requestImage(x, y, level, request) {
// 重写 requestImage 方法以支持异步操作
// 异步获取时间信息
try {
// 根据获取的时间信息构建新的 URL
let imageUrl = this.buildImageUrl(this.indexTime, x, y, level);
return Cesium.ImageryProvider.loadImage(this, imageUrl);
} catch (error) {
return undefined;
}
}
buildImageUrl(indexTime, x, y, level) {
return `http://tileser.giiiis.com/timetile/${indexTime}/${level}/${x}/${y}.jpg`;
}
}
根据时间去加载历史影像
封装 XbsjTileserHisImageryProvider,就很方便修改 url 的信息。然后封装一个根据时间传参,穿件地图的方法,
javascript
function updateImageryLayer(decimalTime) {
viewer.imageryLayers.removeAll(); // 移除所有现有的图层
var imgLayer = new XbsjTileserHisImageryProvider({
indexTime: decimalTime
});
viewer.imageryLayers.addImageryProvider(imgLayer);
}
//创建插件中,历史影像核心对象XbsjTileserHisImageryProvider,配置indexTime为0,即可加载最新影像
//当传值为allTimes的数组的某一个值,如果"2021-9-28",即可修改时间。
请注意!请注意! 切换影像或者时间,请务必移除全部影像! viewer.imageryLayers.removeAll(); // 移除所有现有的图层
更新影像,交互,较为复杂。
简单来说就是根据相机变化,await getCurrentTileCoordinates(viewer);获取时间信息。然后交给下拉菜单进行展示。
javascript
// 全局变量,存储当前选中的时间和上一次选中的时间
let currentSelectedTime;
updateImageryLayer(0); //初始化图层,默认为0,即刚开始默认加载最新时间
getAndDisplayInitialTimeData(); //获取初始时间并显示,这里主要作用直接获取全部时间,然后显示到下拉菜单的控件中
//该处,用户可以根据业务需要,重写displayDatesInDropdown函数。
//**//
let isCameraMoving = false;
viewer.camera.moveStart.addEventListener(() => {
isCameraMoving = true;
});
viewer.camera.moveEnd.addEventListener(async () => {
if (isCameraMoving) {
isCameraMoving = false;
const allTimes = await getCurrentTileCoordinates(viewer);
displayDatesInDropdown(allTimes);
}
});
//以上代码核心作用是监听相机改变事件,也就是当:相机位移,即获取最新的全部时间。然后displayDatesInDropdown进行判定和显示,
//**//
async function getAndDisplayInitialTimeData() {
const initialTimes = await getCurrentTileCoordinates(viewer); // 获取时间数据
displayDatesInDropdown(initialTimes); // 显示时间数据
}
显示时间,较为复杂
根据时间表,进行展示,同时!最关键的是,onchange 事件,也就是点击选择时间就切换整个地球的时间。 以下代码建议全保留
javascript
// 显示下拉菜单的函数
function displayDatesInDropdown(allTimes) {
const dropdown = document.getElementById('dateDropdown');
dropdown.innerHTML = ''; // 清空现有选项
let closestIndex = -1;
let closestDiff = Infinity;
// 函数:计算两个日期字符串之间的天数差异
function getDaysDifference(dateStr1, dateStr2) {
const date1 = new Date(dateStr1);
const date2 = new Date(dateStr2);
const diffTime = Math.abs(date2 - date1);
return diffTime / (1000 * 60 * 60 * 24);
}
allTimes.forEach((time, index) => {
const option = document.createElement('option');
option.value = time;
option.textContent = time;
dropdown.appendChild(option);
// 寻找与 currentSelectedTime 最接近的时间
const diff = getDaysDifference(time, currentSelectedTime);
if (diff < closestDiff) {
closestDiff = diff;
closestIndex = index;
}
});
// 如果找到最接近的时间,则选择它
if (closestIndex !== -1) {
dropdown.selectedIndex = closestIndex;// 更新当前选中的时间
}
if (allTimes.length > 0) {
document.getElementById('dateDropdownContainer').style.display = 'block';
} else {
document.getElementById('dateDropdownContainer').style.display = 'none';
}
dropdown.onchange = function () {
currentSelectedTime = dropdown.value; // 更新当前选中的时间
updateImageryLayer(currentSelectedTime);
};
}
UE 等其他说明
历史影像同样可以在 ue 中实现,不过,因为相对更加复杂。尤其是获取相机信息。这个必须要在 UE 里实现。 为了方便试用,代码已经封装到 EarthSDK,详见 https://earthsdk.com/
如果不在意,获取时间麻烦,UE 加载需要转为地图引擎,比如 cesium for Unreal、Unity。
javascript
地址:https://tileser.giiiis.com/timetile/tms/{时间数值}/tilemapresource.xml
将上面的地址,复制到 Cesium World Terrain 的 CesiumTileMapServiceRasterOverlay 的 url 中