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

news2024/10/23 2:45:19

大家好,我是日拱一卒的攻城师不浪,专注可视化、数字孪生、前端、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,也欢迎数字孪生可视化领域的交流合作。

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Python日志模块全面指南】:记录每一行代码的呼吸,掌握应用程序的脉搏

文章目录 &#x1f680;一、了解日志&#x1f308;二、日志作用&#x1f308;三、了解日志模块⭐四、日志级别&#x1f4a5;五、记录日志-基础❤️六、记录日志-处理器handler&#x1f3ac;七、记录日志-格式化记录☔八、记录日志-配置logger&#x1f44a;九、流程梳理 &#x…

基于Windows API DialogBox的对话框

在C中&#xff0c;DialogBox函数是Windows API的一部分&#xff0c;它用于在Win32应用程序中创建并显示一个模态对话框。DialogBox函数是USER32.DLL中的一个导出函数&#xff0c;因此你需要在你的C Win32应用程序中链接到这个库。 #include "framework.h" #include …

劲爆!Kimi月之暗面可以接入微信,智能升级, 打造个性多Agent(二)

前言 在当今这个快速发展的AI时代&#xff0c;抖音推出了一个名为“扣子Coze”的工具&#xff0c;帮助用户快速、低门槛地搭建属于自己的AI机器人。本文将详细介绍如何使用扣子Coze配置自己的AI Agent&#xff0c;并展示其在多个平台上的应用。 如何使用多个Agent 搭建更加智…

【可控图像生成系列论文(二)】MimicBrush 港大、阿里、蚂蚁集团合作论文解读2

【可控图像生成系列论文&#xff08;一&#xff09;】简要介绍了论文的整体流程和方法&#xff0c;本文则将就整体方法、模型结构、训练数据和纹理迁移进行详细介绍。 1.整体方法 MimicBrush 的整体框架如下图所示。为了实现模仿编辑&#xff0c;作者设计了一种具有双扩散模型…

2024年6月20日 (周四) 叶子游戏新闻

超市播音系统: 定时播放不同音乐 强制卸载软件: 一款强制卸载软件 免费多人沙盒游戏《宝藏世界》推出更新“潮起潮落”&#xff0c;带来全新克苏鲁风冒险准备好迎接一场超凡的冒险吧&#xff0c;MMORPG发行商gamigo宣布《宝藏世界》的最新更新&#xff1a;“潮起潮落”。这次更…

我的创作纪念日--码农阿豪

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

C语言程序设计-7 数组

在程序设计中&#xff0c;为了处理方便&#xff0c;把具有相同类型的若干变量按有序的形式组织起来。这些按序排列的同类数据元素的集合称为数组。在&#xff23;语言中&#xff0c;数组属于构造数据类型。一个数 组可以分解为多个数组元素&#xff0c;这些数组元素可以是基本数…

性能测试-性能监控分析与调优(三)《实战》

性能监控 使用命令监控 cpu瓶颈分析 top命令 在进行性能测试时使用top命令&#xff0c;界面如下 上图可以看出 CPU 概况区&#xff1a; %Cpu(s): us&#xff08;用户进程占用CPU的百分比&#xff09;, 和 sy&#xff08;系统进程占用CPU的百分比&#xff09; 的数值很高…

Mybatis Plus 详解 IService、BaseMapper、自动填充、分页查询功能

结构直接看目录 前言 MyBatis-Plus 是一个 MyBatis 的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 愿景 我们的愿景是成为 MyBatis 最好的搭档&#xff0c;就像 魂斗罗 中的 1P、2P&#xff0c;基友搭配&#xff0c;效…

【前端技巧】css篇

利用counter实现计数器 counter-reset&#xff1a;为计数器设置名称&#xff0c;语法如下&#xff1a; counter-rese: <idntifier><integer>第一个参数为变量名称&#xff0c;第二个参数为初始值&#xff0c;默认为0 counter-increment&#xff1a;设置计数器增…

Redis 主从复制+哨兵+集群

