开源!在goview中实现cesium的低代码可视化编辑

大家好,我是日拱一卒的攻城师不浪,专注可视化、数字孪生、前端、nodejs、AI学习、GIS等学习沉淀,这是2024年输出的第19/100篇文章;

前言

前阵子写了一篇goview二开的文章教程,很多小伙伴留言对goview嵌套cesium并实现cesium的低代码可视化编辑很感兴趣,今天我就来将我的实现和思路分享给大家,希望能够对大家有帮助。

对goview二开还不怎么了解的朋友可以先看我上篇文章:理解了GoView低代码平台(可视化大屏)的开发原理,基于它进行了二开,因为二开需要对goview的整个框架有一个基础的了解才可。

goview嵌套cesium

安装cesium相关npm包

npm i cesium
npm i vite-plugin-cesium

vite-plugin-cesium:一个能够快速初始化cesium的vite插件,帮你省去cesium的繁琐配置;

vite.config.js中使用:

import cesium from 'vite-plugin-cesium';

plugins: [
    vue(),
    //...
    cesium(),
  ],

构建cesium组件基础文件配置

首先,在goview中,如果想新增一个cesium可视化组件,需要先在src/packages/components/Cesium/Base/CesiumBase目录下新建4个基础文件。

index.ts

cesium的可视化的配置文件

import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
import { CesiumCategoryEnum, CesiumCategoryEnumName } from '../../index.d'

export const CesiumBaseConfig: ConfigType = {
  key: 'CesiumBase',
  chartKey: 'VCesiumBase',
  conKey: 'VCCesiumBase',
  title: 'Cesium地球',
  category: CesiumCategoryEnum.CESIUM_BASE,
  categoryName: CesiumCategoryEnumName.CESIUM_BASE,
  package: PackagesCategoryEnum.CESIUM,
  chartFrame: ChartFrameEnum.COMMON,
  image: 'global.png',
  redirectComponent: `Cesium/Base/CesiumBase` // 跳转组件路径规则
}

index.vue

渲染cesium的vue文件。

<template>
  <div id="cesiumContainer"></div>
</template>
<script setup>
import * as Cesium from "cesium";
import { useChartDataFetch } from '@/hooks'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { ref, onMounted, toRefs, watch } from 'vue';
import { LocationEnum } from "./config"
import { getImg, resolveGeojson } from '@/api/path'

//token
Cesium.Ion.defaultAccessToken =
  "xxx";

const props = defineProps({
  chartConfig: {
    type: Object,
    required: true
  }
})
let {
  center,
  locationMode,
  markImgUrl,
  markGeojsonData
} = toRefs(props.chartConfig.option.viewOptions)
onMounted(() => {
  init();
});
let viewer = null
const stopWatch = watch(
  () => props.chartConfig.option.viewOptions,
  options => {
    init(options)
  },
  {
    deep: true
  }
)

