Bootstrap

cesium3Dtileset本地缓存

项目模型过大导致内网环境下加载速度慢,用户体验不佳。由于浏览器无法直接读取本地文件,我们尝试使用Electron框架发布桌面端应用,以便通过Node访问本地文件。然而,用户仍希望在Web端使用。为此,我们考虑利用浏览器的缓存、sessionStorage和localStorage,但它们存在存储限制。最终,选择了使用IndexedDB进行数据存储

项目结构

核心代码

顺着路径去找到这个文件 检索processArrayBuffer将代码加入

function isEmpty(obj) {
  return Object.keys(obj).length === 0;
}
function setArrayBufferObj(name, arrayBuffer) {
  window.bufferList[name] = arrayBuffer
  console.log(window.bufferList, "添加")
}

 if (isEmpty(window.arrayBufferList)) {
      arrayBuffer = await requestPromise;
      if (arrayBuffer) {
        setArrayBufferObj(tile._contentHeader.uri, arrayBuffer)
      }
    } else {
      let uri = tile._contentHeader.uri
      arrayBuffer = window.arrayBufferList[uri]
    }

直接上代码

main.js

import { createApp } from 'vue'
import './style.css'

import "cesium/Build/Cesium/Widgets/widgets.css";
import App from './App.vue'
window.CESIUM_BASE_URL = '/Cesium';
// createApp(App).mount('#app')

function loadModuleScript() {
    createApp(App).mount('#app')
}

let indexedDBVision = Number(window.localStorage.getItem("iDBV"));
console.log(indexedDBVision);
if (indexedDBVision < 1) {
    indexedDBVision = 1;
}
window.arrayBufferList = {};
let indexedDB = null;
const dbName = "MODELCATCHE";
const objectStoreName = "product";
// 打开一个 IndexedDB 数据库,指定数据库名称和版本号
const req = window.indexedDB.open(dbName, indexedDBVision);
// 监听数据库打开成功事件
req.onsuccess = function (event) {
    // 当数据库成功打开时输出信息
    console.log("数据库打开成功");
    // 获取打开的数据库对象
    indexedDB = event.target.result;
    // 检查对象仓库是否存在
    if (!indexedDB.objectStoreNames.contains(objectStoreName)) {
        // 如果对象仓库不存在,加载模块脚本
        loadModuleScript();
        console.log("对象仓库不存在,添加对象仓库及初始数据");
        // 设置下一个版本号
        const version = indexedDB.version + 1;
        // 将新版本号存入 localStorage
        window.localStorage.setItem("iDBV", version);
        // 关闭当前数据库连接
        indexedDB.close();
        // 再次打开数据库,使用新版本号
        const secondReq = window.indexedDB.open(dbName, version);
        // 处理数据库升级事件
        secondReq.onupgradeneeded = function (event) {
            const db = event.target.result;
            // 创建一个对象仓库,指定主键
            const objectStore = db.createObjectStore(objectStoreName, {
                keyPath: "item",
            });
            // 向对象仓库中添加初始数据,初始化为空数组
            objectStore.add({ item: "item", data: {} });
        };

        // 监听数据库成功打开的事件
        secondReq.onsuccess = function (event) {
            // 初始化全局变量
            window.bufferList = {};
            // 获取新打开的数据库对象
            indexedDB = event.target.result;
            // 创建一个读写事务
            const transaction = indexedDB.transaction([objectStoreName], "readwrite");
            // 获取对象仓库
            const objectStore = transaction.objectStore(objectStoreName);
            // 在这里继续处理业务逻辑
        };
    } else {
        // 如果对象仓库已存在,创建一个读写事务
        const transaction = indexedDB.transaction([objectStoreName], "readwrite");
        // 获取对象仓库
        const objectStore = transaction.objectStore(objectStoreName);
        // 从对象仓库获取数据
        const getDataReq = objectStore.get("item");
        // 监听获取数据成功事件
        getDataReq.onsuccess = function (event) {
            // 如果成功获取到数据
            if (getDataReq.result) {
                // 将获取到的数据赋值给全局变量
                window.arrayBufferList = getDataReq.result.data;
                window.bufferList = {};
                console.log("已有数据:", getDataReq.result.data);
                // 加载模块脚本
                loadModuleScript();
                // 在这里处理已有数据
            } else {
                // 如果没有数据,添加初始数据
                console.log("没有数据,添加初始数据");
                objectStore.add({ item: "item", data: {} }); // 初始化数据为空数组
                window.bufferList = {};
                loadModuleScript(); // 加载模块脚本
            }
        };
    }
};
req.onerror = function (event) {
    console.log("打开数据库时出错:", event.target.error);
};


App.vue

<template>
  <div id="app">
    <div style="position: absolute;left: 40px;top: 40px;z-index: 999;color: blue;"> {{ loadingText }}</div>
    <div style="width: 100%;height: 100%;" id="ces"></div>


    <!-- <div class="background-video-container">
    <video autoplay muted loop class="background-video">
      <source src="/0828_0.mp4" type="video/mp4">
    </video>
    <div class="content">
      <slot></slot>
    </div>
  </div> -->

  </div>