Redis复制 查看docker 容器 ip docker inspect 容器id | grep IPAddressdocker inspect -f{{.Name}} {{.NetworkSettings.IPAddress}} $(docker ps -aq)修改配置文件 初始配置文件见 > redis.conf 远程访问 bind 0.0.0.0protected-mode no 主机设置 replicaof 172.17.0.…

fastadmin多语言切换设置

fastadmin版本&#xff1a;1.4.0.20230711 以简体&#xff0c;繁体&#xff0c;英文为例 一&#xff0c;在application\config.php 里开启多语言 // 是否开启多语言lang_switch_on > true, // 允许的语言列表allow_lang_list > [zh-cn, en,zh-tw], 二…

文本高效管理神器:支持自定义行数拆分,轻松实现批量高效编辑与管理新体验

在信息爆炸的时代&#xff0c;文本处理成为了我们日常工作中不可或缺的一部分。然而&#xff0c;面对大量的文本数据&#xff0c;如何高效地进行编辑和管理&#xff0c;却成为了许多人头疼的问题。现在&#xff0c;有了我们的文本批量高效编辑管理工具&#xff0c;一切将变得简…

RS485中继器的作用你还不知道?

RS485是一种串行通信协议&#xff0c;支持设备间长距离通信。RS485中继器则像“传声筒”&#xff0c;能放大衰减信号&#xff0c;延长通信距离&#xff0c;隔离噪声&#xff0c;扩展分支。在实际场景中&#xff0c;如工厂内&#xff0c;通过中继器可确保控制室与远距离机器间通…

虚拟现实环境下的远程教育和智能评估系统(十一)

视频帧画面知识点区域划分 知识点区域精确分割技术: 在深度学习检测模型结果基础上使用基于交并比&#xff08;IoU&#xff09;阈值的目标合并算法&#xff0c;合并过度重合目标区域面积&#xff0c;实现知识点区域精确分割 多模态知识点内容匹配策略: 图像&#xff1a;利用…

Linux驱动开发-01配置开发环境

一、配置网络环境 使用桥接网卡时 Ubuntu 就是使用一个真实的网卡 &#xff1a;开发板的网线也连接到这个真实的网卡上&#xff0c;这样 Windows 、 Ubuntu 、开发板就都可以用过这个网卡互通了。 NAT 网卡&#xff1a; Ubuntu 通过它上网&#xff0c;只要 Windows 能上网&…

idea 配置文件中文乱码

再进行springboot项目开发时发现新建的配置文件中文注释乱码&#xff0c;如下: 处理办法: 1、打开idea&#xff0c;在 File 中找到 Settings,如下图 2、搜索 encodings 找到 File Encodings&#xff0c;如下图 3、将上图中圈上的地方全部改为 UTF-8 编码最后点击 Apply 应用即…

鸿蒙Harmony实战—通过登录Demo了解ArkTS

ArkTS是HarmonyOS优选的主力应用开发语言。ArkTS围绕应用开发在TypeScript&#xff08;简称TS&#xff09;生态基础上做了进一步扩展&#xff0c;继承了TS的所有特性&#xff0c;是TS的超集。 ArkTS在TS的基础上主要扩展了如下能力&#xff1a; 基本语法&#xff1a;ArkTS定义…

信息学奥赛初赛天天练-30CSP-J2022完善程序-结构体构造函数初始化、auto关键字、连通块、洪水填充算法实战

PDF文档公众号回复关键字:20240620 2022 CSP-J 阅读程序2 完善程序 (单选题 &#xff0c;每小题3分&#xff0c;共30分) 2 (洪水填充) 现有用字符标记像素颜色的8 * 8图像。颜色填充操作描述如下&#xff1a;给定起始像素的位置和待填充的颜色&#xff0c;将起始像素和所有可…

山东华素制药有限公司:素心做药,感恩回报

在山东威海这片美丽的土地上,有一颗璀璨的明珠——山东华素制药有限公司。自2013年成立以来,这家企业以其深厚的制药底蕴、卓越的研发实力和坚定的社会责任,赢得了社会各界的广泛赞誉。它不仅是化学药品制剂制造的佼佼者,更是“素心做药,感恩回报”的典范。 一、素心做药,品质为…