news 2026/5/27 8:21:18

EarthSDK3实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
EarthSDK3实战

在 WebGL 三维可视化领域,CesiumJS 是当之无愧的王者,但其庞大的 API 和复杂的坐标系让许多开发者望而却步。EarthSDK 地球可视化二次开发框架,一套代码,实现 Cesium、UnrealEngine、OpenLayers 多引擎可视化。本文记录了基于 EarthSDK 3版本的二次开发实战过程,涵盖环境搭建、常用功能实现。


一、 搭建一个地球

1.访问官方网站下载模板

2.个人从0搭建 (vue+vite项目)

Node.js ≥ 20.x(推荐 LTS)Vue 3 + Vite 已不再支持 Node 18 及以下
包管理器 pnpm / npm / yarn / bun
推荐 pnpm(速度快、磁盘占用小)
IDE VS Code 必装插件:Vue - Official(原 Volar)

#环境检查 node -v # 输出 v20.x 或更高 pnpm -v # 或 npm -v
  • 项目初始化

  • 安装earthsdk基础包
pnpm add earthsdk3 --save # pnpm add cesium pnpm add earthsdk3-ue --save # pnpm add cesium pnpm add earthsdk3-cesium --save #静态资源提取库 pnpm add earthsdk3-assets --save #配置一个资源copy pnpm add vite-plugin-static-copy #配置插件 pnpm add vite-plugin-cesium
  • 配置

main.ts

import { createApp } from 'vue' import App from './App.vue' import { ESObjectsManager } from 'earthsdk3'; import { ESCesiumViewer } from 'earthsdk3-cesium'; import { ESUeViewer } from 'earthsdk3-ue'; const objm = new ESObjectsManager(ESUeViewer, ESCesiumViewer); createApp(App,{ objm }).mount('#app') objm.sceneTree.createSceneObjectTreeItemFromJson({ "id": "ae103185-08c7-4ed0-b6d4-15ad77bbbf66", "type": "ESImageryLayer", "url": "https://server.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", "maximumLevel": 18, "name": "全球影像", "allowPicking": true })

vite.config.ts