</template>
<script setup>
import * as Cesium from "cesium";
import { onMounted, ref } from 'vue'
const loadingText = ref('')
function createViewer() {
  const CesiumToken =
    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI3YTBjZmUwYi1lNzg3LTRiY2UtYTBmZi00NzJmY2E3MzhkZDAiLCJpZCI6MjM1NTU2LCJpYXQiOjE3MjQxMzkyNzZ9.1xQWtg4OFt2mv9rHql3sbxyd0mQyfTjNLcRTw1a6PDk";
  //  const earthImageryProvider = await Cesium.SingleTileImageryProvider.fromUrl(earthImg);
  Cesium.Ion.defaultAccessToken = CesiumToken;
  window.viewer = new Cesium.Viewer("ces", {

    infoBox: false,
    geocoder: false, // 位置查找工具
    homeButton: false, // 视角返回初始位置
    sceneModePicker: false, // 选择视角的模式(球体、平铺、斜视平铺)
    baseLayerPicker: false, // 图层选择器(地形影像服务)
    navigationHelpButton: false, // 导航帮助(手势,鼠标)
    animation: false, // 左下角仪表盘(动画器件)
    timeline: true, // 底部时间线
    fullscreenButton: false, // 全屏
    vrButton: false, // VR
    selectionIndicator: false,
    shouldAnimate: true,

  });
  load3dModel()
}
async function load3dModel() {
  // 从指定的 URL 加载 3D tileset
  let Tileset = await Cesium.Cesium3DTileset.fromUrl(
   url,
    {
      // 缓存设置以提高加载性能
      cacheBytes: Number.MAX_VALUE,
      maximumCacheOverflowBytes: Number.MAX_VALUE,
    }
  );

  // 将 tileset 添加到 Cesium 视图器的场景中
  window.viewer.scene.primitives.add(Tileset);
  // 将视图器缩放以适应 tileset
  window.viewer.zoomTo(Tileset);
  // 添加一个事件监听器,用于监控 tileset 的加载进度
  Tileset.loadProgress.addEventListener(function (loaded, total) {
    // 从 localStorage 中获取 iDBVval,以确定加载状态
    let iDBVval = Number(window.localStorage.getItem("iDBVval"));

    // 根据 iDBVval 值更新加载文本
    loadingText.value = iDBVval === 0
      ? "正在下载模型,请稍等片刻"
      : "正在加载模型,请稍等片刻";

    // 检查加载是否完成
    if (loaded === 0 && total === 0) {
      // 如果这是第一次加载,提示保存
      if (iDBVval === 0) {
        loadingText.value = "模型保存中...";
        let indexedDBVision = Number(window.localStorage.getItem("iDBV"));
        const dbName = "MODELCATCHE";

        // 打开 IndexedDB
        const req = window.indexedDB.open(dbName, indexedDBVision);

        req.onsuccess = function (event) {
          const indexedDB = event.target.result; // 获取数据库对象
          const transaction = indexedDB.transaction(["product"], "readwrite"); // 创建事务
          const objectStore = transaction.objectStore("product"); // 获取对象存储

          console.log(window.bufferList);

          // 用模型数据更新对象存储
          const updateRequest = objectStore.put({
            item: "item",
            data: window.bufferList,
          });

          updateRequest.onsuccess = function () {
            loadingText.value = "模型保存成功"; // 成功时更新加载文本

            // 获取已保存的数据以确认
            const getDataReq = objectStore.get("item");
            getDataReq.onsuccess = function () {
              if (getDataReq.result) {
                window.localStorage.setItem("iDBVval", 1); // 更新 localStorage 状态
                loadingText.value = "模型加载完成"; // 更新加载文本

                // 延迟以便于视觉反馈
                setTimeout(() => {
                  console.log(getCurrentTime());
                }, 1000);
              }
            };
          };

          updateRequest.onerror = function () {
            loadingText.value = "模型保存失败"; // 失败时更新加载文本
          };
        };
      } else {
        // 如果之前已经加载,只需确认加载完成
        loadingText.value = "模型加载完成";

        // 延迟以便于视觉反馈
        setTimeout(() => {
          console.log(getCurrentTime());
        }, 1000);
      }
    }
  });
}

onMounted(() => {
  createViewer()
})
function getCurrentTime() {
  const now = new Date();
  const year = now.getFullYear();
  const month = String(now.getMonth() + 1).padStart(2, '0'); // 月份从0开始
  const day = String(now.getDate()).padStart(2, '0');
  const hours = String(now.getHours()).padStart(2, '0');
  const minutes = String(now.getMinutes()).padStart(2, '0');
  const seconds = String(now.getSeconds()).padStart(2, '0');
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
</script>
<style scoped>
#app {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}

.background-video-container {
  position: relative;
  width: 100%;
  height: 100vh;
  /* 全屏视频 */
  overflow: hidden;
}

.background-video {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* 确保视频覆盖整个背景 */
  transform: translate(-50%, -50%);
  z-index: 1;
  /* 视频置于底层 */
}

.content {
  position: relative;
  z-index: 2;
  /* 页面内容放置于视频上层 */
  color: #ffffff;
  /* 确保内容颜色在视频背景上清晰可见 */
}
</style>

需要注意的地方

1.缓存模型的地址必须和当时储存时的地址一样,需要替换模型的话就要手动清理数据库和缓存,可以自己优化一下我这里只说如何本地缓存

2.启动命令需要加上 --force这里不做解释自行百度

 "dev": "vite --force",

3.鼠标缩放查看模型时有时会导致模型一部分切片重新请求导致缺失,最好是在加载时加入loading一是防止用户操作,二是为了提高数据储存成功的概率,在模型加载事件完成后加入这个属性就可以防止模型缺失的问题

本人比较愚钝目前只能这样解决,有哪位大神有更好的办法希望能沟通交流一下

;