const init = async (opts) => {
  if (opts) {
    // 当配置属性发生变化时触发当前分支
    const watchCenter = opts.center
    const {
      center: watch_center,
      locationMode: watch_locationMode,
      markImgUrl: watch_markImgUrl,
      geojsonFileName: watch_geojsonFileName,
    } = opts
    // 中心坐标
    const centerArr = watch_center?.split(",")
    if (centerArr?.length) {
      const arr = centerArr.map(Number)
      cameraLocation(watch_locationMode, arr)
    }
    // 若geojson文件存在
    if (watch_geojsonFileName) {
      try {
        const res = await resolveGeojson({
          fileName: watch_geojsonFileName
        })
        if (res?.data?.features?.length) {
          renderMarks(res.data.features, watch_markImgUrl)
        }
      } catch (err) {
        console.error(JSON.stringify(err))
      }

    }
  } else {
    // 初始化
    viewer = new Cesium.Viewer("cesiumContainer", {
      infoBox: false,
      timeline: false, // 是否显示时间线控件
    });
    // 去除logo
    viewer.cesiumWidget.creditContainer.style.display = "none";
    // 显示帧率
    viewer.scene.debugShowFramesPerSecond = true;
    viewer.scene.globe.depthTestAgainstTerrain = true;

    const centerArr = center.value?.split(",")
    if (centerArr?.length && locationMode.value) {
      const arr = centerArr.map(Number)
      cameraLocation(locationMode.value, arr)
    }

    // 调试使用
    window.viewer = viewer
    // 监听点击事件,拾取坐标
    const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    handler.setInputAction((e) => {
      const clickPosition = viewer.scene.camera.pickEllipsoid(e.position);
      const randiansPos = Cesium.Cartographic.fromCartesian(clickPosition);
      console.log(
        "经度:" +
        Cesium.Math.toDegrees(randiansPos.longitude) +
        ", 纬度:" +
        Cesium.Math.toDegrees(randiansPos.latitude)
      );
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
  }
};

const fly = (option) => {
  const lntlon = option.lntlon
  viewer.camera.flyTo({
    // 从以度为单位的经度和纬度值返回笛卡尔3位置。
    destination: Cesium.Cartesian3.fromDegrees(...lntlon, 40000),
    orientation: {
      // heading:默认方向为正北,正角度为向东旋转,即水平旋转,也叫偏航角。
      // pitch:默认角度为-90,即朝向地面,正角度在平面之上,负角度为平面下,即上下旋转,也叫俯仰角。
      // roll:默认旋转角度为0,左右旋转,正角度向右,负角度向左,也叫翻滚角
      heading: Cesium.Math.toRadians(0.0), // 正东,默认北
      pitch: Cesium.Math.toRadians(-90), // 向正下方看
      roll: 0.0, // 左右
    },
    duration: 3, // 飞行时间(s)
  })
}
const setView = (option) => {
  const lntlon = option.lntlon
  viewer.camera.setView({
    destination: Cesium.Cartesian3.fromDegrees(...lntlon, 40000),
  })
}
// 相机飞行代码
const cameraLocation = (locationMode = LocationEnum.FLY, lntlon) => {
  if (locationMode === LocationEnum.FLY) {
    fly({
      lntlon
    })
  } else if (locationMode === LocationEnum.SET_VIEW) {
    setView({ lntlon })
  }
}

const renderMarks = async (json, imgName) => {
  const imgRes = await getImg({
    fileName: imgName
  })
  let imgUrl = ""
  if (imgRes.data) {
    imgUrl = imgRes.data.imgUrl
  }
  if (json?.length) {
    formatJsonData(json, imgUrl)
  }
}
// 打点代码
const formatJsonData = (features, img) => {

  const billboardsCollection = viewer.scene.primitives.add(
    new Cesium.BillboardCollection()
  );
  for (let i = 0; i < features.length; i++) {
    const feature = features[i];
    const coordinates = feature.geometry.coordinates;
    const position = Cesium.Cartesian3.fromDegrees(
      coordinates[0],
      coordinates[1],
      100
    );
    const name = feature.properties.name;
    // 带图片的点
    billboardsCollection.add({
      image: img,
      width: 32,
      height: 32,
      position,
    });
  }
}
</script>
<style lang='scss' scoped>
#cesiumContainer {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
}
</style>

config.ts

这个文件是cesium的配置项文件,也就是这个模块的内容;

import { PublicConfigClass } from '@/packages/public'
import { CreateComponentType } from '@/packages/index.d'
import { CesiumBaseConfig } from './index'
import { chartInitConfig } from '@/settings/designSetting'
import cloneDeep from 'lodash/cloneDeep'
import dataJson from "./data.json"
import type { UploadFileInfo } from 'naive-ui'

// 定位模式
export enum LocationEnum {
  FLY = "fly", // 飞行
  SET_VIEW = "setView" // 直接定位
}

export const option = {
  dataset: dataJson,
  viewOptions: {
    center: "", // 中心点坐标
    locationMode: LocationEnum.FLY, // 定位模式
    markImgUrl: "",
    markImgList: [] as UploadFileInfo[],
    geojsonFileName: "", // geojson文件名
    geojsonFileList: [] as UploadFileInfo[], // json列表
    // markGeojsonData: {}, // mark geojson
  },
}

export default class Config extends PublicConfigClass implements CreateComponentType {
  public key = CesiumBaseConfig.key
  public attr = { ...chartInitConfig, w: 1000, h: 800, zIndex: -1 }
  public chartConfig = cloneDeep(CesiumBaseConfig)
  public option = cloneDeep(option)
}

config.vue

OK,配置项准备好之后,我们再继续画这个配置模块的vue页面。

<template>
  <collapse-item name="基础配置" :expanded="true" key="baseSetting">
    <setting-item-box name="中心坐标" :alone="true">
      <setting-item>
        <n-input placeholder="例如 120.36, 36.09" v-model:value="optionState.center" size="small"></n-input>
      </setting-item>
    </setting-item-box>
    <setting-item-box name="定位模式" :alone="true">
      <setting-item>
        <n-select v-model:value="optionState.locationMode" :options="locationOpts" />
      </setting-item>
    </setting-item-box>
    <setting-item-box>
      <n-button type="primary" @click="handleSave">保存</n-button>
    </setting-item-box>
  </collapse-item>
</template>

<script setup lang="ts">
import { PropType, reactive, nextTick, ref, computed } from 'vue'
import { option, LocationEnum } from './config'
import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
import cloneDeep from 'lodash/cloneDeep'

const props = defineProps({
  optionData: {
    type: Object as PropType<typeof option>,
    required: true
  }
})
const locationOpts = [
  {
    label: "飞行定位",
    value: LocationEnum.FLY,
  },
  {
    label: "直接定位",
    value: LocationEnum.SET_VIEW,
  }
]

// 此处深拷贝是为了当viewOptions是一个深层次的对象时,深层次的引用改变能够不直接影响props.optionData.viewOptions
const cloneViewOpts = cloneDeep(props.optionData.viewOptions)
const optionState = reactive(cloneViewOpts)

const handleSave = () => {
  props.optionData.viewOptions = Object.assign(props.optionData.viewOptions, optionState)
}
</script>

组件大类新增Cesium选项卡

也就是这里。

src/packages/index.ts中在大类组件列表中配置cesium模块:

// * 所有图表
export let packagesList: PackagesType = {
  //...
  [PackagesCategoryEnum.CESIUM]: CesiumList,
}

接着在src/views/chart/ContentCharts/hooks/useAside.hook.ts侧边栏渲染的hooks文件中配置cesium名称和图标:

const packagesListObj = {
  //...
  [PackagesCategoryEnum.CESIUM]: {
    icon: renderIcon(SpellCheckIcon),
    label: PackagesCategoryName.CESIUM
  }
}

OK,经过以上配置,我们其实就已经完成了cesium在goview中的嵌套,只不过还有一个问题是,当我点击页面预览的时候,在预览界面cesium没有渲染成功,报错:cesiumContainer这个dom元素找不到

cesium组件预览

玩过cesium的应该都知道,cesium在技术上的渲染底层是基于canvas,canvas的渲染需要基于基础元素,由于低代码平台的原理,所有组件渲染都是基于全局组件动态渲染去完成的。

而goview的预览是新开一个浏览器标签页,因此会导致cesium初始化渲染的时候无法找到cesiumContainer这个dom元素

因此,我们需要重点修改一下预览界面文件的动态组件component的配置,当检测到是cesium组件时,需要提前定义好元素id为cesiumContainer
src/views/preview/components/PreviewRenderList/index.vue

<!-- 此处chartkey为VCesiumBase时可以区分是因为在预览页面找不到对应container -->
    <component v-else :is="item.chartConfig.chartKey"
      :id="item.chartConfig.chartKey === 'VCesiumBase' ? 'cesiumContainer' : item.id" :chartConfig="item"
      :svgEl="item.props?.svgEl" :themeSetting="themeSetting" :themeColor="themeColor"
      :style="{ ...getSizeStyle(item.attr) }" v-on="useLifeHandler(item)"></component>

这样,问题就迎刃而解。

总结

如果想对goview进行二开,首先得必须了解整个项目架构,这个开源整体的代码质量还是不错的,文件分布比较合理清晰,对于二开的小伙伴来说非常友好,其实跟平常自己新封装一个组件的难度基本无差。

我把二开的代码已上传到github,有需要的小伙伴自取,如果觉得有帮助请star,以鼓励和支持我持续开源下去。

【开源地址】:https://github.com/tingyuxuan2302/goview-fe

有需要进技术产品开发交流群(可视化&GIS)可以加我:brown_7778,也欢迎数字孪生可视化领域的交流合作。

最后,如果觉得文章对你有帮助,也希望可以一键三连👏👏👏,支持我持续开源和分享~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/727707.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

c++学习-----内存管理

1. C/C内存分布 我们先来看下面的一段代码和相关问题 答案揭晓&#xff1a; 这里很多人会误认为*char2在常量区&#xff0c;这其实是错误的 因为&#xff1a; 首先在内存字符常量区分配一块内存空间放下”abcd\0”&#xff0c;然后在栈中分配一块连续的内存空间&#xff0c;…

Qt扩展-轻量数学公式计算

轻量数学公式计算 一、概述二、代码结构三、简单使用四、解析支持1. 操作数2. 运算符3. 括号 一、概述 这个是我写得简单的一个数学计算公式工具。easy-math-parser 是一个用C编写的简单工具包&#xff0c;支持四个操作字符串的计算工具&#xff0c;灵感来自Muparser。 在这个…

四川古力未来科技有限公司抖音小店解锁电商新机遇

在数字化浪潮席卷全球的今天&#xff0c;电商行业正以前所未有的速度蓬勃发展。四川古力未来科技有限公司紧跟时代步伐&#xff0c;积极拥抱变革&#xff0c;在抖音平台上开设小店&#xff0c;为品牌发展注入了新的活力。那么&#xff0c;四川古力未来科技有限公司抖音小店究竟…

go sync包(一) 互斥锁(一)

Sync包 sync包是go提供的用于并发控制的方法&#xff0c;类似于Java的JUC包。 &#xff08;图片来自《go设计与实现》&#xff09; 互斥锁 Mutex Go 语言的 sync.Mutex 由两个字段 state 和 sema 组成。 state 表示当前互斥锁的状态。sema 是用于控制锁状态的信号量。 ty…

同城跑腿多合一系统源码小程序支持安卓+IOS+公众号+H5

&#x1f680; 同城跑腿多合一小程序&#xff1a;便捷生活新选择 &#x1f4a8; 一、引言&#xff1a;走进便捷新纪元 在这个快节奏的现代生活中&#xff0c;时间成了最宝贵的财富。而“同城跑腿多合一小程序”正是为了满足大家对于便捷、高效生活的追求而诞生的。它不仅是一款…

6.2 事件的创建,修改和删除

6.2.1 事件的概述 事件(Event)是在指定时刻才被执行的过程式数据库对象。 事件通过MySQL中一个很有特色的功能模块——事件调度器(Event Scheduler)进行监视&#xff0c;并确定其是否需要被调用。 MySQL的事件调度器可以精确到每秒钟执行一个任务&#xff0c;比操作系统的计…

python离线安装第三方库、及其依赖库(单个安装,非批量移植)

文章目录 1.外网下载第三方库、依赖库2.内网安装第三方库3.补充附录内网中离线安装python第三方库,这时候只能去外网手动下载第三方库,再传回内网进行安装。 问题是python第三方库往往有其前置依赖包,你很难清楚某个第三方库依赖的是哪些依赖包,更难受的是依赖包可能还有其…

Samtec制造理念系列二 | 差异变量管理的意义与挑战

【摘要/前言】 制造高端电子产品是非常复杂精密的过程。制作用于演示或原型的一次性样品可能具有挑战性&#xff0c;但真正的挑战在于如何以盈利的方式持续生产。 这就是Samtec风险投资研发工程总监Aaron Tucker在一次关于生产高密度微小型连接器的挑战的演讲中所强调的观点。…

深度学习之计算机视觉

神经网络简介 全连接层和卷积层的根本区别在于权重在中间层中彼此连接的方式。图5.1描述了全连接层或线性层是如何工作的。 在计算机视觉中使用线性层或全连接层的最大挑战之一是它们丢失了所有空间信息&#xff0c;并且就全连接层使用的权重数量而言复杂度太高。例如&#xf…

中文分词词云图

目录 一、分词1、分词方式方法2、分词优缺点 二、jieba使用示例1、引入库2、切分模式3、加载自定义字典 三、词的可视化1、读取数据2、数据处理3、统计词频4、去除停用词5、词云图1、pyecharts绘图2、WordCloud绘图 一、分词 1、分词方式方法 以构词规则为出发点的规则分词 全…

平安养老险黄山中支开展“反洗钱电影送下乡”活动

为不断增强反洗钱教育宣传的精准性和有效性&#xff0c;提升乡村群众的洗钱风险防范意识&#xff0c;6月18日&#xff0c;在中国人民银行黄山市分行的部署和指导下&#xff0c;平安养老保险股份有限公司&#xff08;以下简称“平安养老险”&#xff09;黄山中心支公司、平安人寿…

服务器主机托管服务内容科普

在现代信息技术快速发展的背景下&#xff0c;服务器主机托管服务已成为众多企业、机构和个人不可或缺的一部分。本文将为您详细科普服务器主机托管服务的内容&#xff0c;帮助您更好地理解和选择适合自己的托管方案。 一、硬件与基础设施 服务器主机托管服务首先提供了硬件和网…

HTML静态网页成品作业(HTML+CSS)—— 家乡成都介绍网页(4个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有4个页面。 二、作品演示 三、代…

数据库异常恢复2-备份文件恢复(快速恢复的手动启动方式)

(四) 备份文件备份恢复的概念 本次所说的数据恢复有异于数据的导入导出 1. 备份工具 gbase8s数据库提供了两种工具进行完成系统物理备份、逻辑日志备份和系统恢复&#xff1a;ontape和onbar ontape&#xff1a;提供了基本的系统物理备份、日志备份和恢复能力&#xff0c;其…

多目标跟踪中用到的求解线性分配问题(Linear Assignment Problem,LAP)Python

多目标跟踪中用到的求解线性分配问题&#xff08;Linear Assignment Problem&#xff0c;LAP&#xff09;Python flyfish 如果想看 C版本的&#xff0c;请点这里。 线性分配问题&#xff08;LAP&#xff0c;Linear Assignment Problem&#xff09;是一个经典的优化问题&…

java实现分类下拉树,点击时对应搜索---后端逻辑

一直想做分类下拉&#xff0c;然后选择后搜索的页面&#xff0c;正好做项目有了明确的需求&#xff0c;查找后发现el-tree的构件可满足需求&#xff0c;数据要求为&#xff1a;{ id:1, label:name, childer:[……] }形式的&#xff0c;于是乎&#xff0c;开搞&#xff01; 一…

如何下载DVS Gesture数据集?解决tonic.datasets.DVSGesture错误HTTP Error 403: Forbidden

1 问题 DVSGesture数据集是一个专注于动态视觉传感&#xff08;Dynamic Vision Sensor, DVS&#xff09;技术的数据集&#xff0c;它包含了基于事件的图像记录&#xff0c;用于手势识别任务。DVSGesture数据集由一系列动态图像组成&#xff0c;这些图像是通过动态视觉传感器捕…

免费分享:GIS插件-ARCGIS一键拓扑(模型构建器)(附下载方法)

工具详情 ARCGIS一键拓扑(模型构建器)&#xff0c;可直接在ArcGIS中安装使用。 可试用本工具进行数据拓扑检查。 下载方法 下载地址&#xff1a;https://open.geovisearth.com/service/resource/150下载流程&#xff1a;点击上面的下载地址&#xff0c;打开数字地球开放平台…

数据结构-绪论

目录 前言一、从问题到程序二、数据结构的研究内容三、理解数据结构3.1 数据3.2 结构3.2.1 逻辑结构的分类3.2.2 存储结构的分类 3.3 数据结构 四、数据类型和抽象数据类型4.1 抽象数据类型的定义格式4.2 抽象数据类型的实现 总结 前言 本篇文章先介绍数据结构的研究内容&…

php反序列化的一些知识

问题 <?php $raw O:1:"A":1:{s:1:"a";s:1:"b";}; echo serialize(unserialize($raw)); //O:1:"A":1:{s:1:"a";s:1:"b";}?> php反序列化的时按理说找不到A这个类&#xff0c;但是他没有报错&#xff0c;…