import vue from '@vitejs/plugin-vue'; import { defineConfig, normalizePath } from 'vite'; import cesium from 'vite-plugin-cesium'; import { viteStaticCopy } from 'vite-plugin-static-copy'; import path from 'path'; export default defineConfig({ // Use relative asset paths so dist can be hosted under subdirectories. base: './', server: { proxy: { '/pfsc-api': { target: 'https://pfsc.agri.cn', changeOrigin: true, rewrite: (p) => p.replace(/^\/pfsc-api/, '') } } }, resolve: { alias: { '@': path.resolve(__dirname, 'src'), // 配置Cesium的访问 cesium: path.resolve(__dirname, 'node_modules/cesium/Source/Cesium') } }, plugins: [ vue(), cesium(), // 运行和构建时copy viteStaticCopy({ targets: [ { src: normalizePath(path.resolve(__dirname, './node_modules/earthsdk3-assets')), dest: './js' } ] }) ] })

getobjm.ts

import { ESObjectsManager } from "earthsdk3"; import { inject } from "vue"; /** * 获取ESObjectsManager * @returns {ESObjectsManager} */ export function getobjm() { const objm = inject<ESObjectsManager>('objm'); if (!objm) throw new Error('ESObjectsManager not found'); return objm; }

MapContainer.vue

<script setup lang="ts"> import {getobjm} from "@/scripts/getobjm.ts"; import {nextTick, ref} from "vue"; import type { ESObjectsManager } from "earthsdk3"; const container = ref<HTMLDivElement | null>(null); const objm: ESObjectsManager = getobjm() as ESObjectsManager; console.log("objm",objm); objm.viewerCreatedEvent.don(async (viewer: any) => { viewer.clickEvent.don((e: any) => { viewer.pick(e.screenPosition, "customPick").then((res: unknown) => { console.log("全局点击事件"); }); }); }); nextTick(() => { if (!container.value) return; objm.createCesiumViewer(container.value); }) </script> <template> <div class="container" ref="container"></div> </template> <style scoped lang="scss"> </style>

app.vue

<script setup lang="ts"> import MapContainer from "@/views/MapContainer.vue"; import {provide} from "vue"; const props = defineProps(['objm']); provide('objm', props.objm); </script> <template> <MapContainer /> </template> <style scoped></style>

成功界面

二、 实战核心功能模块

1、引擎切换

#创建Cesium视口 const viewer = objm.createCesiumViewer(container.value); #创建UE视口 const options = { type: "ESUeViewer", container.value, options: { "https://earth3d.alink.link:30002", "earthsdk3"}, } const viewer = objm.createUeViewer(options); #引擎切换 const switchEngine = (engine) => { if (engine === 'cesium') { objm.switchToCesiumViewer({ "container": container.value }) } else { objm.switchToUEViewer({ "container": container.value, "uri": "https://earth3d.alink.link:30002", "app": "earthsdk3" }) } }

2、获取ESS里场景的数据

# 获取ess登陆凭证 async function fetchEssLoginAuth() { let authorization = localStorage.getItem("essAuthorization"); if (authorization) return authorization; const url="ESS登陆地址"; //例如https://earth3d:1111/login try { const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ username: '登陆账号', password: '登陆密码' }) }); const data = await response.json(); if (data.status !== "ok") { throw new Error(`登录失败,原因:${data.status}`); } localStorage.setItem("essAuthorization", data.data); return data.data; } catch (error) { console.error('ESS认证失败:', error); throw error; } } #获取内容 async function fetchEssContent(id) { const authorization = await fetchEssLoginAuth(); const url=`https://earth3d:1111/staticscene/get?id=${id}` try { const response = await fetch(url, { method: 'GET', headers: { 'Authorization': `${authorization}` } }); const data = await response.json(); if (data.status !== "ok") { throw new Error(`获取场景内容失败,原因:${data.status}`); } const rootChildren = data?.data?.content?.sceneTree?.root?.children; if (rootChildren && Array.isArray(rootChildren)) { return rootChildren; } } catch (error) { console.error('获取场景内容失败:', error); throw error; } } #返回数据结构 const response=[ { "children": [], "name": "全球影像", "sceneObj": { "maximumLevel": 18, "name": "全球影像", "id": "08838491-acba-451e-bf1f-ed4d458f7e38", "type": "ESImageryLayer", "url": "https://server.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}" } }, { "children": [], "collapsed": true, "name": "boundary", "sceneObj": { "strokeStyle": { "color": [ 0.21568627450980393, 0.9294117647058824, 0.03529411764705882, 1 ], "material": "", "width": 5, "ground": false, "materialParams": {}, "widthType": "screen" }, "flyInParam": { "flyDuration": 1, "rotation": [ 359.99999999999886, -37.20170635557474, 359.99999999999903 ], "position": [ 101.18400820868538, 30.28480013892119, 137990.32151619304 ] }, "name": "boundary", "filled": false, "id": "7a0ca06f-6054-44c6-848f-51855b2b8cb4", "type": "ESGeoJson", "url": "https://gismap.alink.link/3dmapfiles/rangtang/rangtangxian.json" } }, { "children": [], "name": "road", "sceneObj": { "name": "road", "id": "5c3cc102-a8bc-4bf6-a1c2-fdc9c77e19c6", "type": "ESImageryLayer", "url": "https://mapdata.alink.link:6001/indexCia/tms/tilemapresource.xml", "zIndex": 1 } }, { "children": [], "collapsed": true, "name": "terrain", "sceneObj": { "name": "terrain", "id": "d11ddc26-7a25-4351-a100-ee0d22283276", "type": "ESImageryLayer", "url": "https://mapdata.alink.link:6001/pak/layer.json", "zIndex": 2 } }, { "children": [ { "children": [], "collapsed": true, "name": "蒲西乡", "sceneObj": { "name": "蒲西乡", "fontSize": 18, "id": "36c9a5b1-ba60-427c-9efd-8ad394967c1b", "position": [ 101.31096988518799, 31.758069533041372, -0.3554368269705526 ], "text": "蒲西乡", "type": "ESTextLabel" } }, { "children": [], "name": "宗科乡", "sceneObj": { "name": "宗科乡", "fontSize": 18, "id": "6bdf556e-ec0b-4d53-9dca-8e514b6a764b", "position": [ 101.06397423616474, 31.735457402324926, 0.07002222182983407 ], "text": "宗科乡", "type": "ESTextLabel" } }, { "children": [], "name": "石里乡", "sceneObj": { "name": "石里乡", "fontSize": 18, "id": "69e75356-3a34-4b20-9175-6d00ba6cf9e3", "position": [ 101.11188855211684, 31.894306648044548, -0.1276350955737183 ], "text": "石里乡", "type": "ESTextLabel" } }, { "children": [], "collapsed": true, "name": "吾伊乡", "sceneObj": { "name": "吾伊乡", "fontSize": 18, "id": "50e3f588-de57-4b40-b341-3e9d3af1a067", "position": [ 101.00075510206388, 32.049643388354454, -0.395072220384877 ], "text": "吾伊乡", "type": "ESTextLabel" } }, { "children": [], "collapsed": true, "name": "中壤塘镇", "sceneObj": { "name": "中壤塘镇", "fontSize": 18, "id": "65bfbfbe-53bc-47e7-8ab1-0ea63e26fa03", "position": [ 101.22475720742355, 32.18666584333736, 0.04699996893255173 ], "text": "中壤塘镇", "type": "ESTextLabel" } }, { "children": [], "name": "上壤塘乡", "sceneObj": { "name": "上壤塘乡", "fontSize": 18, "id": "f96054b5-40ae-4f06-810a-a484bbd2a468", "position": [ 101.3462915036391, 32.24639772361728, 0.14415805983716212 ], "text": "上壤塘乡", "type": "ESTextLabel" } }, { "children": [], "collapsed": true, "name": "尕多乡", "sceneObj": { "name": "尕多乡", "fontSize": 18, "id": "4fd1ba95-41f9-4c64-add2-0f63e9f9a3ac", "position": [ 101.14077716155053, 32.39338163805662, -0.15134801435379808 ], "text": "尕多乡", "type": "ESTextLabel" } }, { "children": [], "name": "南木达镇", "sceneObj": { "name": "南木达镇", "fontSize": 18, "id": "facf3d08-f1bb-4d0c-a75b-6be49850cfa2", "position": [ 101.02613744401968, 32.410023040030836, -0.22438337926771446 ], "text": "南木达镇", "type": "ESTextLabel" } }, { "children": [], "name": "茸木达乡", "sceneObj": { "name": "茸木达乡", "fontSize": 18, "id": "b523be1e-be6d-4417-a505-efcdbf155825", "position": [ 101.04638705887756, 32.51416480265879, 0.045464148752334864 ], "text": "茸木达乡", "type": "ESTextLabel" } }, { "children": [], "collapsed": true, "name": "上杜柯乡", "sceneObj": { "name": "上杜柯乡", "fontSize": 18, "id": "d074f6cd-cb95-4b22-a95f-95fb0823ad63", "position": [ 100.74553605818096, 32.453110335930056, -0.2785384109580931 ], "text": "上杜柯乡", "type": "ESTextLabel" } }, { "children": [], "collapsed": true, "name": "岗木达镇", "sceneObj": { "name": "岗木达镇", "fontSize": 18, "id": "b538b18b-d8aa-4632-bc78-496b9e3ce4a6", "position": [ 100.88045514041491, 32.23970992447721, -0.10215226380875761 ], "text": "岗木达镇", "type": "ESTextLabel" } } ], "collapsed": true, "name": "boundaryName" }, { "children": [ { "children": [], "collapsed": true, "name": "测试", "sceneObj": { "mode": "SquareV03", "socketName": "站点1", "name": "测试", "style": { "textBoxAlign": "start", "textOffset": [ 10, 2 ], "textBoxShow": true, "fontSize": 20, "textBoxMode": "default", "imageSize": [ 60, 60 ], "textBackgroundSize": [ 120, 30 ], "textBackgroundColor": [ 1, 1, 1, 0 ], "textColor": [ 1, 1, 1, 1 ], "textBoxOffset": [ 40, 0 ] }, "id": "e79ece31-eebb-4eff-877f-211e3a7e7070", "allowPicking": true, "position": [ 100.78260332965166, 32.1923681024918, -0.0021763699037819872 ], "actorTag": "61f09fa0-2820-11f1-a1d0-a736ecedf777", "type": "ESPoi2D" } } ], "name": "sites" } ]

3、场景对象创建与销毁

他们有官方示例,大家可自行参考,我是根据实际开发需求总结的,无论是添加影像、地形、3DTileset以及各种类型的标记等等

#创建方式一、拿到数据里的sceneObj根据createSceneObjectFromJson创建对象 const createSceneObjectFromJson = (name, data) => { if (data.length === 0) return; if (!objm.savedSceenObjects[name]) { objm.savedSceenObjects[name] = []; } for (let datum of data) { const sceneObject = objm.createSceneObjectFromJson(datum.sceneObj); sceneObject.allowPicking = true; if (!objm.savedSceenObjects[name]) { objm.savedSceenObjects[name] = []; } objm.savedSceenObjects[name].push(sceneObject); sceneObject.pickedEvent.don((e) => { console.log("FromJson", e?.sceneObject.json); }) } } 创建方式二、拿到的数据转为GeoJson,使用矢量的方式创建 const formatGeoJsonData = (type, data) => { let result = { "type": "FeatureCollection", "features": [] } data.forEach(item => { const sceneObj = item?.sceneObj; if (!sceneObj) return; const {position, flyToParam} = sceneObj; sceneObj.type = type; if (position && flyToParam) { sceneObj.flyInParam = objm.activeViewer.transformFlyParam(position, flyToParam) } result.features.push({ "type": "Feature", "geometry": { "type": "Point", "coordinates": position }, "properties": { "sceneObj": sceneObj, "name": sceneObj.name, "type": type } }) }); return result; } const createSceneObjectFromGeoJson = (name, data) => { const sceneObject1 = objm.createSceneObject('ESGeoJson'); sceneObject1.url = data; sceneObject1.textProperty = "name"; sceneObject1.allowPicking = true; sceneObject1.textFontSize = 26; sceneObject1.imageSize = [53, 57]; sceneObject1.textOffset = [50, -70] sceneObject1.textAnchor = [0.5, 1] if (name === "boundaryName") { sceneObject1.url = "https://earth3d.alink.link:30002/defaultIcon.png" } if (!objm.savedSceenObjects[name]) { objm.savedSceenObjects[name] = []; } objm.savedSceenObjects[name].push(sceneObject1); sceneObject1.pickedEvent.don((e) => { console.log('FromGeo', e.geojsonPickInfo) }) } #销毁对象 objm.destroySceneObject(sceneObject) #对象显示隐藏 const visible=true/false; sceneObject.show = visible;

三、 高频“踩坑”与解决方案

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/27 8:20:17

3步搞定Windows驱动混乱:DriverStoreExplorer让你的系统重获新生

3步搞定Windows驱动混乱&#xff1a;DriverStoreExplorer让你的系统重获新生 【免费下载链接】DriverStoreExplorer Driver Store Explorer 项目地址: https://gitcode.com/gh_mirrors/dr/DriverStoreExplorer 还在为Windows系统越来越慢而烦恼吗&#xff1f;C盘空间莫名…

作者头像 李华
网站建设 2026/5/27 8:18:24

基于本地LLM与MCP协议构建隐私优先的医疗AI工具集实战

1. 项目概述&#xff1a;构建一个隐私至上的本地医疗AI助手作为一名长期在软件工程和AI应用领域摸爬滚打的开发者&#xff0c;我见过太多关于医疗AI的“炫酷”演示&#xff0c;它们往往都有一个致命的共同点&#xff1a;将患者的敏感数据发送到云端进行处理。这不仅仅是一个糟糕…

作者头像 李华
网站建设 2026/5/27 8:14:22

Flutter+Supabase构建AI学习平台:3天完成54家服务商整合

1. 项目概述&#xff1a;一个为AI学习者打造的“一站式大学”如果你最近也在关注AI领域&#xff0c;大概率会和我有同样的感受&#xff1a;这个领域的变化速度&#xff0c;已经快到让人喘不过气。今天OpenAI发布了新模型&#xff0c;明天Anthropic更新了上下文窗口&#xff0c;…

作者头像 李华
网站建设 2026/5/27 8:14:11

AI智能体在CI/CD流水线中实现自动化代码审查与测试生成

1. 项目概述&#xff1a;当AI智能体在CI流水线中编写生产代码最近在跟几个团队聊自动化测试和持续集成&#xff08;CI&#xff09;的实践时&#xff0c;发现一个挺有意思的趋势&#xff1a;大家不再满足于让AI只是写写单元测试或者生成一些样板代码&#xff0c;而是开始尝试让A…

作者头像 李华
网站建设 2026/5/27 8:14:00

构建极简研究档案库:基于本地文件系统的学术知识聚合与检索方案

1. 项目概述&#xff1a;为什么现代学者需要一个“极简研究档案库”&#xff1f;如果你和我一样&#xff0c;长期在学术研究、技术调研或者深度写作的泥潭里打滚&#xff0c;一定对“知识碎片化”和“资料管理混乱”这两个痛点深有体会。我们每天在Zotero里存几十篇论文&#x…

作者头像 李华
网站建设 2026/5/27 8:13:10

AI代理支付自动化:Ramp CLI如何重构金融基础设施与威胁Visa模式

1. 项目概述&#xff1a;当AI代理拥有自己的命令行工具 最近&#xff0c;金融科技圈里一个名为Ramp的项目引起了我的注意。他们做了一件看似简单、实则可能撬动整个支付行业根基的事&#xff1a;为AI代理&#xff08;AI Agents&#xff09;构建了一个专用的命令行工具&#xff…

作者头像 李华