DESKTOP-CRQ4N2U\jintian 2 年之前
父节点
当前提交
a5c9ca82a5
共有 74 个文件被更改,包括 18602 次插入260 次删除
  1. 1 0
      .env
  2. 49 48
      index.html
  3. 3 180
      src/App.vue
  4. 8 0
      src/assets/styles/main.css
  5. 187 0
      src/components/App.vue
  6. 20 17
      src/jtMap3d/Widgets/StatusBar.js
  7. 2 11
      src/jtMap3d/Widgets/base.js
  8. 1 0
      src/jtMap3d/index.js
  9. 33 4
      src/main.js
  10. 40 0
      src/plugins/elementPlus.ts
  11. 43 0
      src/router/index.js
  12. 65 0
      src/views/main/index.vue
  13. 63 0
      src/views/map/analysis/index.vue
  14. 80 0
      src/views/map/analysis/tools/FloodTool.vue
  15. 61 0
      src/views/map/analysis/tools/MeasureTool.vue
  16. 72 0
      src/views/map/analysis/tools/VideoOn.vue
  17. 97 0
      src/views/map/analysis/tools/ViewshedTool.vue
  18. 38 0
      src/views/map/analysis/tools/VisibilityTool.vue
  19. 11 0
      src/views/map/analysis/tools/floodTool/index.d.ts
  20. 75 0
      src/views/map/analysis/tools/floodTool/index.ts
  21. 39 0
      src/views/map/analysis/tools/measureTool/globeTooltip.ts
  22. 552 0
      src/views/map/analysis/tools/measureTool/index.ts
  23. 64 0
      src/views/map/analysis/tools/measureTool/infoBox.ts
  24. 113 0
      src/views/map/analysis/tools/viewshedTool/glsl.ts
  25. 292 0
      src/views/map/analysis/tools/viewshedTool/index.ts
  26. 147 0
      src/views/map/analysis/tools/visibilityTool/index.ts
  27. 61 0
      src/views/map/index.vue
  28. 179 0
      src/views/map/plot/graphicsDraw/areaDraw/algorithm.ts
  29. 1824 0
      src/views/map/plot/graphicsDraw/areaDraw/index.ts
  30. 756 0
      src/views/map/plot/graphicsDraw/arrowDraw/algorithm.ts
  31. 1661 0
      src/views/map/plot/graphicsDraw/arrowDraw/index.ts
  32. 40 0
      src/views/map/plot/graphicsDraw/lineDraw/algorithm.ts
  33. 844 0
      src/views/map/plot/graphicsDraw/lineDraw/index.ts
  34. 8 0
      src/views/map/plot/graphicsDraw/pointDraw/algorithm.ts
  35. 172 0
      src/views/map/plot/graphicsDraw/pointDraw/index.ts
  36. 272 0
      src/views/map/plot/index.ts
  37. 190 0
      src/views/map/plot/interface/index.ts
  38. 470 0
      src/views/map/plot/tools/index.ts
  39. 81 0
      src/views/map/plotTools/AreaMaterial.vue
  40. 81 0
      src/views/map/plotTools/ArrowMaterial.vue
  41. 268 0
      src/views/map/plotTools/DrawTool.vue
  42. 88 0
      src/views/map/plotTools/LineMaterial.vue
  43. 317 0
      src/views/map/plotTools/PointMaterial.vue
  44. 63 0
      src/views/map3d/analysis/index.vue
  45. 80 0
      src/views/map3d/analysis/tools/FloodTool.vue
  46. 61 0
      src/views/map3d/analysis/tools/MeasureTool.vue
  47. 72 0
      src/views/map3d/analysis/tools/VideoOn.vue
  48. 97 0
      src/views/map3d/analysis/tools/ViewshedTool.vue
  49. 38 0
      src/views/map3d/analysis/tools/VisibilityTool.vue
  50. 11 0
      src/views/map3d/analysis/tools/floodTool/index.d.ts
  51. 75 0
      src/views/map3d/analysis/tools/floodTool/index.ts
  52. 39 0
      src/views/map3d/analysis/tools/measureTool/globeTooltip.ts
  53. 552 0
      src/views/map3d/analysis/tools/measureTool/index.ts
  54. 64 0
      src/views/map3d/analysis/tools/measureTool/infoBox.ts
  55. 113 0
      src/views/map3d/analysis/tools/viewshedTool/glsl.ts
  56. 292 0
      src/views/map3d/analysis/tools/viewshedTool/index.ts
  57. 147 0
      src/views/map3d/analysis/tools/visibilityTool/index.ts
  58. 179 0
      src/views/map3d/index.vue
  59. 179 0
      src/views/map3d/plot/graphicsDraw/areaDraw/algorithm.ts
  60. 1824 0
      src/views/map3d/plot/graphicsDraw/areaDraw/index.ts
  61. 756 0
      src/views/map3d/plot/graphicsDraw/arrowDraw/algorithm.ts
  62. 1661 0
      src/views/map3d/plot/graphicsDraw/arrowDraw/index.ts
  63. 40 0
      src/views/map3d/plot/graphicsDraw/lineDraw/algorithm.ts
  64. 844 0
      src/views/map3d/plot/graphicsDraw/lineDraw/index.ts
  65. 8 0
      src/views/map3d/plot/graphicsDraw/pointDraw/algorithm.ts
  66. 172 0
      src/views/map3d/plot/graphicsDraw/pointDraw/index.ts
  67. 272 0
      src/views/map3d/plot/index.ts
  68. 190 0
      src/views/map3d/plot/interface/index.ts
  69. 470 0
      src/views/map3d/plot/tools/index.ts
  70. 81 0
      src/views/map3d/plotTools/AreaMaterial.vue
  71. 81 0
      src/views/map3d/plotTools/ArrowMaterial.vue
  72. 268 0
      src/views/map3d/plotTools/DrawTool.vue
  73. 88 0
      src/views/map3d/plotTools/LineMaterial.vue
  74. 317 0
      src/views/map3d/plotTools/PointMaterial.vue

+ 1 - 0
.env

@@ -0,0 +1 @@
+VITE_CESIUM_BASE_URL = './ThirdParty/Cesium-1.93'

+ 49 - 48
index.html

@@ -5,6 +5,7 @@
 		<link rel="icon" href="/favicon.ico" />
 		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
 		<title>睿图接口平台1.0</title>
+		<!-- 引入Cesium -->
 		<link rel="stylesheet" type="text/css" href="./public/ThirdParty/Cesium-1.93/Widgets/widgets.css" />
 		<script src="./ThirdParty/Cesium-1.93/Cesium.js"></script>
 		<!-- 引入生成热力图 -->
@@ -15,56 +16,56 @@
 		<div id="app"></div>
 		<script type="module" src="/src/main.js"></script>
 		<script type="text/javascript">
-				(function(designWidth, maxWidth) {
-					var doc = document,
-						win = window,
-						docEl = doc.documentElement,
-						remStyle = document.createElement("style"),
-						tid;
-		
-					function refreshRem() {
-						var width = docEl.getBoundingClientRect().width;
-						console.log('宽度', width)
-						maxWidth = maxWidth || 540;
-						width < 1000 && (width = 1000);
-						width > maxWidth && (width = 1920);
-						var rem = width * 1 / designWidth;
-						remStyle.innerHTML = 'html{font-size:' + rem + 'px;}';
-					}
-		
-					if (docEl.firstElementChild) {
-						docEl.firstElementChild.appendChild(remStyle);
-					} else {
-						var wrap = doc.createElement("div");
-						wrap.appendChild(remStyle);
-						doc.write(wrap.innerHTML);
-						wrap = null;
-					}
-					//要等 wiewport 设置好后才能执行 refreshRem,不然 refreshRem 会执行2次;
-					refreshRem();
-		
-					win.addEventListener("resize", function() {
-						clearTimeout(tid); //防止执行两次
+			(function(designWidth, maxWidth) {
+				var doc = document,
+					win = window,
+					docEl = doc.documentElement,
+					remStyle = document.createElement("style"),
+					tid;
+
+				function refreshRem() {
+					var width = docEl.getBoundingClientRect().width;
+					console.log('宽度', width)
+					maxWidth = maxWidth || 540;
+					width < 1000 && (width = 1000);
+					width > maxWidth && (width = 1920);
+					var rem = width * 1 / designWidth;
+					remStyle.innerHTML = 'html{font-size:' + rem + 'px;}';
+				}
+
+				if (docEl.firstElementChild) {
+					docEl.firstElementChild.appendChild(remStyle);
+				} else {
+					var wrap = doc.createElement("div");
+					wrap.appendChild(remStyle);
+					doc.write(wrap.innerHTML);
+					wrap = null;
+				}
+				//要等 wiewport 设置好后才能执行 refreshRem,不然 refreshRem 会执行2次;
+				refreshRem();
+
+				win.addEventListener("resize", function() {
+					clearTimeout(tid); //防止执行两次
+					tid = setTimeout(refreshRem, 300);
+				}, false);
+
+				win.addEventListener("pageshow", function(e) {
+					if (e.persisted) { // 浏览器后退的时候重新计算
+						clearTimeout(tid);
 						tid = setTimeout(refreshRem, 300);
-					}, false);
-		
-					win.addEventListener("pageshow", function(e) {
-						if (e.persisted) { // 浏览器后退的时候重新计算
-							clearTimeout(tid);
-							tid = setTimeout(refreshRem, 300);
-						}
-					}, false);
-		
-					if (doc.readyState === "complete") {
-						doc.body.style.fontSize = "16rem";
-					} else {
-						doc.addEventListener("DOMContentLoaded", function(e) {
-							doc.body.style.fontSize = "16rem";
-						}, false);
 					}
-				})(1920, 1920);
-			</script>
-		
+				}, false);
+
+				if (doc.readyState === "complete") {
+					doc.body.style.fontSize = "16rem";
+				} else {
+					doc.addEventListener("DOMContentLoaded", function(e) {
+						doc.body.style.fontSize = "16rem";
+					}, false);
+				}
+			})(1920, 1920);
+		</script>
+
 	</body>
 </html>
 <style>

+ 3 - 180
src/App.vue

@@ -1,187 +1,10 @@
-<script setup>
-	import CrMap from './components/CrMap.vue';
-</script>
-
 <template>
-	<div class="jt-layout">
-		<Layout>
-			<Header>
-				<Menu mode="horizontal" theme="dark" active-name="1">
-					<div class="jt-layout-logo">{{msg}}</div>
-					<div class="jt-layout-nav">
-						<MenuItem name="系统配置">
-						<Icon type="md-settings" />
-						<router-link target="_blank" to="/#"><span style="color: #fff;" title="系统配置">系统配置</span>
-						</router-link>
-						</MenuItem>
-					</div>
-					<div class="jt-layout-info">
-						<MenuItem>
-						<Icon type="md-time" />{{localtimeStr}}
-						</MenuItem>
-						<MenuItem>
-						<Icon type="md-person" />{{userName}}
-						</MenuItem>
-						<MenuItem @click="logout()">
-						<Icon type="md-power" />注销登录
-						</MenuItem>
-					</div>
-				</Menu>
-			</Header>
-		</Layout>
-	</div>
-
-	<CrMap />
+	<!-- 切换为路由显示 -->
+	<router-view></router-view>
 </template>
 
 <script>
-	//引入当前时间
-	import localtime from './assets/js/localtime.js';
-
 	export default {
 		name: 'App',
-
-		/* 数据 */
-		data() {
-			return {
-				msg: '这是Cesium-Map3d学习页面',
-				userName: "登录用户名", //登录用户名
-				timer: null, //定义一个定时器的变量	
-				localtimeStr: '0000-00-00', //当前时间
-			}
-		},
-
-		/* 方法 */
-		methods: {
-			/**
-			 * 退出登录
-			 **/
-			logout() {
-				//this.$router.push('/login')
-			},
-		},
-
-		/* 初始化 */
-		mounted() {
-			let _self = this;
-
-			_self.timer = setInterval(function() {
-				_self.localtimeStr = localtime.showLocale();
-			}, 1000);
-		},
-
-		/* 初始化html */
-		created() {
-
-		},
-
-		beforeDestroy() {
-			if (this.timer) {
-				clearInterval(this.timer); // 在Vue实例销毁前,清除我们的定时器
-			}
-		}
-	}
-</script>
-
-<style>
-	@font-face {
-		font-family: "iconfont";
-		/* Project id 1994416 */
-		src: url('@/assets/fonts/iconfont/iconfont.woff2') format('woff2'),
-			url('@/assets/fonts/iconfont/iconfont.woff') format('woff'),
-			url('@/assets/fonts/iconfont/iconfont.ttf') format('truetype');
-	}
-	
-	#app {
-		font-family: Avenir, Helvetica, Arial, sans-serif;
-		-webkit-font-smoothing: antialiased;
-		-moz-osx-font-smoothing: grayscale;
-		text-align: center;
-		color: rgb(255, 0, 0);
-		width: 100%;
-		height: 100%;
-	}
-
-	html,
-	body {
-		width: 100%;
-		height: 100%;
-		margin: 0px;
-		padding: 0px;
-	}
-
-	#navigationDiv {
-		position: absolute;
-		bottom: 150px;
-		left: 10px;
-		width: 100px;
-		height: 220px;
-	}
-
-	/*罗盘定位*/
-	/* .compass {
-		position: absolute;
-		left: 2%;
-		top: 2%;
-	} */
-
-	/*比例尺位置*/
-	/* .distance-legend {
-		position: absolute;
-		right: 2%;
-		bottom: 6%;
-	} */
-
-	/*缩放定位*/
-	/* .navigation-controls {
-		position: absolute;
-		left: 2%;
-		bottom: 10%;
-	} */
-</style>
-<style>
-	.jt-layout {
-		background: #23527c;
-		position: relative;
-		overflow: hidden;
-	}
-
-	.jt-layout-logo {
-		width: calc(33%);
-		background: #ffffff00;
-		border-radius: 3px;
-		float: left;
-		position: relative;
-		left: 0px;
-		color: #fff;
-		font-size: 20px;
-		text-align: left;
-		border: 0px solid red;
-	}
-
-	.jt-layout-nav {
-		width: 420px;
-		margin: 0 auto;
-		margin-left: 20px;
-		float: left;
-	}
-
-	.jt-layout-info {
-		width: auto;
-		float: right;
-		border: 0px solid red;
-	}
-
-	.jt-layout-info .ivu-menu-item {
-		padding: 0 10px;
-	}
-
-	.jt-layout .ivu-layout-header {
-		padding: 0 10px;
-		background-color: #23527c;
-	}
-
-	.jt-layout .ivu-menu-dark {
-		background: #23527c;
 	}
-</style>
+</script>

+ 8 - 0
src/assets/styles/main.css

@@ -0,0 +1,8 @@
+html,
+body,
+#app {
+  width: 100%;
+  height: 100%;
+  margin: 0;
+  padding: 0;
+}

+ 187 - 0
src/components/App.vue

@@ -0,0 +1,187 @@
+<script setup>
+	import CrMap from './components/CrMap.vue';
+</script>
+
+<template>
+	<div class="jt-layout">
+		<Layout>
+			<Header>
+				<Menu mode="horizontal" theme="dark" active-name="1">
+					<div class="jt-layout-logo">{{msg}}</div>
+					<div class="jt-layout-nav">
+						<MenuItem name="系统配置">
+						<Icon type="md-settings" />
+						<router-link target="_blank" to="/#"><span style="color: #fff;" title="系统配置">系统配置</span>
+						</router-link>
+						</MenuItem>
+					</div>
+					<div class="jt-layout-info">
+						<MenuItem>
+						<Icon type="md-time" />{{localtimeStr}}
+						</MenuItem>
+						<MenuItem>
+						<Icon type="md-person" />{{userName}}
+						</MenuItem>
+						<MenuItem @click="logout()">
+						<Icon type="md-power" />注销登录
+						</MenuItem>
+					</div>
+				</Menu>
+			</Header>
+		</Layout>
+	</div>
+
+	<CrMap />
+</template>
+
+<script>
+	//引入当前时间
+	import localtime from './assets/js/localtime.js';
+
+	export default {
+		name: 'App',
+
+		/* 数据 */
+		data() {
+			return {
+				msg: '这是Cesium-Map3d学习页面',
+				userName: "登录用户名", //登录用户名
+				timer: null, //定义一个定时器的变量	
+				localtimeStr: '0000-00-00', //当前时间
+			}
+		},
+
+		/* 方法 */
+		methods: {
+			/**
+			 * 退出登录
+			 **/
+			logout() {
+				//this.$router.push('/login')
+			},
+		},
+
+		/* 初始化 */
+		mounted() {
+			let _self = this;
+
+			_self.timer = setInterval(function() {
+				_self.localtimeStr = localtime.showLocale();
+			}, 1000);
+		},
+
+		/* 初始化html */
+		created() {
+
+		},
+
+		beforeDestroy() {
+			if (this.timer) {
+				clearInterval(this.timer); // 在Vue实例销毁前,清除我们的定时器
+			}
+		}
+	}
+</script>
+
+<style>
+	@font-face {
+		font-family: "iconfont";
+		/* Project id 1994416 */
+		src: url('@/assets/fonts/iconfont/iconfont.woff2') format('woff2'),
+			url('@/assets/fonts/iconfont/iconfont.woff') format('woff'),
+			url('@/assets/fonts/iconfont/iconfont.ttf') format('truetype');
+	}
+	
+	#app {
+		font-family: Avenir, Helvetica, Arial, sans-serif;
+		-webkit-font-smoothing: antialiased;
+		-moz-osx-font-smoothing: grayscale;
+		text-align: center;
+		color: rgb(255, 0, 0);
+		width: 100%;
+		height: 100%;
+	}
+
+	html,
+	body {
+		width: 100%;
+		height: 100%;
+		margin: 0px;
+		padding: 0px;
+	}
+
+	#navigationDiv {
+		position: absolute;
+		bottom: 150px;
+		left: 10px;
+		width: 100px;
+		height: 220px;
+	}
+
+	/*罗盘定位*/
+	/* .compass {
+		position: absolute;
+		left: 2%;
+		top: 2%;
+	} */
+
+	/*比例尺位置*/
+	/* .distance-legend {
+		position: absolute;
+		right: 2%;
+		bottom: 6%;
+	} */
+
+	/*缩放定位*/
+	/* .navigation-controls {
+		position: absolute;
+		left: 2%;
+		bottom: 10%;
+	} */
+</style>
+<style>
+	.jt-layout {
+		background: #23527c;
+		position: relative;
+		overflow: hidden;
+	}
+
+	.jt-layout-logo {
+		width: calc(33%);
+		background: #ffffff00;
+		border-radius: 3px;
+		float: left;
+		position: relative;
+		left: 0px;
+		color: #fff;
+		font-size: 20px;
+		text-align: left;
+		border: 0px solid red;
+	}
+
+	.jt-layout-nav {
+		width: 420px;
+		margin: 0 auto;
+		margin-left: 20px;
+		float: left;
+	}
+
+	.jt-layout-info {
+		width: auto;
+		float: right;
+		border: 0px solid red;
+	}
+
+	.jt-layout-info .ivu-menu-item {
+		padding: 0 10px;
+	}
+
+	.jt-layout .ivu-layout-header {
+		padding: 0 10px;
+		background-color: #23527c;
+	}
+
+	.jt-layout .ivu-menu-dark {
+		background: #23527c;
+	}
+</style>

+ 20 - 17
src/jtMap3d/Widgets/StatusBar.js

@@ -258,26 +258,29 @@ class StatusBar {
 		var ray = this._viewer.scene.camera.getPickRay(screenPoint);
 		/* 找到射线与渲染的地球表面之间的交点 */
 		var position = this._viewer.scene.globe.pick(ray, this._viewer.scene);
-		/* 获取地理位置的制图表达 */
-		var cartographic = Cesium.Ellipsoid.WGS84.cartesianToCartographic(position);
-		cartographic = Cesium.Cartographic.fromCartesian(position);
-		/* 查询屏幕位置的要素 */
-		var feature = this._viewer.scene.pick(screenPoint);
-		if (feature === undefined && Cesium.defined(cartographic)) {
-			lng = this._arcToDegree(cartographic.longitude);
-			lat = this._arcToDegree(cartographic.latitude);
-			height = cartographic.height;
-		} else {
-			var cartesian = this._viewer.scene.pickPosition(screenPoint);
-			if (Cesium.defined(cartesian)) {
-				var cartographic = Cesium.Cartographic.fromCartesian(cartesian);
-				if (Cesium.defined(cartographic)) {
-					lng = this._arcToDegree(cartographic.longitude);
-					lat = this._arcToDegree(cartographic.latitude);
-					height = cartographic.height;
+		if (position) {
+			/* 获取地理位置的制图表达 */
+			var cartographic = Cesium.Ellipsoid.WGS84.cartesianToCartographic(position);
+			cartographic = Cesium.Cartographic.fromCartesian(position);
+			/* 查询屏幕位置的要素 */
+			var feature = this._viewer.scene.pick(screenPoint);
+			if (feature === undefined && Cesium.defined(cartographic)) {
+				lng = this._arcToDegree(cartographic.longitude);
+				lat = this._arcToDegree(cartographic.latitude);
+				height = cartographic.height;
+			} else {
+				var cartesian = this._viewer.scene.pickPosition(screenPoint);
+				if (Cesium.defined(cartesian)) {
+					var cartographic = Cesium.Cartographic.fromCartesian(cartesian);
+					if (Cesium.defined(cartographic)) {
+						lng = this._arcToDegree(cartographic.longitude);
+						lat = this._arcToDegree(cartographic.latitude);
+						height = cartographic.height;
+					}
 				}
 			}
 		}
+		
 		/* 返回结果 */
 		return {
 			lng: lng,

+ 2 - 11
src/jtMap3d/Widgets/base.js

@@ -2,19 +2,10 @@
 // import * as Cesium from 'Cesium';
 
 //状态栏
-import StatusBar from "./StatusBar.js";
-
-import {
-	getHeigthByPointsMostDetailed
-} from "./common/common.js";
+// import StatusBar from "./StatusBar.js";
 
 import '../Assets/styles/base.css';
 
-//定义地图自旋相关参数
-var spinEntity, spinCentreX, spinCentreY, spinCentreZ;
-//是否自旋
-var isSpin = false;
-
 /**
  * 场景视图
  */
@@ -69,7 +60,7 @@ class jtMap3d {
 		//默认天空盒子
 		this._defaultSkyBox = this._viewer.scene.skyBox;
 
-		this.statusBar = new StatusBar(this._viewer);
+		// this.statusBar = new StatusBar(this._viewer);
 
 		console.log(Cesium.buildModuleUrl.getCesiumBaseUrl());
 	}

+ 1 - 0
src/jtMap3d/index.js

@@ -2,6 +2,7 @@ export const VERSION = '2.01';
 
 //大球初始化
 export { default as jtMap3d } from './Widgets/base.js';
+export { default as StatusBar } from './Widgets/StatusBar.js';
 //加载各类图层服务
 export { default as layer } from "./Widgets/layer.js";
 //加载浮动图层

+ 33 - 4
src/main.js

@@ -1,21 +1,50 @@
 import {
 	createApp
-} from 'vue'
-import App from './App.vue'
+} from 'vue';
+
+import App from './App.vue';
+import router from "./router";
+
+import "./assets/styles/main.css";
 
 // View UI Plus 是 View Design 设计体系中基于 Vue.js 3 的一套 UI 组件库,主要用于企业级中后台系统。
 import ViewUIPlus from 'view-ui-plus'
 import 'view-ui-plus/dist/styles/viewuiplus.css'
 
+//全局引入ElementPlus,无需按需引入
+// import ElementPlus from 'element-plus';
+// import * as ElementPlusIconsVue from '@element-plus/icons-vue'
+//按需导入Element Plus组件
+import {
+	components
+} from "@/plugins/elementPlus";
 // 引入elementUI 样式
-import ElementPlus from 'element-plus';
 import 'element-plus/dist/index.css';
 
+
+
 import $ from 'jquery'
 window.jquery = window.$ = $
 
+// Object.defineProperty(globalThis, "CESIUM_BASE_URL", {
+// 	value: import.meta.env.VITE_CESIUM_BASE_URL,
+// });
+
 /* 定义 */
 let app = createApp(App);
+app.use(router);
+
 app.use(ViewUIPlus);
-app.use(ElementPlus);
+
+// app.use(ElementPlus);
+// 按需导入Element Plus组件
+components.forEach((component) => {
+	app.component(component.name, component);
+});
+
+//
+// for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+//   app.component(key, component)
+// }
+
 app.mount('#app');

+ 40 - 0
src/plugins/elementPlus.ts

@@ -0,0 +1,40 @@
+import {
+  ElRadioGroup,
+  ElRadioButton,
+  ElMenu,
+  ElSubMenu,
+  ElIcon,
+  ElMenuItemGroup,
+  ElMenuItem,
+  ElSwitch,
+  ElContainer,
+  ElAside,
+  ElMain,
+  ElTabs,
+  ElTabPane,
+  ElSpace,
+  ElCard,
+  ElButton,
+  ElRow,
+  ElButtonGroup,
+} from "element-plus";
+export const components: any[] = [
+  ElRadioGroup,
+  ElRadioButton,
+  ElMenu,
+  ElSubMenu,
+  ElIcon,
+  ElMenuItemGroup,
+  ElMenuItem,
+  ElSwitch,
+  ElContainer,
+  ElAside,
+  ElMain,
+  ElTabs,
+  ElTabPane,
+  ElSpace,
+  ElCard,
+  ElButton,
+  ElRow,
+  ElButtonGroup,
+];

+ 43 - 0
src/router/index.js

@@ -0,0 +1,43 @@
+import { createRouter, createWebHistory, createWebHashHistory } from "vue-router";
+
+const router = createRouter({
+	// history: createWebHistory(import.meta.env.BASE_URL),
+	history: createWebHashHistory(),
+	routes: [
+		{
+			path: "/",
+			name: "main",
+			component: () => import("../views/main/index.vue"),
+			children: [
+				{
+					path: "map3d",
+					name: "map3d",
+					component: () => import("../views/map3d/index.vue"),
+					meta: {
+						keepAlive: true // 需要缓存
+					}
+				},
+			],
+		},
+	],
+	
+	// routes: [
+	// 	{
+	// 		path: "/",
+	// 		name: "home",
+	// 		component: () => import("../views/map3d/index.vue"),
+	// 		children: [
+	// 			{
+	// 				path: "map3d",
+	// 				name: "map3d",
+	// 				component: () => import("../views/map3d/index.vue"),
+	// 				meta: {
+	// 					keepAlive: true // 需要缓存
+	// 				}
+	// 			},
+	// 		],
+	// 	},
+	// ],
+});
+
+export default router;

+ 65 - 0
src/views/main/index.vue

@@ -0,0 +1,65 @@
+<script setup>
+	import {
+		RouterView
+	} from "vue-router";
+	import {
+		ref,
+		defineComponent
+	} from "vue";
+	import {
+		Location,
+		DataAnalysis
+	} from "@element-plus/icons-vue";
+
+	import CrMap from '@/views/map3d/index.vue';
+</script>
+
+<template>
+	<el-container style="width: 100%; height: 100%">
+		<el-aside width="aotu" style="height: 100%">
+			<el-menu default-active="1-1" class="el-menu-vertical-demo" :collapse="false" :unique-opened="true">
+				<el-sub-menu index="1">
+					<template #title>
+						<el-icon>
+							<location />
+						</el-icon>
+						<span>地图展示</span>
+					</template>
+					<el-menu-item-group>
+						<el-menu-item index="1-1" @click="displayClick('地图')">地图</el-menu-item>
+						<el-menu-item index="1-2" @click="displayClick('编辑')">编辑</el-menu-item>
+					</el-menu-item-group>
+				</el-sub-menu>
+				<el-sub-menu index="2">
+					<template #title>
+						<el-icon>
+							<DataAnalysis />
+						</el-icon>
+						<span>空间分析</span>
+					</template>
+					<el-menu-item-group>
+						<el-menu-item index="2-1" @click="analyzeClick('测量工具')">测量工具</el-menu-item>
+						<el-menu-item index="2-2" @click="analyzeClick('透视分析')">透视分析</el-menu-item>
+						<el-menu-item index="2-3" @click="analyzeClick('淹没分析')">淹没分析</el-menu-item>
+						<el-menu-item index="2-4" @click="analyzeClick('可视域分析')">可视域分析</el-menu-item>
+						<el-menu-item index="2-5" @click="analyzeClick('视频融合')">视频融合</el-menu-item>
+					</el-menu-item-group>
+				</el-sub-menu>
+			</el-menu>
+		</el-aside>
+		<el-main style="height: 100%; padding: 0">
+			<CrMap />
+		</el-main>
+	</el-container>
+
+</template>
+
+<style scoped>
+	.el-menu-vertical-demo:not(.el-menu--collapse) {
+		width: 200px;
+	}
+
+	.el-menu-vertical-demo {
+		height: 100%;
+	}
+</style>

+ 63 - 0
src/views/map/analysis/index.vue

@@ -0,0 +1,63 @@
+<template>
+  <div class="analysisTool">
+    <MeasureTool v-if="props.analysisType == '测量工具'" />
+    <FloodTool v-else-if="props.analysisType == '淹没分析'" />
+    <VideoOn v-else-if="props.analysisType == '视频融合'" />
+    <ViewshedTool v-else-if="props.analysisType == '可视域分析'" />
+    <VisibilityTool v-else-if="props.analysisType == '透视分析'" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { defineComponent } from "vue";
+import MeasureTool from "./tools/MeasureTool.vue";
+import FloodTool from "./tools/FloodTool.vue";
+import VideoOn from "./tools/VideoOn.vue";
+import ViewshedTool from "./tools/ViewshedTool.vue";
+import VisibilityTool from "./tools/VisibilityTool.vue";
+
+defineComponent({
+  MeasureTool,
+  FloodTool,
+  VideoOn,
+  ViewshedTool,
+  VisibilityTool,
+});
+
+const props = defineProps({
+  analysisType: {
+    type: String,
+    required: true,
+  },
+});
+</script>
+
+<style lang="scss" scoped>
+.analysisTool {
+  position: fixed;
+  top: 20%;
+  right: 0;
+  z-index: 2000;
+  height: auto;
+  width: 20%;
+  background: #1d1e1f;
+
+  ::-webkit-scrollbar {
+    /*滚动条整体样式*/
+    width: 2px; /*高宽分别对应横竖滚动条的尺寸*/
+    height: 1px;
+  }
+  ::-webkit-scrollbar-thumb {
+    /*滚动条里面小方块*/
+    border-radius: 10px;
+    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
+    background: #535353;
+  }
+  ::-webkit-scrollbar-track {
+    /*滚动条里面轨道*/
+    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
+    border-radius: 10px;
+    background: #ededed;
+  }
+}
+</style>

+ 80 - 0
src/views/map/analysis/tools/FloodTool.vue

@@ -0,0 +1,80 @@
+<template>
+  <el-card style="overflow: auto">
+    <el-input
+      style="margin-top: 10px"
+      v-for="(item, index) in floodConfig"
+      :key="index"
+      v-model="item.value"
+      :oninput="item.oninput"
+    >
+      <template #prepend>
+        {{ item.name }}
+      </template>
+    </el-input>
+
+    <el-button-group>
+      <el-button type="primary" text :icon="Edit" @click="floodClick"
+        >绘制</el-button
+      >
+      <el-button type="primary" text :icon="Delete" @click="clearFloodClick"
+        >清除</el-button
+      >
+    </el-button-group>
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+import { Delete, Edit } from "@element-plus/icons-vue";
+import { reactive } from "vue";
+import emitter from "@/mitt";
+import { Polygon } from "../../plot/graphicsDraw/areaDraw";
+import { FloodAnalysis } from "./floodTool";
+
+const floodConfig = reactive([
+  {
+    name: "高度",
+    value: 200,
+    oninput: "value=value.replace(/[^0-9.]/g,'')",
+  },
+  {
+    name: "速度",
+    value: 2,
+    oninput: "value=value.replace(/[^0-9.]/g,'')",
+  },
+]);
+
+let floodTool: FloodAnalysis;
+var rect: Polygon | null = new Polygon();
+function floodClick() {
+  rect = new Polygon();
+  rect.startDraw();
+  emitter.on("drawEnd", createFlood);
+}
+
+function clearFloodClick() {
+  floodTool.clear();
+  emitter.off("drawEnd", createFlood);
+}
+
+function createFlood() {
+  setTimeout(() => {
+    rect.stopDraw();
+    const pointArr = rect.pointList.slice(0, rect.pointList.length);
+    floodTool = new FloodAnalysis(
+      eval(floodConfig[0].value as unknown as string),
+      0,
+      1,
+      pointArr,
+      0.05
+    );
+    if (rect.areaPrimitive) {
+      window.Viewer.scene.groundPrimitives.remove(rect.areaPrimitive);
+    }
+    rect.disable();
+    rect = null;
+    floodTool.start();
+  }, 300);
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 61 - 0
src/views/map/analysis/tools/MeasureTool.vue

@@ -0,0 +1,61 @@
+<template>
+  <el-card style="overflow: auto">
+    <el-button-group v-for="(item, index) of measureList" :key="index">
+      <el-button
+        type="primary"
+        text
+        :icon="Edit"
+        @click="measureClick(item.enName)"
+        >{{ item.name }}</el-button
+      >
+      <el-button
+        type="primary"
+        text
+        :icon="Delete"
+        @click="clearMeasureClick(item.enName)"
+        >清除</el-button
+      >
+    </el-button-group>
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+import { Delete, Edit } from "@element-plus/icons-vue";
+import MeasurementCalc from "./measureTool";
+
+const measureTool = new MeasurementCalc();
+// * 测量类型
+const measureList = [
+  { name: "平面长度", enName: "getPlaneLength" },
+  { name: "平面面积", enName: "getPlaneArea" },
+  { name: "贴地长度", enName: "getGroundLength" },
+  { name: "贴地面积", enName: "getGroundArea" },
+];
+
+// * 开始测量
+function measureClick(name: string) {
+  switch (name) {
+    case "getPlaneLength":
+      measureTool[name]();
+      break;
+    case "getPlaneArea":
+      measureTool[name]();
+      break;
+    case "getGroundLength":
+      measureTool[name]();
+      break;
+    case "getGroundArea":
+      measureTool[name]();
+      break;
+    default:
+      break;
+  }
+}
+
+// * 清除测量结果
+const clearMeasureClick = (name: string) => {
+  measureTool.clearOne(name);
+};
+</script>
+
+<style lang="scss" scoped></style>

+ 72 - 0
src/views/map/analysis/tools/VideoOn.vue

@@ -0,0 +1,72 @@
+<!-- eslint-disable prettier/prettier -->
+<template>
+  <el-card style="overflow: auto">
+    <el-input style="margin-top: 10px" v-model="videoUrl">
+      <template #prepend> 视频地址 </template>
+    </el-input>
+    <el-button-group>
+      <el-button type="primary" text :icon="Edit" @click="videoClick"
+        >视频融合</el-button
+      >
+      <el-button type="primary" text :icon="Delete" @click="clearVideoClick"
+        >清除</el-button
+      >
+    </el-button-group>
+  </el-card>
+  <video id="myVideo" muted="" autoplay="" loop="" crossorigin="" controls="">
+    <source :src="videoUrl" type="video/webm" />
+  </video>
+</template>
+
+<script lang="ts" setup>
+import { Delete, Edit } from "@element-plus/icons-vue";
+import { ref, onMounted } from "vue";
+import emitter from "@/mitt";
+import { Rectangle } from "../../plot/graphicsDraw/areaDraw";
+import { VideoSynchronizer } from "cesium";
+
+const videoUrl = ref(
+  "http://localhost:8091/Videos/big-buck-bunny-trailer-small.webm"
+);
+
+var rect: Rectangle | null = new Rectangle();
+let videoEntity;
+function videoClick() {
+  rect = new Rectangle();
+  rect.startDraw();
+  emitter.on("drawEnd", VideoOn);
+}
+function clearVideoClick() {
+  window.Viewer.entities.remove(videoEntity);
+  rect.disable();
+  rect = null;
+  videoEntity = null;
+  emitter.off("drawEnd", VideoOn);
+}
+let videoElement;
+onMounted(() => {
+  videoElement = document.getElementById("myVideo");
+  videoElement.style.display = "none";
+
+  new VideoSynchronizer({
+    clock: window.Viewer.clock,
+    element: videoElement,
+  });
+});
+function VideoOn() {
+  setTimeout(() => {
+    videoEntity = window.Viewer.entities.add({
+      rectangle: {
+        coordinates: rect.areaPrimitive.geometryInstances.geometry.rectangle,
+        material: videoElement,
+      },
+    });
+    if (rect.areaPrimitive) {
+      window.Viewer.scene.groundPrimitives.remove(rect.areaPrimitive);
+    }
+    window.Viewer.clock.shouldAnimate = true;
+  }, 300);
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 97 - 0
src/views/map/analysis/tools/ViewshedTool.vue

@@ -0,0 +1,97 @@
+<template>
+  <el-card style="overflow: auto">
+    <el-input
+      style="margin-top: 10px"
+      v-for="(item, index) in viewshedConfig"
+      :key="index"
+      v-model="item.value"
+      :oninput="item.oninput"
+    >
+      <template #prepend>
+        {{ item.name }}
+      </template>
+    </el-input>
+    <el-button-group>
+      <el-button type="primary" text :icon="Edit" @click="viewshedClick"
+        >可视域分析</el-button
+      >
+      <el-button
+        type="primary"
+        text
+        :icon="Delete"
+        @click="clearViewshedClick()"
+        >清除</el-button
+      >
+    </el-button-group>
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+import { Delete, Edit } from "@element-plus/icons-vue";
+import { reactive } from "vue";
+import { ViewshedAnalysis } from "./viewshedTool/index";
+import {
+  Color,
+  ScreenSpaceEventHandler,
+  ScreenSpaceEventType,
+  defined,
+} from "cesium";
+
+const viewshedConfig = reactive([
+  {
+    name: "航向角",
+    value: 0,
+    oninput: "value=value.replace(/[^0-9.]/g,'')",
+  },
+  {
+    name: "俯仰角",
+    value: 0,
+    oninput: "value=value.replace(/[^0-9.]/g,'')",
+  },
+  {
+    name: "可视域水平夹角",
+    value: 90,
+    oninput: "value=value.replace(/[^0-9.]/g,'')",
+  },
+  {
+    name: "可视域垂直夹角",
+    value: 60,
+    oninput: "value=value.replace(/[^0-9.]/g,'')",
+  },
+]);
+let viewshedList = [];
+let drawHandler = new ScreenSpaceEventHandler(window.Viewer.scene.canvas);
+function viewshedClick() {
+  drawHandler = new ScreenSpaceEventHandler(window.Viewer.scene.canvas);
+  // * 监测鼠标左击事件
+  drawHandler.setInputAction((event) => {
+    let position = event.position;
+    if (!defined(position)) return;
+    let ray = window.Viewer.camera.getPickRay(position);
+    if (!defined(ray)) return;
+    let cartesian = window.Viewer.scene.globe.pick(ray, window.Viewer.scene);
+    if (!defined(cartesian)) return;
+    const viewshed = new ViewshedAnalysis({
+      viewPosition: cartesian,
+      viewDistance: 1000,
+      viewHeading: viewshedConfig[0].value,
+      viewPitch: viewshedConfig[1].value,
+      horizontalViewAngle: viewshedConfig[2].value,
+      verticalViewAngle: viewshedConfig[3].value,
+      visibleAreaColor: Color.GREEN,
+      invisibleAreaColor: Color.RED,
+    });
+    viewshedList.push(viewshed);
+  }, ScreenSpaceEventType.LEFT_CLICK);
+}
+function clearViewshedClick() {
+  drawHandler.destroy();
+  viewshedList.forEach((viewshed, i) => {
+    viewshedList[i] = null;
+    viewshed.clear();
+  });
+  viewshedList = [];
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 38 - 0
src/views/map/analysis/tools/VisibilityTool.vue

@@ -0,0 +1,38 @@
+<template>
+  <el-card style="overflow: auto">
+    <el-button-group>
+      <el-button type="primary" text :icon="Edit" @click="visibilityClick"
+        >透视分析</el-button
+      >
+      <el-button
+        type="primary"
+        text
+        :icon="Delete"
+        @click="clearVisibilityClick()"
+        >清除</el-button
+      >
+    </el-button-group>
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+import { Delete, Edit } from "@element-plus/icons-vue";
+import { VisibilityAnalysis } from "./visibilityTool";
+import { onUnmounted } from "vue";
+
+const visibilityTool = new VisibilityAnalysis();
+
+function visibilityClick() {
+  visibilityTool.analysisVisible();
+}
+
+function clearVisibilityClick() {
+  visibilityTool.clearAll();
+}
+
+onUnmounted(() => {
+  visibilityTool.destory();
+});
+</script>
+
+<style lang="scss" scoped></style>

+ 11 - 0
src/views/map/analysis/tools/floodTool/index.d.ts

@@ -0,0 +1,11 @@
+export declare class FloodAnalysis {
+  constructor(
+    height_max: number,
+    height_min: number,
+    step: number,
+    positionsArr: Cartesian3[],
+    speed: number
+  );
+  start: () => {};
+  clear: () => {};
+}

+ 75 - 0
src/views/map/analysis/tools/floodTool/index.ts

@@ -0,0 +1,75 @@
+import {
+  CallbackProperty,
+  Cartesian3,
+  Color,
+  PolygonHierarchy,
+  type Entity,
+  HeightReference,
+} from "cesium";
+
+export class FloodAnalysis {
+  polygonEntities: null | Entity;
+  extrudedHeight: number;
+  height_max: number;
+  height_min: number;
+  step: number;
+  polygon_degrees: Cartesian3[];
+  speed: number;
+  timer: number | null;
+  constructor(
+    height_max: number,
+    height_min: number,
+    step: number,
+    positionsArr: Cartesian3[],
+    speed: number
+  ) {
+    this.polygonEntities = null;
+    this.extrudedHeight = height_min;
+    this.height_max = height_max;
+    this.height_min = height_min;
+    this.step = step;
+    this.polygon_degrees = positionsArr;
+    this.speed = speed;
+    this.timer = 0;
+  }
+  _drawPoly() {
+    this.polygonEntities = window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new PolygonHierarchy(this.polygon_degrees),
+        material: Color.fromBytes(64, 157, 253, 100),
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+        extrudedHeight: new CallbackProperty(() => this.extrudedHeight, false),
+      },
+    });
+  }
+  start() {
+    const that = this;
+    this.timer = window.setInterval(() => {
+      if (
+        that.height_max > that.extrudedHeight &&
+        that.extrudedHeight >= that.height_min
+      ) {
+        that.extrudedHeight = that.extrudedHeight + that.step;
+      } else {
+        that.extrudedHeight = that.height_min;
+      }
+    }, that.speed * 1000);
+    that._drawPoly();
+  }
+  clear() {
+    if (this.timer) {
+      window.clearInterval(this.timer);
+      this.timer = null;
+    }
+    this.extrudedHeight = this.height_min;
+    window.Viewer.entities.remove(this.polygonEntities);
+    this.polygonEntities = null;
+  }
+  changeMapType(type: boolean) {
+    if (!type) {
+      this.polygonEntities && (this.polygonEntities.show = false);
+    } else {
+      this.polygonEntities && (this.polygonEntities.show = true);
+    }
+  }
+}

+ 39 - 0
src/views/map/analysis/tools/measureTool/globeTooltip.ts

@@ -0,0 +1,39 @@
+import type { Cartesian2 } from "cesium";
+
+export class GlobeTooltip {
+  protected _frameDiv: HTMLElement | undefined;
+  protected _div: HTMLElement;
+  protected _titleDiv: HTMLElement;
+  constructor(frameDiv: HTMLElement) {
+    const div = document.createElement("div");
+    div.className = "twipsy-right";
+    div.style.position = "absolute";
+
+    const arrow = document.createElement("div");
+    arrow.className = "twipsy-arrow";
+    div.appendChild(arrow);
+
+    const title = document.createElement("div");
+    title.className = "twipsy-inner";
+    div.appendChild(title);
+
+    frameDiv.appendChild(div);
+
+    this._frameDiv = frameDiv;
+    this._div = div;
+    this._titleDiv = title;
+  }
+  setVisible(visible: boolean) {
+    this._div.style.display = visible ? "block" : "none";
+  }
+  showAt(position: Cartesian2, message: string) {
+    this.setVisible(true);
+    this._titleDiv.innerHTML = message;
+    this._div.style.left = position.x + 10 + "px";
+    this._div.style.top = position.y - this._div.clientHeight / 2 + "px";
+  }
+  destory() {
+    this._frameDiv?.removeChild(this._div);
+    this._frameDiv = undefined;
+  }
+}

+ 552 - 0
src/views/map/analysis/tools/measureTool/index.ts

@@ -0,0 +1,552 @@
+import {
+  CallbackProperty,
+  Cartesian3,
+  Color,
+  defined,
+  Ellipsoid,
+  Entity,
+  HeightReference,
+  PolygonHierarchy,
+  PolylineGraphics,
+  Property,
+  ScreenSpaceEventHandler,
+  ScreenSpaceEventType,
+  Math as cesiumMath,
+  EllipsoidGeodesic,
+  sampleTerrainMostDetailed,
+  createWorldTerrain,
+  Cartographic,
+} from "cesium";
+import { GlobeTooltip } from "./globeTooltip";
+import { getCatesian3FromPX } from "@/views/map/plot/tools";
+import { infoBox } from "./infoBox";
+import * as turf from "@turf/turf";
+
+export default class MeasurementCalc {
+  planeLengthEntityList: any[];
+  planeLengthDivList: any[];
+  planeLengthListenList: any[];
+  planeAreaEntityList: any[];
+  planeAreaDivList: any[];
+  planeAreaListenList: any[];
+  groundLengthEntityList: any[];
+  groundLengthDivList: any[];
+  groundLengthListenList: any[];
+  groundAreaEntityList: any[];
+  groundAreaDivList: any[];
+  groundAreaListenList: any[];
+  pointList: any[];
+  handler: any;
+  constructor() {
+    this.planeLengthListenList = [];
+    this.planeLengthDivList = [];
+    this.planeLengthEntityList = [];
+    this.planeAreaEntityList = [];
+    this.planeAreaDivList = [];
+    this.planeAreaListenList = [];
+    this.groundLengthListenList = [];
+    this.groundLengthDivList = [];
+    this.groundLengthEntityList = [];
+    this.groundAreaEntityList = [];
+    this.groundAreaDivList = [];
+    this.groundAreaListenList = [];
+    this.pointList = [];
+    this.handler = new ScreenSpaceEventHandler(window.Viewer.scene.canvas);
+  }
+
+  // * 创建中间线条entity以适应动态数据
+  createLineEntity(isGround: boolean): Entity {
+    const update = () => {
+      return this.pointList;
+    };
+    return window.Viewer.entities.add({
+      polyline: new PolylineGraphics({
+        positions: new CallbackProperty(update, false),
+        show: true,
+        material: Color.BLUE,
+        clampToGround: isGround,
+      }),
+    });
+  }
+
+  // * 创建中间多边形entity以适应动态数据
+  createAreaEntity(isGround: boolean): Entity {
+    const update = () => {
+      return new PolygonHierarchy(this.pointList);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: isGround
+          ? HeightReference.CLAMP_TO_GROUND
+          : HeightReference.NONE,
+      },
+    });
+  }
+
+  //   * 获取平面长度
+  getPlaneLength() {
+    this.pointList = [];
+    let lineEntity: null | Entity;
+    const tooltip = new GlobeTooltip(window.Viewer.container);
+    tooltip.setVisible(false);
+    // * 监测鼠标左击事件
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+
+    // * 监测鼠标移动事件
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!defined(cartesian)) return;
+
+      const position = evt.endPosition;
+      if (this.pointList.length < 1) {
+        tooltip.showAt(position, "<p>选择起点</p>");
+        return;
+      }
+
+      if (this.pointList.length == 2 && !lineEntity) {
+        lineEntity = this.createLineEntity(false);
+        this.planeLengthEntityList.push(lineEntity);
+      }
+
+      const num = this.pointList.length;
+      let tip = "<p>点击添加下一个点</p>";
+      if (num > 2) {
+        tip += "<p>点击鼠标右键结束绘制</p>";
+      }
+      tooltip.showAt(position, tip);
+
+      this.pointList.pop();
+      this.pointList.push(cartesian);
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 监测鼠标右击事件
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 3) return;
+      let total = 0;
+      for (let i = 1; i < this.pointList.length; i++) {
+        const p1 = this.pointList[i - 1];
+        const p2 = this.pointList[i];
+        const dis = Cartesian3.distance(p1, p2) / 1000;
+        total += dis;
+      }
+      lineEntity &&
+        lineEntity.polyline &&
+        (lineEntity.polyline.positions = this.pointList as unknown as Property);
+      const { infoDiv, listenerEvt } = infoBox(
+        window.Viewer.container,
+        this.pointList[this.pointList.length - 1],
+        total.toFixed(2) + "km"
+      );
+      this.planeLengthDivList.push(infoDiv);
+      this.planeLengthListenList.push(listenerEvt);
+      tooltip.destory();
+      this.destoryMeasure();
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+
+  //   * 获取平面面积
+  getPlaneArea() {
+    this.pointList = [];
+    let areaEntity: null | Entity;
+    const tooltip = new GlobeTooltip(window.Viewer.container);
+    tooltip.setVisible(false);
+    // * 监测鼠标左击事件
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+
+    // * 监测鼠标移动事件
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!defined(cartesian)) return;
+
+      const position = evt.endPosition;
+      if (this.pointList.length < 1) {
+        tooltip.showAt(position, "<p>选择起点</p>");
+        return;
+      }
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !areaEntity) {
+        areaEntity = this.createAreaEntity(false);
+        this.planeAreaEntityList.push(areaEntity);
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+
+      const num = this.pointList.length;
+      let tip = "<p>点击添加下一个点</p>";
+      if (num > 3) {
+        tip += "<p>点击鼠标右键结束绘制</p>";
+      }
+      tooltip.showAt(position, tip);
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 监测鼠标右击事件
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 3) return;
+      const elliposid = Ellipsoid.WGS84,
+        lonLatArray = [];
+      for (const item of this.pointList) {
+        const cartographic = elliposid.cartesianToCartographic(item);
+        lonLatArray.push([
+          cesiumMath.toDegrees(cartographic.longitude),
+          cesiumMath.toDegrees(cartographic.latitude),
+        ]);
+      }
+      lonLatArray.push(lonLatArray[0]);
+      const polygonGeoJson = turf.polygon([lonLatArray]);
+      const total = turf.area(polygonGeoJson);
+      areaEntity &&
+        areaEntity.polygon &&
+        (areaEntity.polygon.hierarchy = new PolygonHierarchy(
+          this.pointList
+        ) as unknown as Property);
+      const { infoDiv, listenerEvt } = infoBox(
+        window.Viewer.container,
+        this.pointList[this.pointList.length - 1],
+        total.toFixed(2) + "km²"
+      );
+      this.planeAreaDivList.push(infoDiv);
+      this.planeAreaListenList.push(listenerEvt);
+      tooltip.destory();
+      this.destoryMeasure();
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+
+  //   * 获取贴地长度
+  getGroundLength() {
+    this.pointList = [];
+    let lineEntity: null | Entity;
+    const tooltip = new GlobeTooltip(window.Viewer.container);
+    tooltip.setVisible(false);
+    // * 监测鼠标左击事件
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+
+    // * 监测鼠标移动事件
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!defined(cartesian)) return;
+
+      const position = evt.endPosition;
+      if (this.pointList.length < 1) {
+        tooltip.showAt(position, "<p>选择起点</p>");
+        return;
+      }
+
+      if (this.pointList.length == 2 && !lineEntity) {
+        lineEntity = this.createLineEntity(true);
+        this.groundLengthEntityList.push(lineEntity);
+      }
+
+      const num = this.pointList.length;
+      let tip = "<p>点击添加下一个点</p>";
+      if (num > 2) {
+        tip += "<p>点击鼠标右键结束绘制</p>";
+      }
+      tooltip.showAt(position, tip);
+
+      this.pointList.pop();
+      this.pointList.push(cartesian);
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 监测鼠标右击事件
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 3) return;
+
+      // * 进行插值
+      const pnts = [].concat(...this.pointList);
+      const num = pnts.length;
+      const tempPositions = [];
+      for (let i = 1; i < num; i++) {
+        const p1 = pnts[i - 1];
+        const p2 = pnts[i];
+        const ellipsoid = window.Viewer.scene.globe.ellipsoid;
+        const c1 = ellipsoid.cartesianToCartographic(p1),
+          c2 = ellipsoid.cartesianToCartographic(p2);
+        const cm = new EllipsoidGeodesic(c1, c2).interpolateUsingFraction(0.5);
+        const cp = ellipsoid.cartographicToCartesian(cm);
+        tempPositions.push(p1);
+        tempPositions.push(cp);
+      }
+      const last = pnts[num - 1];
+      tempPositions.push(last);
+      let total = 0;
+      for (let i = 1; i < tempPositions.length; i++) {
+        const p1 = tempPositions[i - 1];
+        const p2 = tempPositions[i];
+        const dis = Cartesian3.distance(p1, p2) / 1000;
+        total += dis;
+      }
+
+      lineEntity &&
+        lineEntity.polyline &&
+        (lineEntity.polyline.positions = this.pointList as unknown as Property);
+      const { infoDiv, listenerEvt } = infoBox(
+        window.Viewer.container,
+        this.pointList[this.pointList.length - 1],
+        total.toFixed(2) + "km"
+      );
+      this.groundLengthDivList.push(infoDiv);
+      this.groundLengthListenList.push(listenerEvt);
+      tooltip.destory();
+      this.destoryMeasure();
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+
+  //   * 获取贴地面积
+  getGroundArea() {
+    this.pointList = [];
+    let areaEntity: null | Entity;
+    const tooltip = new GlobeTooltip(window.Viewer.container);
+    tooltip.setVisible(false);
+    // * 监测鼠标左击事件
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+
+    // * 监测鼠标移动事件
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!defined(cartesian)) return;
+
+      const position = evt.endPosition;
+      if (this.pointList.length < 1) {
+        tooltip.showAt(position, "<p>选择起点</p>");
+        return;
+      }
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !areaEntity) {
+        areaEntity = this.createAreaEntity(false);
+        this.groundAreaEntityList.push(areaEntity);
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+
+      const num = this.pointList.length;
+      let tip = "<p>点击添加下一个点</p>";
+      if (num > 3) {
+        tip += "<p>点击鼠标右键结束绘制</p>";
+      }
+      tooltip.showAt(position, tip);
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 监测鼠标右击事件
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 3) return;
+      const elliposid = Ellipsoid.WGS84,
+        lonLatArray = [];
+      for (const item of this.pointList) {
+        const cartographic = elliposid.cartesianToCartographic(item);
+        lonLatArray.push([
+          cesiumMath.toDegrees(cartographic.longitude),
+          cesiumMath.toDegrees(cartographic.latitude),
+        ]);
+      }
+      lonLatArray.push(lonLatArray[0]);
+      // * 将多边形转三角集
+      const polygonGeoJson = turf.polygon([lonLatArray]);
+      const triangles = turf.tesselate(polygonGeoJson);
+      const promiseArray = [];
+      for (const triangle of triangles.features) {
+        const area = turf.area(triangle);
+        const cellSize = Math.sqrt(area / 1000);
+        // * 通过三个点形成一个矩形
+        const enveloped = turf.envelope(triangle);
+        // * 获取最大及最小的xy值
+        const bbox = turf.bbox(enveloped);
+        // * 通过最大最小点形成矩形内插值,返回点集
+        const grid = turf.pointGrid(bbox, cellSize, { units: "meters" });
+        // * 获取所有落在三角形内的点
+        const trianglePoint = turf.pointsWithinPolygon(grid, triangle);
+        const allPos = [];
+        for (const triPoint of trianglePoint.features) {
+          allPos.push(
+            Cartographic.fromDegrees(
+              triPoint.geometry.coordinates[0],
+              triPoint.geometry.coordinates[1]
+            )
+          );
+        }
+        const promisePos = sampleTerrainMostDetailed(
+          createWorldTerrain(),
+          allPos
+        );
+        promiseArray.push(promisePos);
+      }
+      Promise.all(promiseArray).then((updatedPositions) => {
+        let groundArea = 0;
+        for (let m = 0; m < updatedPositions.length; m++) {
+          const mapPos = new Map();
+          for (let i = 0; i < updatedPositions[m].length; i++) {
+            mapPos.set(
+              updatedPositions[m][i].longitude.toString() +
+                updatedPositions[m][i].latitude.toString(),
+              updatedPositions[m][i].height
+            );
+          }
+          const area = turf.area(triangles.features[m]);
+          const cellSize = Math.sqrt(area / 1000);
+          // * 通过三个点形成一个矩形
+          const enveloped = turf.envelope(triangles.features[m]);
+          // * 获取最大及最小的xy值
+          const bbox = turf.bbox(enveloped);
+          // * 通过最大最小点形成矩形内插值,返回点集
+          const grid = turf.pointGrid(bbox, cellSize, { units: "meters" });
+          // * 获取所有落在三角形内的点
+          const trianglePoint = turf.pointsWithinPolygon(
+            grid,
+            triangles.features[m]
+          );
+          const tin = turf.tin(trianglePoint);
+          for (let j = 0; j < tin.features.length; j++) {
+            const car3Array = [];
+            for (let k = 0; k < 3; k++) {
+              const lon = tin.features[j].geometry.coordinates[0][k][0];
+              const lat = tin.features[j].geometry.coordinates[0][k][1];
+              const car2g = Cartographic.fromDegrees(lon, lat);
+              const height = mapPos.get(
+                car2g.longitude.toString() + car2g.latitude.toString()
+              );
+              car2g.height = height;
+              car3Array.push(car2g);
+            }
+            const firstPoint2car3 = Cartesian3.fromRadians(
+              car3Array[0].longitude,
+              car3Array[0].latitude,
+              car3Array[0].height
+            );
+            const secondPoint2car3 = Cartesian3.fromRadians(
+              car3Array[1].longitude,
+              car3Array[1].latitude,
+              car3Array[1].height
+            );
+            const thirdPoint2car3 = Cartesian3.fromRadians(
+              car3Array[2].longitude,
+              car3Array[2].latitude,
+              car3Array[2].height
+            );
+            const a = Cartesian3.distance(firstPoint2car3, secondPoint2car3);
+            const b = Cartesian3.distance(thirdPoint2car3, secondPoint2car3);
+            const c = Cartesian3.distance(firstPoint2car3, thirdPoint2car3);
+            const p = (a + b + c) / 2;
+
+            groundArea += Math.sqrt(p * (p - a) * (p - b) * (p - c));
+          }
+        }
+        areaEntity &&
+          areaEntity.polygon &&
+          (areaEntity.polygon.hierarchy = new PolygonHierarchy(
+            this.pointList
+          ) as unknown as Property);
+        const { infoDiv, listenerEvt } = infoBox(
+          window.Viewer.container,
+          this.pointList[this.pointList.length - 1],
+          groundArea.toFixed(2) + "km²"
+        );
+        this.groundAreaDivList.push(infoDiv);
+        this.groundAreaListenList.push(listenerEvt);
+        tooltip.destory();
+        this.destoryMeasure();
+      });
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+
+  //   * 清除销毁测量
+  destoryMeasure() {
+    this.handler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+    this.handler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
+    this.handler.removeInputAction(ScreenSpaceEventType.RIGHT_CLICK);
+  }
+
+  //   * 清除某一类测量
+  clearOne(name: string) {
+    switch (name) {
+      case "getPlaneLength":
+        this.planeLengthDivList.forEach((item) => {
+          window.Viewer.container.removeChild(item);
+        });
+        this.planeLengthListenList.forEach((item) => {
+          window.Viewer.scene.postRender.addEventListener(item);
+        });
+        this.planeLengthEntityList.forEach((item) => {
+          window.Viewer.entities.remove(item);
+        });
+        this.planeLengthListenList = [];
+        this.planeLengthDivList = [];
+        this.planeLengthEntityList = [];
+        this.pointList = [];
+        break;
+      case "getPlaneArea":
+        this.planeAreaDivList.forEach((item) => {
+          window.Viewer.container.removeChild(item);
+        });
+        this.planeAreaListenList.forEach((item) => {
+          window.Viewer.scene.postRender.addEventListener(item);
+        });
+        this.planeAreaEntityList.forEach((item) => {
+          window.Viewer.entities.remove(item);
+        });
+        this.planeAreaListenList = [];
+        this.planeAreaDivList = [];
+        this.planeAreaEntityList = [];
+        this.pointList = [];
+        break;
+      case "getGroundLength":
+        this.groundLengthDivList.forEach((item) => {
+          window.Viewer.container.removeChild(item);
+        });
+        this.groundLengthListenList.forEach((item) => {
+          window.Viewer.scene.postRender.addEventListener(item);
+        });
+        this.groundLengthEntityList.forEach((item) => {
+          window.Viewer.entities.remove(item);
+        });
+        this.groundLengthListenList = [];
+        this.groundLengthDivList = [];
+        this.groundLengthEntityList = [];
+        this.pointList = [];
+        break;
+      case "getGroundArea":
+        this.groundAreaDivList.forEach((item) => {
+          window.Viewer.container.removeChild(item);
+        });
+        this.groundAreaListenList.forEach((item) => {
+          window.Viewer.scene.postRender.addEventListener(item);
+        });
+        this.groundAreaEntityList.forEach((item) => {
+          window.Viewer.entities.remove(item);
+        });
+        this.groundAreaListenList = [];
+        this.groundAreaDivList = [];
+        this.groundAreaEntityList = [];
+        this.pointList = [];
+        break;
+      default:
+        break;
+    }
+  }
+}

+ 64 - 0
src/views/map/analysis/tools/measureTool/infoBox.ts

@@ -0,0 +1,64 @@
+import { Cartesian2, Cartesian3, SceneTransforms } from "cesium";
+
+export function infoBox(
+  frameDiv: HTMLElement,
+  cartesain: Cartesian2,
+  info: string
+) {
+  const cartographic =
+    window.Viewer.scene.globe.ellipsoid.cartesianToCartographic(cartesain);
+  const height = window.Viewer.scene.globe.getHeight(cartographic);
+
+  const newCartesain = Cartesian3.fromRadians(
+    cartographic.longitude,
+    cartographic.latitude,
+    height
+  );
+
+  let coordinates = SceneTransforms.wgs84ToWindowCoordinates(
+    window.Viewer.scene,
+    newCartesain
+  );
+  const infoDiv = document.createElement("div");
+  infoDiv.className = "infoBox";
+  infoDiv.innerHTML = info;
+  frameDiv.appendChild(infoDiv);
+  positionPopUp(coordinates, infoDiv);
+
+  const listenerEvt = () => {
+    const new_cartesain = Cartesian3.fromRadians(
+      cartographic.longitude,
+      cartographic.latitude,
+      window.Viewer.scene.globe.getHeight(cartographic)
+    );
+    const changeCoordinates = SceneTransforms.wgs84ToWindowCoordinates(
+      window.Viewer.scene,
+      new_cartesain
+    );
+    if (
+      coordinates &&
+      changeCoordinates &&
+      coordinates.x &&
+      changeCoordinates.x &&
+      coordinates.y &&
+      changeCoordinates.y
+    ) {
+      if (
+        coordinates.x !== changeCoordinates.x ||
+        coordinates.y !== changeCoordinates.y
+      ) {
+        positionPopUp(changeCoordinates, infoDiv);
+        coordinates = changeCoordinates;
+      }
+    }
+  };
+  window.Viewer.scene.postRender.addEventListener(listenerEvt);
+  return { infoDiv, listenerEvt };
+}
+
+function positionPopUp(coordinates: Cartesian2, infoDiv: HTMLDivElement) {
+  const x = coordinates.x - infoDiv.offsetWidth / 2;
+  const y = coordinates.y - infoDiv.offsetHeight - 65;
+  infoDiv.style.left = x + "px";
+  infoDiv.style.top = y + "px";
+}

+ 113 - 0
src/views/map/analysis/tools/viewshedTool/glsl.ts

@@ -0,0 +1,113 @@
+export default `
+#define USE_CUBE_MAP_SHADOW true
+uniform sampler2D colorTexture;
+uniform sampler2D depthTexture;
+in vec2 v_textureCoordinates;
+uniform mat4 camera_projection_matrix;
+uniform mat4 camera_view_matrix;
+uniform samplerCube shadowMap_textureCube;
+uniform mat4 shadowMap_matrix;
+uniform vec4 shadowMap_lightPositionEC;
+uniform vec4 shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness;
+uniform vec4 shadowMap_texelSizeDepthBiasAndNormalShadingSmooth;
+uniform float helsing_viewDistance;
+uniform vec4 helsing_visibleAreaColor;
+uniform vec4 helsing_invisibleAreaColor;
+struct zx_shadowParameters
+{
+    vec3 texCoords;
+    float depthBias;
+    float depth;
+    float nDotL;
+    vec2 texelStepSize;
+    float normalShadingSmooth;
+    float darkness;
+};
+float czm_shadowVisibility(samplerCube shadowMap,zx_shadowParameters shadowParameters)
+{
+    float depthBias = shadowParameters.depthBias;
+    float depth = shadowParameters.depth;
+    float nDotL = shadowParameters.nDotL;
+    float normalShadingSmooth = shadowParameters.normalShadingSmooth;
+    float darkness = shadowParameters.darkness;
+    vec3 uvw = shadowParameters.texCoords;
+    depth -= depthBias;
+    float visibility = czm_shadowDepthCompare(shadowMap, uvw, depth);
+    return czm_private_shadowVisibility(visibility, nDotL, normalShadingSmooth, darkness);
+}
+vec4 getPositionEC(){
+    return czm_windowToEyeCoordinates(gl_FragCoord);
+}
+vec3 getNormalEC(){
+    return vec3(1.);
+}
+vec4 toEye(in vec2 uv,in float depth){
+    vec2 xy=vec2((uv.x*2.-1.),(uv.y*2.-1.));
+    vec4 posInCamera=czm_inverseProjection*vec4(xy,depth,1.);
+    posInCamera=posInCamera/posInCamera.w;
+    return posInCamera;
+}
+float getDepth(in vec4 depth){
+    float z_window=czm_unpackDepth(depth);
+    z_window=czm_reverseLogDepth(z_window);
+    float n_range=czm_depthRange.near;
+    float f_range=czm_depthRange.far;
+    return(2.*z_window-n_range-f_range)/(f_range-n_range);
+}
+float shadow(in vec4 positionEC,in float depth){
+    vec3 normalEC=getNormalEC();
+    zx_shadowParameters shadowParameters;
+    shadowParameters.texelStepSize=shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.xy;
+    shadowParameters.depthBias=shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.z;
+    shadowParameters.normalShadingSmooth=shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.w;
+    shadowParameters.darkness=shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness.w;
+    vec3 directionEC=positionEC.xyz-shadowMap_lightPositionEC.xyz;
+    float distance=length(directionEC);
+    directionEC=normalize(directionEC);
+    float radius=shadowMap_lightPositionEC.w;
+    if(distance>radius)
+    {
+        return 2.;
+    }
+    vec3 directionWC=czm_inverseViewRotation*directionEC;
+    shadowParameters.depth=distance/radius-.0003;
+    shadowParameters.nDotL=clamp(dot(normalEC,-directionEC),0.,1.);
+    shadowParameters.texCoords=directionWC;
+    float visibility=czm_shadowVisibility(shadowMap_textureCube,shadowParameters);
+    return visibility;
+}
+bool visible(in vec4 result)
+{
+    result.x/=result.w;
+    result.y/=result.w;
+    result.z/=result.w;
+    return result.x>=-1.&&result.x<=1.
+    &&result.y>=-1.&&result.y<=1.
+    &&result.z>=-1.&&result.z<=1.;
+}
+void main(){
+    out_FragColor=texture(colorTexture,v_textureCoordinates);
+    float depth=getDepth(texture(depthTexture,v_textureCoordinates));
+    vec4 viewPos=toEye(v_textureCoordinates,depth);
+    // 世界坐标
+    vec4 wordPos=czm_inverseView*viewPos;
+    // 虚拟相机中坐标
+    vec4 vcPos=camera_view_matrix*wordPos;
+    float near=.001*helsing_viewDistance;
+    float dis=length(vcPos.xyz);
+    if(dis>near&&dis<helsing_viewDistance){
+        // 透视投影
+        vec4 posInEye=camera_projection_matrix*vcPos;
+        // 可视区颜色
+        // vec4 helsing_visibleAreaColor=vec4(0.,1.,0.,.5);
+        // vec4 helsing_invisibleAreaColor=vec4(1.,0.,0.,.5);
+        if(visible(posInEye)){
+            float vis=shadow(viewPos,depth);
+            if(vis>.3){
+                out_FragColor=mix(out_FragColor,helsing_visibleAreaColor,.5);
+            }else{
+                out_FragColor=mix(out_FragColor,helsing_invisibleAreaColor,.5);
+            }
+        }
+    }
+}`;

+ 292 - 0
src/views/map/analysis/tools/viewshedTool/index.ts

@@ -0,0 +1,292 @@
+import {
+  Camera,
+  Cartesian2,
+  Cartesian3,
+  Cartesian4,
+  Color,
+  ColorGeometryInstanceAttribute,
+  Entity,
+  FrustumOutlineGeometry,
+  GeometryInstance,
+  HeadingPitchRoll,
+  Matrix3,
+  Matrix4,
+  PerInstanceColorAppearance,
+  PerspectiveFrustum,
+  PostProcessStage,
+  Primitive,
+  Quaternion,
+  ShadowMap,
+  ShowGeometryInstanceAttribute,
+  Transforms,
+  Math as cesiumMath,
+  ShadowMode,
+} from "cesium";
+import glsl from "./glsl.ts";
+
+export class ViewshedAnalysis {
+  private viewPosition: Cartesian3;
+  private viewPositionEnd: Cartesian3;
+  private viewDistance: number;
+  private viewHeading: number;
+  private viewPitch: number;
+  private horizontalViewAngle: number;
+  private verticalViewAngle: number;
+  private visibleAreaColor: any;
+  private invisibleAreaColor: any;
+  private sketch: null | Entity;
+  private postStage: null | PostProcessStage;
+  private frustumOutline: null | Primitive;
+  private lightCamera: Camera | undefined;
+  private shadowMap: any;
+  constructor(option: {
+    viewPosition: Cartesian3;
+    viewPositionEnd: Cartesian3;
+    viewDistance: number;
+    viewHeading: number;
+    viewPitch: number;
+    horizontalViewAngle: number;
+    verticalViewAngle: number;
+    visibleAreaColor: any;
+    invisibleAreaColor: any;
+  }) {
+    this.viewPosition = option.viewPosition;
+    this.viewPositionEnd = option.viewPositionEnd;
+    this.viewDistance = option.viewDistance;
+    this.viewHeading = option.viewHeading;
+    this.viewPitch = option.viewPitch;
+    this.horizontalViewAngle = option.horizontalViewAngle;
+    this.verticalViewAngle = option.verticalViewAngle;
+    this.visibleAreaColor = option.visibleAreaColor;
+    this.invisibleAreaColor = option.invisibleAreaColor;
+    this.sketch = null;
+    this.postStage = null;
+    this.frustumOutline = null;
+    this.update();
+  }
+  private add() {
+    this.createLightCamera();
+    this.createShadowMap();
+    this.createPostStage();
+    this.drawFrustumOutline();
+    this.drawSketch();
+  }
+
+  private update() {
+    this.clear();
+    this.add();
+  }
+  clear() {
+    if (this.sketch) {
+      window.Viewer.entities.removeById(this.sketch.id);
+      this.sketch = null;
+    }
+    if (this.frustumOutline) {
+      window.Viewer.scene.primitives.remove(this.frustumOutline);
+      this.frustumOutline = null;
+    }
+    if (this.postStage) {
+      window.Viewer.scene.postProcessStages.remove(this.postStage);
+      this.postStage = null;
+    }
+  }
+
+  private createLightCamera() {
+    this.lightCamera = new Camera(window.Viewer.scene);
+    this.lightCamera.position = this.viewPosition;
+    this.lightCamera.frustum.near = this.viewDistance * 0.001;
+    this.lightCamera.frustum.far = this.viewDistance;
+    const hr = cesiumMath.toRadians(this.horizontalViewAngle);
+    const vr = cesiumMath.toRadians(this.verticalViewAngle);
+    const aspectRatio =
+      (this.viewDistance * Math.tan(hr / 2) * 2) /
+      (this.viewDistance * Math.tan(vr / 2) * 2);
+    (this.lightCamera.frustum as PerspectiveFrustum).aspectRatio = aspectRatio;
+    (this.lightCamera.frustum as PerspectiveFrustum).fov = hr > vr ? hr : vr;
+    this.lightCamera.setView({
+      destination: this.viewPosition,
+      orientation: {
+        heading: cesiumMath.toRadians(this.viewHeading),
+        pitch: cesiumMath.toRadians(this.viewPitch),
+        roll: 0,
+      },
+    });
+  }
+  private createShadowMap() {
+    this.shadowMap = new ShadowMap({
+      context: window.Viewer.scene.context,
+      lightCamera: this.lightCamera,
+      enabled: true,
+      isPointLight: true,
+      pointLightRadius: this.viewDistance,
+      cascadesEnabled: false,
+      size: 256,
+      softShadows: true,
+      normalOffset: true,
+      fromLightSource: false,
+    });
+    window.Viewer.scene.shadowMap = this.shadowMap;
+    window.Viewer.scene.globe.shadows = ShadowMode.ENABLED;
+    window.Viewer.scene.globe.depthTestAgainstTerrain = true;
+    window.Viewer.scene.logarithmicDepthBuffer = true;
+  }
+  private createPostStage() {
+    const fs = glsl;
+    const postStage = new PostProcessStage({
+      fragmentShader: fs,
+      uniforms: {
+        shadowMap_textureCube: () => {
+          this.shadowMap.update(
+            Reflect.get(window.Viewer.scene, "_frameState")
+          );
+          return Reflect.get(this.shadowMap, "_shadowMapTexture");
+        },
+        shadowMap_matrix: () => {
+          this.shadowMap.update(
+            Reflect.get(window.Viewer.scene, "_frameState")
+          );
+          return Reflect.get(this.shadowMap, "_shadowMapMatrix");
+        },
+        shadowMap_lightPositionEC: () => {
+          this.shadowMap.update(
+            Reflect.get(window.Viewer.scene, "_frameState")
+          );
+          return Reflect.get(this.shadowMap, "_lightPositionEC");
+        },
+        shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness: () => {
+          this.shadowMap.update(
+            Reflect.get(window.Viewer.scene, "_frameState")
+          );
+          const bias = this.shadowMap._pointBias;
+          return Cartesian4.fromElements(
+            bias.normalOffsetScale,
+            this.shadowMap._distance,
+            this.shadowMap.maximumDistance,
+            0.0,
+            new Cartesian4()
+          );
+        },
+        shadowMap_texelSizeDepthBiasAndNormalShadingSmooth: () => {
+          this.shadowMap.update(
+            Reflect.get(window.Viewer.scene, "_frameState")
+          );
+          const bias = this.shadowMap._pointBias;
+          const scratchTexelStepSize = new Cartesian2();
+          const texelStepSize = scratchTexelStepSize;
+          texelStepSize.x = 1.0 / this.shadowMap._textureSize.x;
+          texelStepSize.y = 1.0 / this.shadowMap._textureSize.y;
+
+          return Cartesian4.fromElements(
+            texelStepSize.x,
+            texelStepSize.y,
+            bias.depthBias,
+            bias.normalShadingSmooth,
+            new Cartesian4()
+          );
+        },
+        camera_projection_matrix: this.lightCamera!.frustum.projectionMatrix,
+        camera_view_matrix: this.lightCamera!.viewMatrix,
+        helsing_viewDistance: () => {
+          return this.viewDistance;
+        },
+        helsing_visibleAreaColor: this.visibleAreaColor,
+        helsing_invisibleAreaColor: this.invisibleAreaColor,
+      },
+    });
+    this.postStage = window.Viewer.scene.postProcessStages.add(postStage);
+  }
+  private drawFrustumOutline() {
+    const scratchRight = new Cartesian3();
+    const scratchRotation = new Matrix3();
+    const scratchOrientation = new Quaternion();
+    const direction = this.lightCamera!.directionWC;
+    const up = this.lightCamera!.upWC;
+    let right = this.lightCamera!.rightWC;
+    right = Cartesian3.negate(right, scratchRight);
+    const rotation = scratchRotation;
+    Matrix3.setColumn(rotation, 0, right, rotation);
+    Matrix3.setColumn(rotation, 1, up, rotation);
+    Matrix3.setColumn(rotation, 2, direction, rotation);
+    const orientation = Quaternion.fromRotationMatrix(
+      rotation,
+      scratchOrientation
+    );
+
+    const instance = new GeometryInstance({
+      geometry: new FrustumOutlineGeometry({
+        frustum: this.lightCamera!.frustum,
+        origin: this.viewPosition,
+        orientation: orientation,
+      }),
+      id: Math.random().toString(36).substr(2),
+      attributes: {
+        color: ColorGeometryInstanceAttribute.fromColor(Color.YELLOWGREEN),
+        show: new ShowGeometryInstanceAttribute(true),
+      },
+    });
+
+    this.frustumOutline = window.Viewer.scene.primitives.add(
+      new Primitive({
+        geometryInstances: [instance],
+        appearance: new PerInstanceColorAppearance({
+          flat: true,
+          translucent: false,
+        }),
+      })
+    );
+  }
+  private drawSketch() {
+    this.sketch = window.Viewer.entities.add({
+      name: "sketch",
+      position: this.viewPosition,
+      orientation: Transforms.headingPitchRollQuaternion(
+        this.viewPosition,
+        HeadingPitchRoll.fromDegrees(
+          this.viewHeading - this.horizontalViewAngle,
+          this.viewPitch,
+          0.0
+        )
+      ),
+      ellipsoid: {
+        radii: new Cartesian3(
+          this.viewDistance,
+          this.viewDistance,
+          this.viewDistance
+        ),
+        minimumClock: cesiumMath.toRadians(-this.horizontalViewAngle / 2),
+        maximumClock: cesiumMath.toRadians(this.horizontalViewAngle / 2),
+        minimumCone: cesiumMath.toRadians(this.verticalViewAngle + 7.75),
+        maximumCone: cesiumMath.toRadians(180 - this.verticalViewAngle - 7.75),
+        fill: false,
+        outline: true,
+        subdivisions: 256,
+        stackPartitions: 64,
+        slicePartitions: 64,
+        outlineColor: Color.YELLOWGREEN,
+      },
+    });
+  }
+}
+
+function getHeadingOrPitch(
+  type: string,
+  fromPosition: Cartesian3,
+  toPosition: Cartesian3
+): number {
+  const finalPosition = new Cartesian3();
+  const matrix4 = Transforms.eastNorthUpToFixedFrame(fromPosition);
+  Matrix4.inverse(matrix4, matrix4);
+  Matrix4.multiplyByPoint(matrix4, toPosition, finalPosition);
+  Cartesian3.normalize(finalPosition, finalPosition);
+  switch (type) {
+    case "heading":
+      return cesiumMath.toDegrees(Math.atan2(finalPosition.x, finalPosition.y));
+      break;
+    case "pitch":
+      return cesiumMath.toDegrees(Math.asin(finalPosition.z));
+      break;
+    default:
+      return cesiumMath.toDegrees(Math.atan2(finalPosition.x, finalPosition.y));
+      break;
+  }
+}

+ 147 - 0
src/views/map/analysis/tools/visibilityTool/index.ts

@@ -0,0 +1,147 @@
+import { getCatesian3FromPX } from "@/views/map/plot/tools";
+import {
+  CallbackProperty,
+  Cartesian3,
+  Cesium3DTileset,
+  Color,
+  Entity,
+  HeadingPitchRange,
+  PolylineGraphics,
+  Ray,
+  ScreenSpaceEventHandler,
+  ScreenSpaceEventType,
+  defined,
+} from "cesium";
+
+export class VisibilityAnalysis {
+  handler: any;
+  planeLineEntityList: any[];
+  pointList: any[];
+  tileset: any;
+  constructor() {
+    this.planeLineEntityList = [];
+    this.pointList = [];
+    this.handler = new ScreenSpaceEventHandler(window.Viewer.scene.canvas);
+    this.add3DTile(
+      "http://localhost:8091/Cesium3DTiles/Tilesets/Tileset/tileset.json"
+    );
+  }
+
+  // * 添加3DTile
+  async add3DTile(url: string) {
+    this.tileset = await Cesium3DTileset.fromUrl(url, {});
+
+    window.Viewer.scene.primitives.add(this.tileset);
+    window.Viewer.zoomTo(this.tileset, new HeadingPitchRange(0.0, -0.3, 0.0));
+  }
+
+  // * 创建中间线条entity以适应动态数据
+  createLineEntity(isGround: boolean): Entity {
+    const update = () => {
+      return this.pointList;
+    };
+    return window.Viewer.entities.add({
+      polyline: new PolylineGraphics({
+        positions: new CallbackProperty(update, false),
+        show: true,
+        material: Color.BLUE,
+        clampToGround: isGround,
+      }),
+    });
+  }
+
+  // * 绘制线
+  drawLine(leftPoint: Cartesian3, secPoint: Cartesian3, color: Color) {
+    const line = window.Viewer.entities.add({
+      polyline: {
+        positions: [leftPoint, secPoint],
+        width: 1,
+        material: color,
+        depthFailMaterial: color,
+      },
+    });
+    this.planeLineEntityList.push(line);
+  }
+
+  analysisVisible() {
+    this.pointList = [];
+    let lineEntity: null | Entity;
+    // * 监测鼠标左击事件
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      const num = this.pointList.length;
+      if (num == 1) {
+        this.pointList.push(cartesian.clone());
+      } else {
+        this.pointList.push(cartesian.clone());
+        window.Viewer.entities.remove(lineEntity);
+
+        // 计算射线的方向
+        const direction = Cartesian3.normalize(
+          Cartesian3.subtract(
+            this.pointList[1],
+            this.pointList[0],
+            new Cartesian3()
+          ),
+          new Cartesian3()
+        );
+        // 建立射线
+        const ray = new Ray(this.pointList[0], direction);
+        const result = window.Viewer.scene.globe.pick(ray, window.Viewer.scene); // 计算交互点,返回第一个
+
+        if (result !== undefined && result !== null) {
+          this.drawLine(result, this.pointList[0], Color.GREEN); // 可视区域
+          this.drawLine(result, this.pointList[1], Color.RED); // 不可视区域
+        } else {
+          const tileResult = window.Viewer.scene.pickFromRay(ray);
+          if (defined(tileResult) && defined(tileResult.object)) {
+            this.drawLine(tileResult.position, this.pointList[0], Color.GREEN); // 可视区域
+            this.drawLine(tileResult.position, this.pointList[1], Color.RED); // 不可视区域
+          } else {
+            this.drawLine(this.pointList[0], this.pointList[1], Color.GREEN);
+          }
+        }
+        this.destoryHandler();
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+
+    // * 监测鼠标移动事件
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!defined(cartesian)) return;
+
+      if (this.pointList.length == 2 && !lineEntity) {
+        lineEntity = this.createLineEntity(false);
+      }
+
+      this.pointList.pop();
+      this.pointList.push(cartesian);
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+
+  //   * 清除销毁
+  destoryHandler() {
+    this.handler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+    this.handler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
+    this.handler.removeInputAction(ScreenSpaceEventType.RIGHT_CLICK);
+  }
+
+  //   * 清除所有已绘制图形
+  clearAll() {
+    this.destoryHandler();
+    this.planeLineEntityList.forEach((item) => {
+      window.Viewer.entities.remove(item);
+    });
+    this.planeLineEntityList = [];
+    this.pointList = [];
+  }
+
+  //   * 销毁实例
+  destory() {
+    this.clearAll();
+    window.Viewer.scene.primitives.remove(this.tileset);
+    this.handler.destroy();
+    this.handler = undefined;
+  }
+}

+ 61 - 0
src/views/map/index.vue

@@ -0,0 +1,61 @@
+<template>
+  <div id="cesiumContainer"></div>
+</template>
+<script setup lang="ts">
+import { onMounted } from "vue";
+import { Viewer, Rectangle, Ion, createWorldTerrainAsync } from "cesium";
+import CesiumNavigation from "cesium-navigation-es6";
+onMounted(async () => {
+  Ion.defaultAccessToken =
+    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI3MTNiMzk1My0zZmRlLTRmYjQtYTBlZC0wMTdhYjAzMTFiMTAiLCJpZCI6MTQ3NjQsInNjb3BlcyI6WyJhc2wiLCJhc3IiLCJhc3ciLCJnYyJdLCJpYXQiOjE1NjYzMDc3NjZ9.ddc1YqTozjcAmoYQBE2Na5gr8RClBNKeB8QfkAwyPqk";
+  window.Viewer = new Viewer("cesiumContainer", {
+    animation: true, // * 左下角圆盘 速度控制器
+    shouldAnimate: true, // * 当动画控件出现,用来控制是否通过旋转控件,旋转场景
+    baseLayerPicker: false, // * 右上角图层选择器
+    fullscreenButton: false, // * 右下角全屏按钮
+    vrButton: false, // * 右下角vr按钮
+    homeButton: false, // * 右上角地图恢复到初始页面按钮
+    selectionIndicator: false, // * 点击后地图上显示的选择控件
+    infoBox: false, // * 右上角鼠标点击后信息展示框
+    sceneModePicker: false, // * 右上角2D和3D之间的切换
+    timeline: true, // * 页面下方的时间条
+    navigationHelpButton: false, // * 右上角帮助按钮
+    navigationInstructionsInitiallyVisible: false, // * 是否展开帮助
+    scene3DOnly: true, // * 如果设置为true,则所有几何图形以3D模式绘制以节约GPU资源
+    useDefaultRenderLoop: true, // * 控制渲染循环
+    showRenderLoopErrors: false, // * HTML面板中显示错误信息
+    useBrowserRecommendedResolution: true, // * 如果为true,则以浏览器建议的分辨率渲染并忽略window.devicePixelRatio
+    automaticallyTrackDataSourceClocks: true, // * 自动追踪最近添加的数据源的时钟设置
+    orderIndependentTranslucency: true, // * 如果为true并且配置支持它,则使用顺序无关的半透明性
+    shadows: false, // * 阴影效果
+    projectionPicker: false, // * 透视投影和正投影之间切换
+    requestRenderMode: true, // * 在指定情况下进行渲染,提高性能
+    terrainProvider: await createWorldTerrainAsync(),
+  });
+  window.Viewer._cesiumWidget._creditContainer.style.display = "none"; // * 隐藏版权信息
+  window.Viewer.scene.globe.depthTestAgainstTerrain = true; // * 开启深度测试
+
+  interface CesiumNavigationOptions {
+    defaultResetView: Rectangle; // * 用于在使用重置导航重置地图视图时设置默认视图控制。接受的值是Cesium.Cartographic 和 Cesium.Rectangle.
+    enableCompass: boolean; // * 用于启用或禁用罗盘。true是启用罗盘,false是禁用罗盘。默认值为true。如果将选项设置为false,则罗盘将不会添加到地图中。
+    enableZoomControls: boolean; // * 用于启用或禁用缩放控件。true是启用,false是禁用。默认值为true。如果将选项设置为false,则缩放控件将不会添加到地图中。
+    enableDistanceLegend: boolean; // * 用于启用或禁用比例尺。true是启用,false是禁用。默认值为true。如果将选项设置为false,比例尺将不会添加到地图中。
+    enableCompassOuterRing: boolean; // * 用于启用或禁用指南针外环。true是启用,false是禁用。默认值为true。如果将选项设置为false,则该环将可见但无效。
+  }
+  var options: CesiumNavigationOptions = {
+    defaultResetView: Rectangle.fromDegrees(80, 22, 130, 50),
+    enableCompass: true,
+    enableZoomControls: false,
+    enableDistanceLegend: false,
+    enableCompassOuterRing: true,
+  };
+
+  new CesiumNavigation(window.Viewer, options);
+});
+</script>
+<style scoped>
+#cesiumContainer {
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 179 - 0
src/views/map/plot/graphicsDraw/areaDraw/algorithm.ts

@@ -0,0 +1,179 @@
+import type { Cartesian3 } from "cesium";
+import type { AllPlotI } from "../../interface";
+import type { PointArr } from "../../interface";
+import { P, lonLatToCartesian } from "../../tools";
+
+const curseParams = {
+  t: 0.3,
+};
+const gatheringPlaceParams = {
+  t: 0.4,
+};
+
+export const areaPlot: AllPlotI = {
+  version: "1.0.0",
+  createTime: "2023-2-26",
+  updateTime: "2023-2-26",
+  author: "c-lei-en",
+  algorithm: {
+    // * 获取弓形坐标
+    getArcPositions: (pnts: PointArr[]): Cartesian3[] => {
+      let pnt;
+      if (pnts.length == 2 || pnts[1].toString() == pnts[2].toString()) {
+        const mid = P.PlotUtils.mid(pnts[0], pnts[1]);
+        const d = P.PlotUtils.distance(pnts[0], mid);
+        pnt = P.PlotUtils.getThirdPoint(pnts[0], mid, P.Constants.HALF_PI, d);
+      }
+      const pnt1 = pnts[0];
+      const pnt2 = pnts[1];
+      const pnt3 = pnt ? pnt : pnts[2];
+      const center = P.PlotUtils.getCircleCenterOfThreePoints(pnt1, pnt2, pnt3);
+      const radius = P.PlotUtils.distance(pnt1, center);
+      const angle1 = P.PlotUtils.getAzimuth(pnt1, center);
+      const angle2 = P.PlotUtils.getAzimuth(pnt2, center);
+      let startAngle, endAngle;
+      if (P.PlotUtils.isClockWise(pnt1, pnt2, pnt3)) {
+        startAngle = angle2;
+        endAngle = angle1;
+      } else {
+        startAngle = angle1;
+        endAngle = angle2;
+      }
+      const pntArr = P.PlotUtils.getArcPoints(
+        center,
+        radius,
+        startAngle,
+        endAngle
+      );
+      return pntArr;
+    },
+    // * 获取扇形坐标
+    getSectorPositions: (pnts: PointArr[]): Cartesian3[] => {
+      const center = pnts[0];
+      const radius = P.PlotUtils.distance(pnts[1], center);
+      const startAngle = P.PlotUtils.getAzimuth(pnts[1], center);
+      const endAngle = P.PlotUtils.getAzimuth(pnts[2], center);
+      const pList = P.PlotUtils.getArcPoints(
+        center,
+        radius,
+        startAngle,
+        endAngle
+      );
+      pList.push(lonLatToCartesian(center), pList[0]);
+      return pList;
+    },
+    // * 获取矩形坐标
+    getRectanglePositions: (pnt1: PointArr, pnt2: PointArr): PointArr => {
+      const xmin = Math.min(pnt1[0], pnt2[0]);
+      const xmax = Math.max(pnt1[0], pnt2[0]);
+      const ymin = Math.min(pnt1[1], pnt2[1]);
+      const ymax = Math.max(pnt1[1], pnt2[1]);
+      return [xmin, xmax, ymin, ymax];
+    },
+    // * 获取曲线面坐标
+    getClosedCurvePositions: (pnts: PointArr[]): Cartesian3[] => {
+      if (pnts.length == 2 || pnts[1].toString() == pnts[2].toString()) {
+        const mid = P.PlotUtils.mid(pnts[0], pnts[1]);
+        const d = P.PlotUtils.distance(pnts[0], mid);
+        const pnt = P.PlotUtils.getThirdPoint(
+          pnts[0],
+          mid,
+          P.Constants.HALF_PI,
+          d
+        );
+        pnts.push(pnt);
+      }
+      pnts.push(pnts[0], pnts[1]);
+      let normals: any = [];
+      for (let i = 0; i < pnts.length - 2; i++) {
+        const normalPoints = P.PlotUtils.getBisectorNormals(
+          curseParams.t,
+          pnts[i],
+          pnts[i + 1],
+          pnts[i + 2]
+        );
+        normals = normals.concat(normalPoints);
+      }
+      const count = normals.length;
+      normals = [normals[count - 1]].concat(normals.slice(0, count - 1));
+
+      const pList = [];
+      for (let i = 0; i < pnts.length - 2; i++) {
+        const pnt1 = pnts[i];
+        const pnt2 = pnts[i + 1];
+        pList.push(pnt1);
+        for (let t = 0; t <= P.Constants.FITTING_COUNT; t++) {
+          const pnt = P.PlotUtils.getCubicValue(
+            t / P.Constants.FITTING_COUNT,
+            pnt1,
+            normals[i * 2],
+            normals[i * 2 + 1],
+            pnt2
+          );
+          pList.push(pnt);
+        }
+        pList.push(pnt2);
+      }
+      const cartesianList = [];
+      for (let i = 0, len = pList.length; i < len - 1; i++) {
+        cartesianList.push(lonLatToCartesian(pList[i]));
+      }
+      return cartesianList;
+    },
+    // * 获取聚集地坐标
+    getGatheringPlacePositions: (pnts: PointArr[]) => {
+      if (pnts.length == 2) {
+        const mid = P.PlotUtils.mid(pnts[0], pnts[1]);
+        const d = P.PlotUtils.distance(pnts[0], mid) / 0.9;
+        const pnt = P.PlotUtils.getThirdPoint(
+          pnts[0],
+          mid,
+          P.Constants.HALF_PI,
+          d,
+          true
+        );
+        pnts = [pnts[0], pnt, pnts[1]];
+      }
+      const mid = P.PlotUtils.mid(pnts[0], pnts[2]);
+      pnts.push(mid, pnts[0], pnts[1]);
+
+      let normals: any = [];
+      for (let i = 0; i < pnts.length - 2; i++) {
+        const pnt1 = pnts[i];
+        const pnt2 = pnts[i + 1];
+        const pnt3 = pnts[i + 2];
+        const normalPoints = P.PlotUtils.getBisectorNormals(
+          gatheringPlaceParams.t,
+          pnt1,
+          pnt2,
+          pnt3
+        );
+        normals = normals.concat(normalPoints);
+      }
+      const count = normals.length;
+      normals = [normals[count - 1]].concat(normals.slice(0, count - 1));
+      const pList = [];
+      for (let i = 0; i < pnts.length - 2; i++) {
+        const pnt1 = pnts[i];
+        const pnt2 = pnts[i + 1];
+        pList.push(pnt1);
+        for (let t = 0; t <= P.Constants.FITTING_COUNT; t++) {
+          const pnt = P.PlotUtils.getCubicValue(
+            t / P.Constants.FITTING_COUNT,
+            pnt1,
+            normals[i * 2],
+            normals[i * 2 + 1],
+            pnt2
+          );
+          pList.push(pnt);
+        }
+        pList.push(pnt2);
+      }
+      const cartesianList = [];
+      for (let i = 0, len = pList.length; i < len - 1; i++) {
+        cartesianList.push(lonLatToCartesian(pList[i]));
+      }
+      return cartesianList;
+    },
+  },
+};

+ 1824 - 0
src/views/map/plot/graphicsDraw/areaDraw/index.ts

@@ -0,0 +1,1824 @@
+import {
+  Appearance,
+  Billboard,
+  CallbackProperty,
+  Cartesian3,
+  Color,
+  defined,
+  EllipseGeometry,
+  Entity,
+  GeometryInstance,
+  GroundPrimitive,
+  HeightReference,
+  Material,
+  Primitive,
+  ScreenSpaceEventHandler,
+  ScreenSpaceEventType,
+  VerticalOrigin,
+  Math as cesiumMath,
+  PolygonGeometry,
+  PolygonHierarchy,
+  Rectangle as cesiumRectangle,
+  RectangleGeometry,
+  BillboardCollection,
+} from "cesium";
+import { getCatesian3FromPX, cartesianToLonlat } from "../../tools";
+import type { BaseAreaI, PlotFuncI, PointArr } from "./../../interface";
+import { areaPlot } from "./algorithm";
+import emitter from "@/mitt";
+
+class BaseArea implements BaseAreaI {
+  type: string;
+  baseType: string;
+  objId: number;
+  handler: any;
+  state: number;
+  step: number;
+  floatPoint: any;
+  floatPointArr: any;
+  areaPrimitive: any;
+  areaEntity: any;
+  modifyHandler: any;
+  pointList: any[];
+  material: any;
+  selectPoint: any;
+  clickStep: number;
+  constructor(obj: BaseAreaI) {
+    this.type = obj.type;
+    this.baseType = "area";
+    this.objId = obj.objId;
+    this.handler = obj.handler;
+    this.areaPrimitive = obj.areaPrimitive;
+    this.areaEntity = obj.areaEntity;
+    this.state = obj.state; //state用于区分当前的状态 0 为删除 1为添加 2为编辑
+    this.step = obj.step;
+    this.floatPoint = obj.floatPoint;
+    this.floatPointArr = obj.floatPointArr;
+    this.modifyHandler = obj.modifyHandler;
+    this.pointList = obj.pointList;
+    this.material = obj.material;
+    this.selectPoint = obj.selectPoint;
+    this.clickStep = obj.clickStep;
+  }
+  disable() {
+    if (this.areaEntity) {
+      window.Viewer.entities.remove(this.areaEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.floatPointArr.forEach((item: Billboard) => {
+        window.Viewer.billboards.remove(item);
+      });
+      this.areaEntity = null;
+    }
+    this.state = -1;
+    this.stopDraw();
+  }
+  stopDraw() {
+    if (this.handler) {
+      this.handler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+      this.handler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
+      this.handler.removeInputAction(ScreenSpaceEventType.RIGHT_CLICK);
+      this.handler.destroy();
+      this.handler = null;
+    }
+    if (this.modifyHandler) {
+      this.modifyHandler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+      this.modifyHandler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
+      this.modifyHandler.removeInputAction(ScreenSpaceEventType.RIGHT_CLICK);
+      this.modifyHandler.destroy();
+      this.modifyHandler = null;
+    }
+  }
+  creatPoint(cartesian: number[]): Primitive {
+    if (!window.Viewer.billboards)
+      window.Viewer.billboards = window.Viewer.scene.primitives.add(
+        new BillboardCollection({
+          scene: window.Viewer.scene,
+        })
+      );
+    return window.Viewer.billboards.add({
+      id: "moveBillboard",
+      position: cartesian,
+      image: "/src/assets/icon/point.png",
+      verticalOrigin: VerticalOrigin.BOTTOM,
+      heightReference: HeightReference.CLAMP_TO_GROUND,
+    });
+  }
+}
+
+// * circle 圆
+class Circle extends BaseArea implements PlotFuncI {
+  constructor() {
+    super({
+      type: "Circle",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      areaPrimitive: null,
+      areaEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+      if (this.pointList.length == 3) {
+        this.state = -1;
+        this.pointList.pop();
+        this.areaPrimitive = this.showPrimitiveOnMap();
+        window.Viewer.entities.remove(this.areaEntity);
+        window.Viewer.billboards.remove(this.floatPoint);
+        this.areaEntity = null;
+        this.floatPoint = null;
+        this.stopDraw();
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.areaEntity) {
+        this.areaEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.areaEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.areaPrimitive);
+    this.areaPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.areaPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.areaEntity);
+          this.areaEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.areaPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const distance = Cartesian3.distance(this.pointList[0], this.pointList[1]);
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new EllipseGeometry({
+        center: this.pointList[0],
+        semiMajorAxis: distance,
+        semiMinorAxis: distance,
+      }),
+    });
+    const primitive = window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+    emitter.emit("drawEnd");
+    return primitive;
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算曲线,若有两个点则应该直接返回两个点的连线,若有三个点则返回处理后的坐标集合
+      if (this.pointList.length == 2) {
+        return this.pointList[0];
+      }
+    };
+    // * 通过坐标计算半径
+    const computeDistance = () => {
+      return Cartesian3.distance(this.pointList[0], this.pointList[1]);
+    };
+    return window.Viewer.entities.add({
+      position: new CallbackProperty(update, false),
+      ellipse: {
+        material: Color.BLUE,
+        clampToGround: true,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+        semiMajorAxis: new CallbackProperty(computeDistance, false),
+        semiMinorAxis: new CallbackProperty(computeDistance, false),
+      },
+    });
+  }
+}
+
+// * ellipse 椭圆
+class Ellipse extends BaseArea implements PlotFuncI {
+  constructor() {
+    super({
+      type: "Ellipse",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      areaPrimitive: null,
+      areaEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+      if (this.pointList.length == 3) {
+        this.state = -1;
+        this.pointList.pop();
+        this.areaPrimitive = this.showPrimitiveOnMap();
+        window.Viewer.entities.remove(this.areaEntity);
+        window.Viewer.billboards.remove(this.floatPoint);
+        this.areaEntity = null;
+        this.floatPoint = null;
+        this.stopDraw();
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.areaEntity) {
+        this.areaEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.areaEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.areaPrimitive);
+    this.areaPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.areaPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.areaEntity);
+          this.areaEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.areaPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const maxDistance = Cartesian3.distance(
+      this.pointList[0],
+      this.pointList[1]
+    );
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new EllipseGeometry({
+        center: this.pointList[0],
+        semiMajorAxis: maxDistance,
+        semiMinorAxis: maxDistance / 2,
+        rotation: this.computeRoate(),
+      }),
+    });
+    const primitive = window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+    emitter.emit("drawEnd");
+    return primitive;
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算曲线,若有两个点则应该直接返回两个点的连线,若有三个点则返回处理后的坐标集合
+      if (this.pointList.length == 2) {
+        return this.pointList[0];
+      }
+    };
+    // * 通过坐标计算长半轴
+    const computeMaxDistance = () => {
+      const maxDistance = Cartesian3.distance(
+        this.pointList[0],
+        this.pointList[1]
+      );
+      return maxDistance;
+    };
+    // * 通过坐标计算短半轴
+    const computeMinDistance = () => {
+      const minDistance =
+        Cartesian3.distance(this.pointList[0], this.pointList[1]) / 2;
+      return minDistance;
+    };
+    // * 计算椭圆朝向
+    const computeRoate = () => {
+      return this.computeRoate.apply(this);
+    };
+    return window.Viewer.entities.add({
+      position: new CallbackProperty(update, false),
+      ellipse: {
+        material: Color.BLUE,
+        clampToGround: true,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+        semiMajorAxis: new CallbackProperty(computeMaxDistance, false),
+        semiMinorAxis: new CallbackProperty(computeMinDistance, false),
+        rotation: new CallbackProperty(computeRoate, false),
+      },
+    });
+  }
+  computeRoate() {
+    if (
+      (this.pointList[0].x >= this.pointList[1].x &&
+        this.pointList[0].y >= this.pointList[1].y) ||
+      (this.pointList[0].x < this.pointList[1].x &&
+        this.pointList[0].y < this.pointList[1].y)
+    ) {
+      return Math.atan2(
+        Math.abs(this.pointList[0].y - this.pointList[1].y),
+        Math.abs(this.pointList[0].x - this.pointList[1].x)
+      );
+    } else {
+      return (
+        cesiumMath.toRadians(cesiumMath.TWO_PI) -
+        Math.atan2(
+          Math.abs(this.pointList[0].y - this.pointList[1].y),
+          Math.abs(this.pointList[0].x - this.pointList[1].x)
+        )
+      );
+    }
+  }
+}
+
+// * lune 弓形
+class Lune extends BaseArea implements PlotFuncI {
+  constructor() {
+    super({
+      type: "Lune",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      areaPrimitive: null,
+      areaEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+      if (this.pointList.length == 4) {
+        this.state = -1;
+        this.pointList.pop();
+        this.areaPrimitive = this.showPrimitiveOnMap();
+        window.Viewer.entities.remove(this.areaEntity);
+        window.Viewer.billboards.remove(this.floatPoint);
+        this.areaEntity = null;
+        this.floatPoint = null;
+        this.stopDraw();
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.areaEntity) {
+        this.areaEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.areaEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.areaPrimitive);
+    this.areaPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.areaPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.areaEntity);
+          this.areaEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.areaPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(
+          areaPlot.algorithm.getArcPositions(lnglatArr)
+        ),
+      }),
+    });
+    const primitive = window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+    emitter.emit("drawEnd");
+    return primitive;
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      if (lnglatArr.length == 2) {
+        lnglatArr.push([lnglatArr[1][0] + 0.0000001, lnglatArr[1][1]]);
+      }
+      const res = areaPlot.algorithm.getArcPositions(lnglatArr);
+      return new PolygonHierarchy(res);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * Sector 扇形
+class Sector extends BaseArea implements PlotFuncI {
+  constructor() {
+    super({
+      type: "Sector",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      areaPrimitive: null,
+      areaEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+      if (this.pointList.length == 4) {
+        this.state = -1;
+        this.pointList.pop();
+        this.areaPrimitive = this.showPrimitiveOnMap();
+        window.Viewer.entities.remove(this.areaEntity);
+        window.Viewer.billboards.remove(this.floatPoint);
+        this.areaEntity = null;
+        this.floatPoint = null;
+        this.stopDraw();
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.areaEntity) {
+        this.areaEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.areaEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.areaPrimitive);
+    this.areaPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.areaPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.areaEntity);
+          this.areaEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.areaPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(
+          areaPlot.algorithm.getSectorPositions(lnglatArr)
+        ),
+      }),
+    });
+    const primitives = window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+    emitter.emit("drawEnd");
+    return primitives;
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      if (lnglatArr.length == 2) {
+        lnglatArr.push([lnglatArr[1][0] + 0.0000001, lnglatArr[1][1]]);
+      }
+      const res = areaPlot.algorithm.getSectorPositions(lnglatArr);
+      return new PolygonHierarchy(res);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * Rectangle 矩形
+class Rectangle extends BaseArea implements PlotFuncI {
+  constructor() {
+    super({
+      type: "Rectangle",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      areaPrimitive: null,
+      areaEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+      if (this.pointList.length == 3) {
+        this.state = -1;
+        this.pointList.pop();
+        this.areaPrimitive = this.showPrimitiveOnMap();
+        window.Viewer.entities.remove(this.areaEntity);
+        window.Viewer.billboards.remove(this.floatPoint);
+        this.areaEntity = null;
+        this.floatPoint = null;
+        this.stopDraw();
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.areaEntity) {
+        this.areaEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.areaEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.areaPrimitive);
+    this.areaPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.areaPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.areaEntity);
+          this.areaEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.areaPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const res = areaPlot.algorithm.getRectanglePositions(
+      lnglatArr[0],
+      lnglatArr[1]
+    );
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new RectangleGeometry({
+        rectangle: cesiumRectangle.fromDegrees(res[0], res[2], res[1], res[3]),
+      }),
+    });
+    const primitive = window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+    emitter.emit("drawEnd");
+    return primitive;
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      const res = areaPlot.algorithm.getRectanglePositions(
+        lnglatArr[0],
+        lnglatArr[1]
+      );
+      return cesiumRectangle.fromDegrees(res[0], res[2], res[1], res[3]);
+    };
+    return window.Viewer.entities.add({
+      rectangle: {
+        coordinates: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * closedCurve 曲线面
+class ClosedCurve extends BaseArea implements PlotFuncI {
+  constructor() {
+    super({
+      type: "ClosedCurve",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      areaPrimitive: null,
+      areaEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.areaEntity) {
+        this.areaEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 鼠标右击完成绘制
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 3) return;
+      this.state = -1;
+      this.pointList.pop();
+      this.areaPrimitive = this.showPrimitiveOnMap();
+      window.Viewer.entities.remove(this.areaEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.areaEntity = null;
+      this.floatPoint = null;
+      this.stopDraw();
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.areaEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.areaPrimitive);
+    this.areaPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.areaPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.areaEntity);
+          this.areaEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.areaPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const res = areaPlot.algorithm.getClosedCurvePositions(lnglatArr);
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(res),
+      }),
+    });
+    const primitive = window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+    emitter.emit("drawEnd");
+    return primitive;
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      const res = areaPlot.algorithm.getClosedCurvePositions(lnglatArr);
+      return new PolygonHierarchy(res);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * polygon 多边形
+class Polygon extends BaseArea implements PlotFuncI {
+  constructor() {
+    super({
+      type: "Polygon",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      areaPrimitive: null,
+      areaEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.areaEntity) {
+        this.areaEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 鼠标右击完成绘制
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 3) return;
+      this.state = -1;
+      this.pointList.pop();
+      this.areaPrimitive = this.showPrimitiveOnMap();
+      window.Viewer.entities.remove(this.areaEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.areaEntity = null;
+      this.floatPoint = null;
+      this.stopDraw();
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.areaEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.areaPrimitive);
+    this.areaPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.areaPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.areaEntity);
+          this.areaEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.areaPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(this.pointList),
+      }),
+    });
+    const primitive = window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+    emitter.emit("drawEnd");
+    return primitive;
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      return new PolygonHierarchy(this.pointList);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * freeHandPolygon 自由面
+class FreeHandPolygon extends BaseArea implements PlotFuncI {
+  constructor() {
+    super({
+      type: "FreeHandPolygon",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      areaPrimitive: null,
+      areaEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.areaEntity) {
+        this.areaEntity = this.createEntity();
+      } else if (this.pointList.length >= 2) {
+        // * 随着鼠标移动添加数据
+        this.pointList.push(cartesian.clone());
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 鼠标右击完成绘制
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 3) return;
+      this.state = -1;
+      this.pointList.pop();
+      this.areaPrimitive = this.showPrimitiveOnMap();
+      window.Viewer.entities.remove(this.areaEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.areaEntity = null;
+      this.floatPoint = null;
+      this.stopDraw();
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.areaEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.areaPrimitive);
+    this.areaPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.areaPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.areaEntity);
+          this.areaEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.areaPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(this.pointList),
+      }),
+    });
+    const primitive = window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+    emitter.emit("drawEnd");
+    return primitive;
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      return new PolygonHierarchy(this.pointList);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * gatheringPlace 聚集地
+class GatheringPlace extends BaseArea implements PlotFuncI {
+  constructor() {
+    super({
+      type: "GatheringPlace",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      areaPrimitive: null,
+      areaEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+      if (this.pointList.length == 4) {
+        this.state = -1;
+        this.pointList.pop();
+        this.areaPrimitive = this.showPrimitiveOnMap();
+        window.Viewer.entities.remove(this.areaEntity);
+        window.Viewer.billboards.remove(this.floatPoint);
+        this.areaEntity = null;
+        this.floatPoint = null;
+        this.stopDraw();
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.areaEntity) {
+        this.areaEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.areaEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.areaPrimitive);
+    this.areaPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.areaPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.areaEntity);
+          this.areaEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.areaPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    // * 计算坐标
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const res = areaPlot.algorithm.getGatheringPlacePositions(lnglatArr);
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(res),
+      }),
+    });
+    const primitive = window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+    emitter.emit("drawEnd");
+    return primitive;
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      if (lnglatArr.length == 2) {
+        lnglatArr.push([lnglatArr[1][0] + 0.0000001, lnglatArr[1][1]]);
+      }
+      const res = areaPlot.algorithm.getGatheringPlacePositions(lnglatArr);
+      return new PolygonHierarchy(res);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+export {
+  Circle,
+  Ellipse,
+  Lune,
+  Sector,
+  Rectangle,
+  ClosedCurve,
+  Polygon,
+  FreeHandPolygon,
+  GatheringPlace,
+};

+ 756 - 0
src/views/map/plot/graphicsDraw/arrowDraw/algorithm.ts

@@ -0,0 +1,756 @@
+import type { Cartesian3 } from "cesium";
+import type { AllPlotI } from "../../interface";
+import type { PointArr } from "../../interface";
+import { P, lonLatToCartesian } from "../../tools";
+
+const doubleArrowParams = {
+  headHeightFactor: 0.25,
+  headWidthFactor: 0.3,
+  neckHeightFactor: 0.85,
+  neckWidthFactor: 0.15,
+};
+
+// * 细直箭头与突击方向type
+type FineOrAssault = {
+  tailWidthFactor: number;
+  neckWidthFactor: number;
+  headWidthFactor: number;
+  headAngle: number;
+  neckAngle: number;
+};
+
+const fineArrowParams: FineOrAssault = {
+  tailWidthFactor: 0.15,
+  neckWidthFactor: 0.2,
+  headWidthFactor: 0.25,
+  headAngle: Math.PI / 8.5,
+  neckAngle: Math.PI / 13,
+};
+
+const assaultDirectionParams: FineOrAssault = {
+  tailWidthFactor: 0.2,
+  neckWidthFactor: 0.25,
+  headWidthFactor: 0.3,
+  headAngle: Math.PI / 4,
+  neckAngle: Math.PI * 0.17741,
+};
+
+const attackArrowParams = {
+  headHeightFactor: 0.18,
+  headWidthFactor: 0.3,
+  neckHeightFactor: 0.85,
+  neckWidthFactor: 0.15,
+  headTailFactor: 0.8,
+  tailWidthFactor: 0.1,
+  swallowTailFactor: 1,
+};
+
+const squadCombatParams = {
+  headHeightFactor: 0.18,
+  headWidthFactor: 0.3,
+  neckHeightFactor: 0.85,
+  neckWidthFactor: 0.15,
+  tailWidthFactor: 0.1,
+  swallowTailFactor: 1,
+};
+
+export const arrowPlot: AllPlotI = {
+  version: "1.0.0",
+  createTime: "2023-3-3",
+  updateTime: "2023-3-3",
+  author: "c-lei-en",
+  algorithm: {
+    // * 计算对称点
+    getTempPoint4: (
+      pnt1: PointArr,
+      pnt2: PointArr,
+      point: PointArr
+    ): PointArr => {
+      const midPnt = P.PlotUtils.mid(pnt1, pnt2);
+      const len = P.PlotUtils.distance(midPnt, point);
+      const angle = P.PlotUtils.getAngleOfThreePoints(pnt1, midPnt, point);
+      let symPnt, distance1, distance2, mid;
+      if (angle < P.Constants.HALF_PI) {
+        distance1 = len * Math.sin(angle);
+        distance2 = len * Math.cos(angle);
+        mid = P.PlotUtils.getThirdPoint(
+          pnt1,
+          midPnt,
+          P.Constants.HALF_PI,
+          distance1,
+          false
+        );
+        symPnt = P.PlotUtils.getThirdPoint(
+          midPnt,
+          mid,
+          P.Constants.HALF_PI,
+          distance2,
+          true
+        );
+      } else if (angle >= P.Constants.HALF_PI && angle < Math.PI) {
+        distance1 = len * Math.sin(Math.PI - angle);
+        distance2 = len * Math.cos(Math.PI - angle);
+        mid = P.PlotUtils.getThirdPoint(
+          pnt1,
+          midPnt,
+          P.Constants.HALF_PI,
+          distance1,
+          false
+        );
+        symPnt = P.PlotUtils.getThirdPoint(
+          midPnt,
+          mid,
+          P.Constants.HALF_PI,
+          distance2,
+          false
+        );
+      } else if (angle >= Math.PI && angle < Math.PI * 1.5) {
+        distance1 = len * Math.sin(angle - Math.PI);
+        distance2 = len * Math.cos(angle - Math.PI);
+        mid = P.PlotUtils.getThirdPoint(
+          pnt1,
+          midPnt,
+          P.Constants.HALF_PI,
+          distance1,
+          true
+        );
+        symPnt = P.PlotUtils.getThirdPoint(
+          midPnt,
+          mid,
+          P.Constants.HALF_PI,
+          distance2,
+          true
+        );
+      } else {
+        distance1 = len * Math.sin(Math.PI * 2 - angle);
+        distance2 = len * Math.cos(Math.PI * 2 - angle);
+        mid = P.PlotUtils.getThirdPoint(
+          pnt1,
+          midPnt,
+          P.Constants.HALF_PI,
+          distance1,
+          true
+        );
+        symPnt = P.PlotUtils.getThirdPoint(
+          midPnt,
+          mid,
+          P.Constants.HALF_PI,
+          distance2,
+          false
+        );
+      }
+      return symPnt;
+    },
+    // * 获取箭头坐标
+    getArrowHeadPoints: (points: PointArr): PointArr => {
+      const len = P.PlotUtils.getBaseLength(points);
+      const headHeight = len * doubleArrowParams.headHeightFactor;
+      const headPnt = points[points.length - 1];
+      const headWidth = headHeight * doubleArrowParams.headWidthFactor;
+      const neckWidth = headHeight * doubleArrowParams.neckWidthFactor;
+      const neckHeight = headHeight * doubleArrowParams.neckHeightFactor;
+      const headEndPnt = P.PlotUtils.getThirdPoint(
+        points[points.length - 2],
+        headPnt,
+        0,
+        headHeight,
+        true
+      );
+      const neckEndPnt = P.PlotUtils.getThirdPoint(
+        points[points.length - 2],
+        headPnt,
+        0,
+        neckHeight,
+        true
+      );
+      const headLeft = P.PlotUtils.getThirdPoint(
+        headPnt,
+        headEndPnt,
+        P.Constants.HALF_PI,
+        headWidth,
+        false
+      );
+      const headRight = P.PlotUtils.getThirdPoint(
+        headPnt,
+        headEndPnt,
+        P.Constants.HALF_PI,
+        headWidth,
+        true
+      );
+      const neckLeft = P.PlotUtils.getThirdPoint(
+        headPnt,
+        neckEndPnt,
+        P.Constants.HALF_PI,
+        neckWidth,
+        false
+      );
+      const neckRight = P.PlotUtils.getThirdPoint(
+        headPnt,
+        neckEndPnt,
+        P.Constants.HALF_PI,
+        neckWidth,
+        true
+      );
+      return [neckLeft, headLeft, headPnt, headRight, neckRight];
+    },
+    // * 获取钳击箭身坐标
+    getArrowBodyPoints: (
+      points: PointArr,
+      neckLeft: PointArr,
+      neckRight: PointArr,
+      tailWidthFactor: number
+    ): PointArr => {
+      const allLen = P.PlotUtils.wholeDistance(points);
+      const len = P.PlotUtils.getBaseLength(points);
+      const tailWidth = len * tailWidthFactor;
+      const neckWidth = P.PlotUtils.distance(neckLeft, neckRight);
+      const widthDif = (tailWidth - neckWidth) / 2;
+      let tempLen = 0;
+      const leftBodyPnts = [],
+        rightBodyPnts = [];
+      for (let i = 1; i < points.length - 1; i++) {
+        const angle =
+          P.PlotUtils.getAngleOfThreePoints(
+            points[i - 1],
+            points[i],
+            points[i + 1]
+          ) / 2;
+        tempLen += P.PlotUtils.distance(points[i - 1], points[i]);
+        const w =
+          (tailWidth / 2 - (tempLen / allLen) * widthDif) / Math.sin(angle);
+        const left = P.PlotUtils.getThirdPoint(
+          points[i - 1],
+          points[i],
+          Math.PI - angle,
+          w,
+          true
+        );
+        const right = P.PlotUtils.getThirdPoint(
+          points[i - 1],
+          points[i],
+          angle,
+          w,
+          false
+        );
+        leftBodyPnts.push(left);
+        rightBodyPnts.push(right);
+      }
+      return leftBodyPnts.concat(rightBodyPnts);
+    },
+    // * 获取箭头点
+    getArrowPoints: (
+      pnt1: PointArr,
+      pnt2: PointArr,
+      pnt3: PointArr,
+      clockWise: number
+    ): PointArr => {
+      const midPnt = P.PlotUtils.mid(pnt1, pnt2);
+      const len = P.PlotUtils.distance(midPnt, pnt3);
+      let midPnt1 = P.PlotUtils.getThirdPoint(pnt3, midPnt, 0, len * 0.3, true);
+      let midPnt2 = P.PlotUtils.getThirdPoint(pnt3, midPnt, 0, len * 0.5, true);
+      midPnt1 = P.PlotUtils.getThirdPoint(
+        midPnt,
+        midPnt1,
+        P.Constants.HALF_PI,
+        len / 5,
+        clockWise
+      );
+      midPnt2 = P.PlotUtils.getThirdPoint(
+        midPnt,
+        midPnt2,
+        P.Constants.HALF_PI,
+        len / 4,
+        clockWise
+      );
+
+      const points = [midPnt, midPnt1, midPnt2, pnt3];
+      // 计算箭头部分
+      const arrowPnts = arrowPlot.algorithm.getArrowHeadPoints(
+        points,
+        doubleArrowParams.headHeightFactor,
+        doubleArrowParams.headWidthFactor,
+        doubleArrowParams.neckHeightFactor,
+        doubleArrowParams.neckWidthFactor
+      );
+      const neckLeftPoint = arrowPnts[0];
+      const neckRightPoint = arrowPnts[4];
+      // 计算箭身部分
+      const tailWidthFactor =
+        P.PlotUtils.distance(pnt1, pnt2) /
+        P.PlotUtils.getBaseLength(points) /
+        2;
+      const bodyPnts = arrowPlot.algorithm.getArrowBodyPoints(
+        points,
+        neckLeftPoint,
+        neckRightPoint,
+        tailWidthFactor
+      );
+      const n = bodyPnts.length;
+      let lPoints = bodyPnts.slice(0, n / 2);
+      let rPoints = bodyPnts.slice(n / 2, n);
+      lPoints.push(neckLeftPoint);
+      rPoints.push(neckRightPoint);
+      lPoints = lPoints.reverse();
+      lPoints.push(pnt2);
+      rPoints = rPoints.reverse();
+      rPoints.push(pnt1);
+      return lPoints.reverse().concat(arrowPnts, rPoints);
+    },
+    // * 获取钳击箭头坐标
+    getDoubleArrow: (pnts: PointArr[]): Cartesian3[] => {
+      const pnt1 = pnts[0];
+      const pnt2 = pnts[1];
+      const pnt3 = pnts[2];
+      let tempPoint4, connPoint;
+      if (pnts.length == 3)
+        tempPoint4 = arrowPlot.algorithm.getTempPoint4(pnt1, pnt2, pnt3);
+      else tempPoint4 = pnts[3];
+      if (pnts.length == 3 || pnts.length == 4)
+        connPoint = P.PlotUtils.mid(pnt1, pnt2);
+      else connPoint = pnts[4];
+      let leftArrowPnts, rightArrowPnts;
+      if (P.PlotUtils.isClockWise(pnt1, pnt2, pnt3)) {
+        leftArrowPnts = arrowPlot.algorithm.getArrowPoints(
+          pnt1,
+          connPoint,
+          tempPoint4,
+          false
+        );
+        rightArrowPnts = arrowPlot.algorithm.getArrowPoints(
+          connPoint,
+          pnt2,
+          pnt3,
+          true
+        );
+      } else {
+        leftArrowPnts = arrowPlot.algorithm.getArrowPoints(
+          pnt2,
+          connPoint,
+          pnt3,
+          false
+        );
+        rightArrowPnts = arrowPlot.algorithm.getArrowPoints(
+          connPoint,
+          pnt1,
+          tempPoint4,
+          true
+        );
+      }
+      const m = leftArrowPnts.length;
+      const t = (m - 5) / 2;
+
+      const llBodyPnts = leftArrowPnts.slice(0, t);
+      const lArrowPnts = leftArrowPnts.slice(t, t + 5);
+      let lrBodyPnts = leftArrowPnts.slice(t + 5, m);
+
+      let rlBodyPnts = rightArrowPnts.slice(0, t);
+      const rArrowPnts = rightArrowPnts.slice(t, t + 5);
+      const rrBodyPnts = rightArrowPnts.slice(t + 5, m);
+
+      rlBodyPnts = P.PlotUtils.getBezierPoints(rlBodyPnts);
+      const bodyPnts = P.PlotUtils.getBezierPoints(
+        rrBodyPnts.concat(llBodyPnts.slice(1))
+      );
+      lrBodyPnts = P.PlotUtils.getBezierPoints(lrBodyPnts);
+
+      const positions = rlBodyPnts.concat(
+        rArrowPnts,
+        bodyPnts,
+        lArrowPnts,
+        lrBodyPnts
+      );
+      const res = [] as Cartesian3[];
+      positions.forEach((pos: PointArr) => {
+        res.push(lonLatToCartesian(pos));
+      });
+      return res;
+    },
+    // * 获取细直箭头或者突击方向箭头坐标
+    getFineOrAssault: (pnts: PointArr, param: FineOrAssault): Cartesian3[] => {
+      const pnt1 = pnts[0];
+      const pnt2 = pnts[1];
+      const len = P.PlotUtils.getBaseLength(pnts);
+      const tailWidth = len * param.tailWidthFactor;
+      const neckWidth = len * param.neckWidthFactor;
+      const headWidth = len * param.headWidthFactor;
+      const tailLeft = P.PlotUtils.getThirdPoint(
+        pnt2,
+        pnt1,
+        P.Constants.HALF_PI,
+        tailWidth,
+        true
+      );
+      const tailRight = P.PlotUtils.getThirdPoint(
+        pnt2,
+        pnt1,
+        P.Constants.HALF_PI,
+        tailWidth,
+        false
+      );
+      const headLeft = P.PlotUtils.getThirdPoint(
+        pnt1,
+        pnt2,
+        param.headAngle,
+        headWidth,
+        false
+      );
+      const headRight = P.PlotUtils.getThirdPoint(
+        pnt1,
+        pnt2,
+        param.headAngle,
+        headWidth,
+        true
+      );
+      const neckLeft = P.PlotUtils.getThirdPoint(
+        pnt1,
+        pnt2,
+        param.neckAngle,
+        neckWidth,
+        false
+      );
+      const neckRight = P.PlotUtils.getThirdPoint(
+        pnt1,
+        pnt2,
+        param.neckAngle,
+        neckWidth,
+        true
+      );
+      return [
+        lonLatToCartesian(tailLeft),
+        lonLatToCartesian(neckLeft),
+        lonLatToCartesian(headLeft),
+        lonLatToCartesian(pnt2),
+        lonLatToCartesian(headRight),
+        lonLatToCartesian(neckRight),
+        lonLatToCartesian(tailRight),
+      ];
+    },
+    // * 获取细直箭头坐标
+    getFineArrow: (pnts: PointArr): Cartesian3[] => {
+      return arrowPlot.algorithm.getFineOrAssault(pnts, fineArrowParams);
+    },
+    // * 获取突击方向坐标
+    getAssaultDirection: (pnts: PointArr): Cartesian3[] => {
+      return arrowPlot.algorithm.getFineOrAssault(pnts, assaultDirectionParams);
+    },
+    // * 获取进攻方向箭头坐标
+    getAttackArrowHeadPoints: (
+      points: PointArr,
+      tailLeft: PointArr,
+      tailRight: PointArr
+    ): PointArr => {
+      let len = P.PlotUtils.getBaseLength(points);
+      let headHeight = len * attackArrowParams.headHeightFactor;
+      const headPnt = points[points.length - 1];
+      len = P.PlotUtils.distance(headPnt, points[points.length - 2]);
+      const tailWidth = P.PlotUtils.distance(tailLeft, tailRight);
+      if (headHeight > tailWidth * attackArrowParams.headTailFactor) {
+        headHeight = tailWidth * attackArrowParams.headTailFactor;
+      }
+      const headWidth = headHeight * attackArrowParams.headWidthFactor;
+      const neckWidth = headHeight * attackArrowParams.neckWidthFactor;
+      headHeight = headHeight > len ? len : headHeight;
+      const neckHeight = headHeight * attackArrowParams.neckHeightFactor;
+      const headEndPnt = P.PlotUtils.getThirdPoint(
+        points[points.length - 2],
+        headPnt,
+        0,
+        headHeight,
+        true
+      );
+      const neckEndPnt = P.PlotUtils.getThirdPoint(
+        points[points.length - 2],
+        headPnt,
+        0,
+        neckHeight,
+        true
+      );
+      const headLeft = P.PlotUtils.getThirdPoint(
+        headPnt,
+        headEndPnt,
+        P.Constants.HALF_PI,
+        headWidth,
+        false
+      );
+      const headRight = P.PlotUtils.getThirdPoint(
+        headPnt,
+        headEndPnt,
+        P.Constants.HALF_PI,
+        headWidth,
+        true
+      );
+      const neckLeft = P.PlotUtils.getThirdPoint(
+        headPnt,
+        neckEndPnt,
+        P.Constants.HALF_PI,
+        neckWidth,
+        false
+      );
+      const neckRight = P.PlotUtils.getThirdPoint(
+        headPnt,
+        neckEndPnt,
+        P.Constants.HALF_PI,
+        neckWidth,
+        true
+      );
+      return [neckLeft, headLeft, headPnt, headRight, neckRight];
+    },
+    // * 获取进攻方向箭身坐标
+    getAttackArrowBodyPoints: (
+      points: PointArr,
+      neckLeft: PointArr,
+      neckRight: PointArr,
+      tailWidthFactor: number
+    ): PointArr => {
+      const allLen = P.PlotUtils.wholeDistance(points);
+      const len = P.PlotUtils.getBaseLength(points);
+      const tailWidth = len * tailWidthFactor;
+      const neckWidth = P.PlotUtils.distance(neckLeft, neckRight);
+      const widthDif = (tailWidth - neckWidth) / 2;
+      let tempLen = 0;
+      const leftBodyPnts = [],
+        rightBodyPnts = [];
+      for (let i = 1; i < points.length - 1; i++) {
+        const angle =
+          P.PlotUtils.getAngleOfThreePoints(
+            points[i - 1],
+            points[i],
+            points[i + 1]
+          ) / 2;
+        tempLen += P.PlotUtils.distance(points[i - 1], points[i]);
+        const w =
+          (tailWidth / 2 - (tempLen / allLen) * widthDif) / Math.sin(angle);
+        const left = P.PlotUtils.getThirdPoint(
+          points[i - 1],
+          points[i],
+          Math.PI - angle,
+          w,
+          true
+        );
+        const right = P.PlotUtils.getThirdPoint(
+          points[i - 1],
+          points[i],
+          angle,
+          w,
+          false
+        );
+        leftBodyPnts.push(left);
+        rightBodyPnts.push(right);
+      }
+      return leftBodyPnts.concat(rightBodyPnts);
+    },
+    // * 获取进攻方向坐标
+    getAttackArrow: (pnts: PointArr[]): Cartesian3[] => {
+      // 计算箭尾
+      let tailLeft = pnts[0];
+      let tailRight = pnts[1];
+      if (P.PlotUtils.isClockWise(pnts[0], pnts[1], pnts[2])) {
+        tailLeft = pnts[1];
+        tailRight = pnts[0];
+      }
+      const midTail = P.PlotUtils.mid(tailLeft, tailRight);
+      const bonePnts = [midTail].concat(pnts.slice(2));
+      // 计算箭头
+      const headPnts = arrowPlot.algorithm.getAttackArrowHeadPoints(
+        bonePnts,
+        tailLeft,
+        tailRight
+      );
+      const neckLeft = headPnts[0];
+      const neckRight = headPnts[4];
+      const tailWidthFactor =
+        P.PlotUtils.distance(tailLeft, tailRight) /
+        P.PlotUtils.getBaseLength(bonePnts);
+      // 计算箭身
+      const bodyPnts = arrowPlot.algorithm.getAttackArrowBodyPoints(
+        bonePnts,
+        neckLeft,
+        neckRight,
+        tailWidthFactor
+      );
+      // 整合
+      const count = bodyPnts.length;
+      let leftPnts = [tailLeft].concat(bodyPnts.slice(0, count / 2));
+      leftPnts.push(neckLeft);
+      let rightPnts = [tailRight].concat(bodyPnts.slice(count / 2, count));
+      rightPnts.push(neckRight);
+
+      leftPnts = P.PlotUtils.getQBSplinePoints(leftPnts);
+      rightPnts = P.PlotUtils.getQBSplinePoints(rightPnts);
+      const positions = leftPnts.concat(headPnts, rightPnts.reverse());
+      const res = [] as Cartesian3[];
+      positions.forEach((pos: PointArr) => {
+        res.push(lonLatToCartesian(pos));
+      });
+      return res;
+    },
+    // * 获取进攻方向尾坐标
+    getTailedAttackArrow: (pnts: PointArr[]): Cartesian3[] => {
+      let tailLeft = pnts[0];
+      let tailRight = pnts[1];
+      if (P.PlotUtils.isClockWise(pnts[0], pnts[1], pnts[2])) {
+        tailLeft = pnts[1];
+        tailRight = pnts[0];
+      }
+      const midTail = P.PlotUtils.mid(tailLeft, tailRight);
+      const bonePnts = [midTail].concat(pnts.slice(2));
+      const headPnts = arrowPlot.algorithm.getAttackArrowHeadPoints(
+        bonePnts,
+        tailLeft,
+        tailRight
+      );
+      const neckLeft = headPnts[0];
+      const neckRight = headPnts[4];
+      const tailWidth = P.PlotUtils.distance(tailLeft, tailRight);
+      const allLen = P.PlotUtils.getBaseLength(bonePnts);
+      const len =
+        allLen *
+        attackArrowParams.tailWidthFactor *
+        attackArrowParams.swallowTailFactor;
+      const swallowTailPnt = P.PlotUtils.getThirdPoint(
+        bonePnts[1],
+        bonePnts[0],
+        0,
+        len,
+        true
+      );
+      const factor = tailWidth / allLen;
+      const bodyPnts = arrowPlot.algorithm.getAttackArrowBodyPoints(
+        bonePnts,
+        neckLeft,
+        neckRight,
+        factor
+      );
+      const count = bodyPnts.length;
+      let leftPnts = [tailLeft].concat(bodyPnts.slice(0, count / 2));
+      leftPnts.push(neckLeft);
+      let rightPnts = [tailRight].concat(bodyPnts.slice(count / 2, count));
+      rightPnts.push(neckRight);
+
+      leftPnts = P.PlotUtils.getQBSplinePoints(leftPnts);
+      rightPnts = P.PlotUtils.getQBSplinePoints(rightPnts);
+      const positions = leftPnts.concat(headPnts, rightPnts.reverse(), [
+        swallowTailPnt,
+        leftPnts[0],
+      ]);
+      const res = [] as Cartesian3[];
+      positions.forEach((pos: PointArr) => {
+        res.push(lonLatToCartesian(pos));
+      });
+      return res;
+    },
+    // * 获取尾点
+    getTailPoints: (points: PointArr): PointArr[] => {
+      const allLen = P.PlotUtils.getBaseLength(points);
+      const tailWidth = allLen * squadCombatParams.tailWidthFactor;
+      const tailLeft = P.PlotUtils.getThirdPoint(
+        points[1],
+        points[0],
+        P.Constants.HALF_PI,
+        tailWidth,
+        false
+      );
+      const tailRight = P.PlotUtils.getThirdPoint(
+        points[1],
+        points[0],
+        P.Constants.HALF_PI,
+        tailWidth,
+        true
+      );
+      return [tailLeft, tailRight];
+    },
+    // * 获取分队战斗行动坐标
+    getSquadCombat: (pnts: PointArr[]): Cartesian3[] => {
+      const tailPnts = arrowPlot.algorithm.getTailPoints(pnts);
+      const headPnts = arrowPlot.algorithm.getAttackArrowHeadPoints(
+        pnts,
+        tailPnts[0],
+        tailPnts[1]
+      );
+      const neckLeft = headPnts[0];
+      const neckRight = headPnts[4];
+      const bodyPnts = arrowPlot.algorithm.getAttackArrowBodyPoints(
+        pnts,
+        neckLeft,
+        neckRight,
+        squadCombatParams.tailWidthFactor
+      );
+      const count = bodyPnts.length;
+      let leftPnts = [tailPnts[0]].concat(bodyPnts.slice(0, count / 2));
+      leftPnts.push(neckLeft);
+      let rightPnts = [tailPnts[1]].concat(bodyPnts.slice(count / 2, count));
+      rightPnts.push(neckRight);
+
+      leftPnts = P.PlotUtils.getQBSplinePoints(leftPnts);
+      rightPnts = P.PlotUtils.getQBSplinePoints(rightPnts);
+      const positions = leftPnts.concat(headPnts, rightPnts.reverse());
+      const res = [] as Cartesian3[];
+      positions.forEach((pos: PointArr) => {
+        res.push(lonLatToCartesian(pos));
+      });
+      return res;
+    },
+    // * 获取分队战斗行动尾尾点
+    getTailedTailPoints: (points: PointArr): PointArr[] => {
+      const allLen = P.PlotUtils.getBaseLength(points);
+      const tailWidth = allLen * squadCombatParams.tailWidthFactor;
+      const tailLeft = P.PlotUtils.getThirdPoint(
+        points[1],
+        points[0],
+        P.Constants.HALF_PI,
+        tailWidth,
+        false
+      );
+      const tailRight = P.PlotUtils.getThirdPoint(
+        points[1],
+        points[0],
+        P.Constants.HALF_PI,
+        tailWidth,
+        true
+      );
+      const len = tailWidth * squadCombatParams.swallowTailFactor;
+      const swallowTailPnt = P.PlotUtils.getThirdPoint(
+        points[1],
+        points[0],
+        0,
+        len,
+        true
+      );
+      return [tailLeft, swallowTailPnt, tailRight];
+    },
+    // * 获取分队战斗行动尾坐标
+    getTailedSquadCombat: (pnts: PointArr[]): Cartesian3[] => {
+      const tailPnts = arrowPlot.algorithm.getTailedTailPoints(pnts);
+      const headPnts = arrowPlot.algorithm.getAttackArrowHeadPoints(
+        pnts,
+        tailPnts[0],
+        tailPnts[2]
+      );
+      const neckLeft = headPnts[0];
+      const neckRight = headPnts[4];
+      const bodyPnts = arrowPlot.algorithm.getAttackArrowBodyPoints(
+        pnts,
+        neckLeft,
+        neckRight,
+        squadCombatParams.tailWidthFactor
+      );
+      const count = bodyPnts.length;
+      let leftPnts = [tailPnts[0]].concat(bodyPnts.slice(0, count / 2));
+      leftPnts.push(neckLeft);
+      let rightPnts = [tailPnts[2]].concat(bodyPnts.slice(count / 2, count));
+      rightPnts.push(neckRight);
+
+      leftPnts = P.PlotUtils.getQBSplinePoints(leftPnts);
+      rightPnts = P.PlotUtils.getQBSplinePoints(rightPnts);
+      const positions = leftPnts.concat(headPnts, rightPnts.reverse(), [
+        tailPnts[1],
+        leftPnts[0],
+      ]);
+      const res = [] as Cartesian3[];
+      positions.forEach((pos: PointArr) => {
+        res.push(lonLatToCartesian(pos));
+      });
+      return res;
+    },
+  },
+};

+ 1661 - 0
src/views/map/plot/graphicsDraw/arrowDraw/index.ts

@@ -0,0 +1,1661 @@
+import {
+  Appearance,
+  Billboard,
+  CallbackProperty,
+  Color,
+  defined,
+  Entity,
+  GeometryInstance,
+  GroundPrimitive,
+  HeightReference,
+  Material,
+  Primitive,
+  ScreenSpaceEventHandler,
+  ScreenSpaceEventType,
+  VerticalOrigin,
+  PolygonGeometry,
+  PolygonHierarchy,
+  GroundPolylineGeometry,
+  GroundPolylinePrimitive,
+  PolylineMaterialAppearance,
+  PolylineArrowMaterialProperty,
+} from "cesium";
+import { getCatesian3FromPX, cartesianToLonlat } from "../../tools";
+import type { BaseArrowI, PlotFuncI, PointArr } from "./../../interface";
+import { arrowPlot } from "./algorithm";
+import emitter from "@/mitt";
+
+class BaseArrow implements BaseArrowI {
+  type: string;
+  baseType: string;
+  objId: number;
+  handler: any;
+  state: number;
+  step: number;
+  floatPoint: any;
+  floatPointArr: any;
+  arrowPrimitive: any;
+  arrowEntity: any;
+  modifyHandler: any;
+  pointList: any[];
+  material: any;
+  selectPoint: any;
+  clickStep: number;
+  constructor(obj: BaseArrowI) {
+    this.type = obj.type;
+    this.baseType = "arrow";
+    this.objId = obj.objId;
+    this.handler = obj.handler;
+    this.arrowPrimitive = obj.arrowPrimitive;
+    this.arrowEntity = obj.arrowEntity;
+    this.state = obj.state; //state用于区分当前的状态 0 为删除 1为添加 2为编辑
+    this.step = obj.step;
+    this.floatPoint = obj.floatPoint;
+    this.floatPointArr = obj.floatPointArr;
+    this.modifyHandler = obj.modifyHandler;
+    this.pointList = obj.pointList;
+    this.material = obj.material;
+    this.selectPoint = obj.selectPoint;
+    this.clickStep = obj.clickStep;
+  }
+  disable() {
+    if (this.arrowEntity) {
+      window.Viewer.entities.remove(this.arrowEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.floatPointArr.forEach((item: Billboard) => {
+        window.Viewer.billboards.remove(item);
+      });
+      this.arrowEntity = null;
+    }
+    this.state = -1;
+    this.stopDraw();
+  }
+  stopDraw() {
+    if (this.handler) {
+      this.handler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+      this.handler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
+      this.handler.removeInputAction(ScreenSpaceEventType.RIGHT_CLICK);
+      this.handler.destroy();
+      this.handler = null;
+    }
+    if (this.modifyHandler) {
+      this.modifyHandler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+      this.modifyHandler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
+      this.modifyHandler.removeInputAction(ScreenSpaceEventType.RIGHT_CLICK);
+      this.modifyHandler.destroy();
+      this.modifyHandler = null;
+    }
+  }
+  creatPoint(cartesian: number[]): Primitive {
+    return window.Viewer.billboards.add({
+      id: "moveBillboard",
+      position: cartesian,
+      image: "/src/assets/icon/point.png",
+      verticalOrigin: VerticalOrigin.BOTTOM,
+      heightReference: HeightReference.CLAMP_TO_GROUND,
+    });
+  }
+}
+
+// * doubleArrow 钳击
+class DoubleArrow extends BaseArrow implements PlotFuncI {
+  constructor() {
+    super({
+      type: "DoubleArrow",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      arrowPrimitive: null,
+      arrowEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+      if (this.pointList.length == 5) {
+        this.state = -1;
+        this.pointList.pop();
+        this.arrowPrimitive = this.showPrimitiveOnMap();
+        window.Viewer.entities.remove(this.arrowEntity);
+        window.Viewer.billboards.remove(this.floatPoint);
+        this.arrowEntity = null;
+        this.floatPoint = null;
+        this.stopDraw();
+        emitter.emit("drawEnd");
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.arrowEntity) {
+        this.arrowEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.arrowEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.arrowPrimitive);
+    this.arrowPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.arrowPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.arrowEntity);
+          this.arrowEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.arrowPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(
+          arrowPlot.algorithm.getDoubleArrow(lnglatArr)
+        ),
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      if (
+        lnglatArr.length == 2 ||
+        lnglatArr[1].toString() == lnglatArr[2].toString()
+      ) {
+        lnglatArr.push([lnglatArr[1][0] + 0.0000001, lnglatArr[1][1]]);
+      }
+      const res = arrowPlot.algorithm.getDoubleArrow(lnglatArr);
+      return new PolygonHierarchy(res);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * straightArrow 直箭头
+class StraightArrow extends BaseArrow implements PlotFuncI {
+  constructor() {
+    super({
+      type: "StraightArrow",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      arrowPrimitive: null,
+      arrowEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("PolylineArrow", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.arrowEntity) {
+        this.arrowEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 鼠标右击完成绘制
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 2) return;
+      this.state = -1;
+      this.pointList.pop();
+      this.arrowPrimitive = this.showPrimitiveOnMap();
+      window.Viewer.entities.remove(this.arrowEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.arrowEntity = null;
+      this.floatPoint = null;
+      this.stopDraw();
+      emitter.emit("drawEnd");
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.arrowEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.arrowPrimitive);
+    this.arrowPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.arrowPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.arrowEntity);
+          this.arrowEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.arrowPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  showPrimitiveOnMap(): Primitive {
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new GroundPolylineGeometry({
+        positions: this.pointList,
+        width: 10,
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPolylinePrimitive({
+        geometryInstances: instance,
+        appearance: new PolylineMaterialAppearance({
+          material: this.material,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算曲线,若有两个点则应该直接返回两个点的连线,若有三个点则返回处理后的坐标集合
+      return this.pointList;
+    };
+    return window.Viewer.entities.add({
+      polyline: {
+        positions: new CallbackProperty(update, false),
+        show: true,
+        width: 10,
+        material: new PolylineArrowMaterialProperty(Color.BLUE.clone()),
+        clampToGround: true,
+      },
+    });
+  }
+}
+
+// * fineArrow 细直箭头
+class FineArrow extends BaseArrow implements PlotFuncI {
+  constructor() {
+    super({
+      type: "FineArrow",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      arrowPrimitive: null,
+      arrowEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+      if (this.pointList.length == 3) {
+        this.state = -1;
+        this.pointList.pop();
+        this.arrowPrimitive = this.showPrimitiveOnMap();
+        window.Viewer.entities.remove(this.arrowEntity);
+        window.Viewer.billboards.remove(this.floatPoint);
+        this.arrowEntity = null;
+        this.floatPoint = null;
+        this.stopDraw();
+        emitter.emit("drawEnd");
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.arrowEntity) {
+        this.arrowEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.arrowEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.arrowPrimitive);
+    this.arrowPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.arrowPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.arrowEntity);
+          this.arrowEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.arrowPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(
+          arrowPlot.algorithm.getFineArrow(lnglatArr)
+        ),
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      if (lnglatArr.length == 2) {
+        lnglatArr.push([lnglatArr[1][0] + 0.0000001, lnglatArr[1][1]]);
+      }
+      const res = arrowPlot.algorithm.getFineArrow(lnglatArr);
+      return new PolygonHierarchy(res);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * assaultDirection 突击方向
+class AssaultDirection extends BaseArrow implements PlotFuncI {
+  constructor() {
+    super({
+      type: "AssaultDirection",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      arrowPrimitive: null,
+      arrowEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+      if (this.pointList.length == 3) {
+        this.state = -1;
+        this.pointList.pop();
+        this.arrowPrimitive = this.showPrimitiveOnMap();
+        window.Viewer.entities.remove(this.arrowEntity);
+        window.Viewer.billboards.remove(this.floatPoint);
+        this.arrowEntity = null;
+        this.floatPoint = null;
+        this.stopDraw();
+        emitter.emit("drawEnd");
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.arrowEntity) {
+        this.arrowEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.arrowEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.arrowPrimitive);
+    this.arrowPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.arrowPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.arrowEntity);
+          this.arrowEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.arrowPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(
+          arrowPlot.algorithm.getAssaultDirection(lnglatArr)
+        ),
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      if (lnglatArr.length == 2) {
+        lnglatArr.push([lnglatArr[1][0] + 0.0000001, lnglatArr[1][1]]);
+      }
+      const res = arrowPlot.algorithm.getAssaultDirection(lnglatArr);
+      return new PolygonHierarchy(res);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * attackArrow 进攻方向
+class AttackArrow extends BaseArrow implements PlotFuncI {
+  constructor() {
+    super({
+      type: "AttackArrow",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      arrowPrimitive: null,
+      arrowEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.arrowEntity) {
+        this.arrowEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 鼠标右击完成绘制
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 2) return;
+      this.state = -1;
+      this.pointList.pop();
+      this.arrowPrimitive = this.showPrimitiveOnMap();
+      window.Viewer.entities.remove(this.arrowEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.arrowEntity = null;
+      this.floatPoint = null;
+      this.stopDraw();
+      emitter.emit("drawEnd");
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.arrowEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.arrowPrimitive);
+    this.arrowPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.arrowPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.arrowEntity);
+          this.arrowEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.arrowPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(
+          arrowPlot.algorithm.getAttackArrow(lnglatArr)
+        ),
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      if (lnglatArr.length == 2) {
+        lnglatArr.push([lnglatArr[1][0] + 0.0000001, lnglatArr[1][1]]);
+      } else if (
+        lnglatArr[lnglatArr.length - 1].toString() ==
+          lnglatArr[lnglatArr.length - 2].toString() &&
+        lnglatArr.length > 3
+      ) {
+        lnglatArr.pop();
+      } else {
+        lnglatArr[lnglatArr.length - 1][0] += 0.0000001;
+      }
+      const res = arrowPlot.algorithm.getAttackArrow(lnglatArr);
+      return new PolygonHierarchy(res);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * tailedAttackArrow 进攻方向(尾)
+class TailedAttackArrow extends BaseArrow implements PlotFuncI {
+  constructor() {
+    super({
+      type: "TailedAttackArrow",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      arrowPrimitive: null,
+      arrowEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.arrowEntity) {
+        this.arrowEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 鼠标右击完成绘制
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 2) return;
+      this.state = -1;
+      this.pointList.pop();
+      this.arrowPrimitive = this.showPrimitiveOnMap();
+      window.Viewer.entities.remove(this.arrowEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.arrowEntity = null;
+      this.floatPoint = null;
+      this.stopDraw();
+      emitter.emit("drawEnd");
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.arrowEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.arrowPrimitive);
+    this.arrowPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.arrowPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.arrowEntity);
+          this.arrowEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.arrowPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(
+          arrowPlot.algorithm.getTailedAttackArrow(lnglatArr)
+        ),
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      if (lnglatArr.length == 2) {
+        lnglatArr.push([lnglatArr[1][0] + 0.0000001, lnglatArr[1][1]]);
+      } else if (
+        lnglatArr[lnglatArr.length - 1].toString() ==
+          lnglatArr[lnglatArr.length - 2].toString() &&
+        lnglatArr.length > 3
+      ) {
+        lnglatArr.pop();
+      } else {
+        lnglatArr[lnglatArr.length - 1][0] += 0.0000001;
+      }
+      const res = arrowPlot.algorithm.getTailedAttackArrow(lnglatArr);
+      return new PolygonHierarchy(res);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * squadCombat 分队战斗行动
+class SquadCombat extends BaseArrow implements PlotFuncI {
+  constructor() {
+    super({
+      type: "SquadCombat",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      arrowPrimitive: null,
+      arrowEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.arrowEntity) {
+        this.arrowEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 鼠标右击完成绘制
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 2) return;
+      this.state = -1;
+      this.pointList.pop();
+      this.arrowPrimitive = this.showPrimitiveOnMap();
+      window.Viewer.entities.remove(this.arrowEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.arrowEntity = null;
+      this.floatPoint = null;
+      this.stopDraw();
+      emitter.emit("drawEnd");
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.arrowEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.arrowPrimitive);
+    this.arrowPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.arrowPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.arrowEntity);
+          this.arrowEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.arrowPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(
+          arrowPlot.algorithm.getSquadCombat(lnglatArr)
+        ),
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      if (lnglatArr.length == 2) {
+        lnglatArr.push([lnglatArr[1][0] + 0.0000001, lnglatArr[1][1]]);
+      } else if (
+        lnglatArr[lnglatArr.length - 1].toString() ==
+          lnglatArr[lnglatArr.length - 2].toString() &&
+        lnglatArr.length > 3
+      ) {
+        lnglatArr.pop();
+      } else {
+        lnglatArr[lnglatArr.length - 1][0] += 0.0000001;
+      }
+      const res = arrowPlot.algorithm.getSquadCombat(lnglatArr);
+      return new PolygonHierarchy(res);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * tailedSquadCombat 分队战斗行动(尾)
+class TailedSquadCombat extends BaseArrow implements PlotFuncI {
+  constructor() {
+    super({
+      type: "TailedSquadCombat",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      arrowPrimitive: null,
+      arrowEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.arrowEntity) {
+        this.arrowEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 鼠标右击完成绘制
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 2) return;
+      this.state = -1;
+      this.pointList.pop();
+      this.arrowPrimitive = this.showPrimitiveOnMap();
+      window.Viewer.entities.remove(this.arrowEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.arrowEntity = null;
+      this.floatPoint = null;
+      this.stopDraw();
+      emitter.emit("drawEnd");
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.arrowEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.arrowPrimitive);
+    this.arrowPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.arrowPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.arrowEntity);
+          this.arrowEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.arrowPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(
+          arrowPlot.algorithm.getTailedSquadCombat(lnglatArr)
+        ),
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      if (lnglatArr.length == 2) {
+        lnglatArr.push([lnglatArr[1][0] + 0.0000001, lnglatArr[1][1]]);
+      } else if (
+        lnglatArr[lnglatArr.length - 1].toString() ==
+          lnglatArr[lnglatArr.length - 2].toString() &&
+        lnglatArr.length > 3
+      ) {
+        lnglatArr.pop();
+      } else {
+        lnglatArr[lnglatArr.length - 1][0] += 0.0000001;
+      }
+      const res = arrowPlot.algorithm.getTailedSquadCombat(lnglatArr);
+      return new PolygonHierarchy(res);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+export {
+  DoubleArrow,
+  StraightArrow,
+  FineArrow,
+  AssaultDirection,
+  AttackArrow,
+  TailedAttackArrow,
+  SquadCombat,
+  TailedSquadCombat,
+};

+ 40 - 0
src/views/map/plot/graphicsDraw/lineDraw/algorithm.ts

@@ -0,0 +1,40 @@
+import type { AllPlotI } from "../../interface";
+import type { PointArr } from "../../interface";
+import { P } from "../../tools";
+
+const curseParams = {
+  t: 0.3,
+};
+
+export const linePlot: AllPlotI = {
+  version: "1.0.0",
+  createTime: "2023-2-13",
+  updateTime: "2023-2-13",
+  author: "c-lei-en",
+  algorithm: {
+    // * 获取弧线坐标
+    getArcPositions: (
+      pnt1: PointArr,
+      pnt2: PointArr,
+      pnt3: PointArr
+    ): PointArr[] => {
+      const center = P.PlotUtils.getCircleCenterOfThreePoints(pnt1, pnt2, pnt3);
+      const radius = P.PlotUtils.distance(pnt1, center);
+      const angle1 = P.PlotUtils.getAzimuth(pnt1, center);
+      const angle2 = P.PlotUtils.getAzimuth(pnt2, center);
+      let startAngle, endAngle;
+      if (P.PlotUtils.isClockWise(pnt1, pnt2, pnt3)) {
+        startAngle = angle2;
+        endAngle = angle1;
+      } else {
+        startAngle = angle1;
+        endAngle = angle2;
+      }
+      return P.PlotUtils.getArcPoints(center, radius, startAngle, endAngle);
+    },
+    // * 获取曲线坐标
+    getCurvePoints: (controlPoints: Array<PointArr>): PointArr[] => {
+      return P.PlotUtils.getCurvePoints(curseParams.t, controlPoints);
+    },
+  },
+};

+ 844 - 0
src/views/map/plot/graphicsDraw/lineDraw/index.ts

@@ -0,0 +1,844 @@
+import {
+  Billboard,
+  CallbackProperty,
+  Color,
+  defined,
+  Entity,
+  GeometryInstance,
+  GroundPolylineGeometry,
+  GroundPolylinePrimitive,
+  HeightReference,
+  Material,
+  PolylineGraphics,
+  PolylineMaterialAppearance,
+  Primitive,
+  ScreenSpaceEventHandler,
+  ScreenSpaceEventType,
+  VerticalOrigin,
+} from "cesium";
+import { getCatesian3FromPX, cartesianToLonlat } from "../../tools";
+import type { BaseLineI, PlotFuncI, PointArr } from "./../../interface/index";
+import { linePlot } from "./algorithm";
+import emitter from "@/mitt";
+
+class BaseLine implements BaseLineI {
+  type: string;
+  baseType: string;
+  objId: number;
+  handler: any;
+  state: number;
+  step: number;
+  floatPoint: any;
+  floatPointArr: any;
+  linePrimitive: any;
+  lineEntity: any;
+  modifyHandler: any;
+  pointList: any[];
+  outlineMaterial: any;
+  selectPoint: any;
+  clickStep: number;
+  constructor(obj: BaseLineI) {
+    this.type = obj.type;
+    this.baseType = "line";
+    this.objId = obj.objId;
+    this.handler = obj.handler;
+    this.linePrimitive = obj.linePrimitive;
+    this.lineEntity = obj.lineEntity;
+    this.state = obj.state; //state用于区分当前的状态 0 为删除 1为添加 2为编辑
+    this.step = obj.step;
+    this.floatPoint = obj.floatPoint;
+    this.floatPointArr = obj.floatPointArr;
+    this.modifyHandler = obj.modifyHandler;
+    this.pointList = obj.pointList;
+    this.outlineMaterial = obj.outlineMaterial;
+    this.selectPoint = obj.selectPoint;
+    this.clickStep = obj.clickStep;
+  }
+  disable() {
+    if (this.lineEntity) {
+      window.Viewer.entities.remove(this.lineEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.floatPointArr.forEach((item: Billboard) => {
+        window.Viewer.billboards.remove(item);
+      });
+      this.lineEntity = null;
+    }
+    this.state = -1;
+    this.stopDraw();
+  }
+  stopDraw() {
+    if (this.handler) {
+      this.handler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+      this.handler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
+      this.handler.removeInputAction(ScreenSpaceEventType.RIGHT_CLICK);
+      this.handler.destroy();
+      this.handler = null;
+    }
+    if (this.modifyHandler) {
+      this.modifyHandler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+      this.modifyHandler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
+      this.modifyHandler.removeInputAction(ScreenSpaceEventType.RIGHT_CLICK);
+      this.modifyHandler.destroy();
+      this.modifyHandler = null;
+    }
+  }
+  creatPoint(cartesian: number[]): Primitive {
+    return window.Viewer.billboards.add({
+      id: "moveBillboard",
+      position: cartesian,
+      image: "/src/assets/icon/point.png",
+      verticalOrigin: VerticalOrigin.BOTTOM,
+      heightReference: HeightReference.CLAMP_TO_GROUND,
+    });
+  }
+}
+
+// * arc 弧线
+class Arc extends BaseLine implements PlotFuncI {
+  constructor() {
+    super({
+      type: "Arc",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      linePrimitive: null,
+      lineEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      outlineMaterial: Material.fromType("PolylineOutline", {
+        outlineWidth: 5,
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+      if (this.pointList.length == 4) {
+        this.state = -1;
+        this.pointList.pop();
+        this.linePrimitive = this.showPrimitiveOnMap();
+        window.Viewer.entities.remove(this.lineEntity);
+        window.Viewer.billboards.remove(this.floatPoint);
+        this.lineEntity = null;
+        this.floatPoint = null;
+        this.stopDraw();
+        emitter.emit("drawEnd");
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.lineEntity) {
+        this.lineEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.lineEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.linePrimitive);
+    this.linePrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.linePrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.lineEntity);
+          this.lineEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.linePrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const res = linePlot.algorithm.getArcPositions(...lnglatArr);
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new GroundPolylineGeometry({
+        positions: res,
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPolylinePrimitive({
+        geometryInstances: instance,
+        appearance: new PolylineMaterialAppearance({
+          material: this.outlineMaterial,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算曲线,若有两个点则应该直接返回两个点的连线,若有三个点则返回处理后的坐标集合
+      if (this.pointList.length == 2) {
+        return this.pointList;
+      } else if (this.pointList.length == 3) {
+        if (
+          JSON.stringify(this.pointList[2]) == JSON.stringify(this.pointList[1])
+        ) {
+          return this.pointList;
+        }
+        const lnglatArr = [];
+        for (let i = 0; i < this.pointList.length; i++) {
+          const lnglat = cartesianToLonlat(this.pointList[i]);
+          lnglatArr.push(lnglat);
+        }
+        const res = linePlot.algorithm.getArcPositions(...lnglatArr);
+        const index = JSON.stringify(res).indexOf("null");
+        let returnData = [];
+        if (index == -1) returnData = res;
+        return returnData;
+      } else {
+        return [];
+      }
+    };
+    return window.Viewer.entities.add({
+      polyline: new PolylineGraphics({
+        positions: new CallbackProperty(update, false),
+        show: true,
+        material: Color.BLUE,
+        clampToGround: true,
+      }),
+    });
+  }
+}
+
+// * 曲线
+class Curve extends BaseLine implements PlotFuncI {
+  constructor() {
+    super({
+      type: "Curve",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      linePrimitive: null,
+      lineEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      outlineMaterial: Material.fromType("PolylineOutline", {
+        outlineWidth: 5,
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.lineEntity) {
+        this.lineEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 鼠标右击完成绘制
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 2) return;
+      this.state = -1;
+      this.linePrimitive = this.showPrimitiveOnMap();
+      window.Viewer.entities.remove(this.lineEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.lineEntity = null;
+      this.floatPoint = null;
+      this.stopDraw();
+      emitter.emit("drawEnd");
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.lineEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.linePrimitive);
+    this.linePrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.linePrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.lineEntity);
+          this.lineEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.linePrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  showPrimitiveOnMap(): Primitive {
+    let res;
+    if (this.pointList.length == 2) {
+      res = this.pointList;
+    } else {
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      res = linePlot.algorithm.getCurvePoints(lnglatArr);
+    }
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new GroundPolylineGeometry({
+        positions: res,
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPolylinePrimitive({
+        geometryInstances: instance,
+        appearance: new PolylineMaterialAppearance({
+          material: this.outlineMaterial,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算曲线,若有两个点则应该直接返回两个点的连线,若有三个点则返回处理后的坐标集合
+      if (this.pointList.length == 2) {
+        return this.pointList;
+      } else {
+        const lnglatArr = [];
+        for (let i = 0; i < this.pointList.length; i++) {
+          const lnglat = cartesianToLonlat(this.pointList[i]);
+          lnglatArr.push(lnglat);
+        }
+        const res = linePlot.algorithm.getCurvePoints(lnglatArr);
+        const index = JSON.stringify(res).indexOf("null");
+        let returnData = [];
+        if (index == -1) returnData = res;
+        return returnData;
+      }
+    };
+    return window.Viewer.entities.add({
+      polyline: new PolylineGraphics({
+        positions: new CallbackProperty(update, false),
+        show: true,
+        material: Color.BLUE,
+        clampToGround: true,
+      }),
+    });
+  }
+}
+
+// * 折线
+class Polyline extends BaseLine implements PlotFuncI {
+  constructor() {
+    super({
+      type: "Polyline",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      linePrimitive: null,
+      lineEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      outlineMaterial: Material.fromType("PolylineOutline", {
+        outlineWidth: 5,
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.lineEntity) {
+        this.lineEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 鼠标右击完成绘制
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 2) return;
+      this.state = -1;
+      this.linePrimitive = this.showPrimitiveOnMap();
+      window.Viewer.entities.remove(this.lineEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.lineEntity = null;
+      this.floatPoint = null;
+      this.stopDraw();
+      emitter.emit("drawEnd");
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.lineEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.linePrimitive);
+    this.linePrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.linePrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.lineEntity);
+          this.lineEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.linePrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  showPrimitiveOnMap(): Primitive {
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new GroundPolylineGeometry({
+        positions: this.pointList,
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPolylinePrimitive({
+        geometryInstances: instance,
+        appearance: new PolylineMaterialAppearance({
+          material: this.outlineMaterial,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算曲线,若有两个点则应该直接返回两个点的连线,若有三个点则返回处理后的坐标集合
+      return this.pointList;
+    };
+    return window.Viewer.entities.add({
+      polyline: new PolylineGraphics({
+        positions: new CallbackProperty(update, false),
+        show: true,
+        material: Color.BLUE,
+        clampToGround: true,
+      }),
+    });
+  }
+}
+
+// * 自由线
+class FreeHandPolyline extends BaseLine implements PlotFuncI {
+  constructor() {
+    super({
+      type: "FreeHandPolyline",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      linePrimitive: null,
+      lineEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      outlineMaterial: Material.fromType("PolylineOutline", {
+        outlineWidth: 5,
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.lineEntity) {
+        this.lineEntity = this.createEntity();
+      } else if (this.pointList.length >= 2) {
+        // * 随着鼠标移动添加数据
+        this.pointList.push(cartesian.clone());
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 鼠标右击完成绘制
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 2) return;
+      this.state = -1;
+      this.linePrimitive = this.showPrimitiveOnMap();
+      window.Viewer.entities.remove(this.lineEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.lineEntity = null;
+      this.floatPoint = null;
+      this.stopDraw();
+      emitter.emit("drawEnd");
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.lineEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.linePrimitive);
+    this.linePrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.linePrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.lineEntity);
+          this.lineEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.linePrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  showPrimitiveOnMap(): Primitive {
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new GroundPolylineGeometry({
+        positions: this.pointList,
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPolylinePrimitive({
+        geometryInstances: instance,
+        appearance: new PolylineMaterialAppearance({
+          material: this.outlineMaterial,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算曲线,若有两个点则应该直接返回两个点的连线,若有三个点则返回处理后的坐标集合
+      return this.pointList;
+    };
+    return window.Viewer.entities.add({
+      polyline: new PolylineGraphics({
+        positions: new CallbackProperty(update, false),
+        show: true,
+        material: Color.BLUE,
+        clampToGround: true,
+      }),
+    });
+  }
+}
+
+export { Arc, Curve, Polyline, FreeHandPolyline };

+ 8 - 0
src/views/map/plot/graphicsDraw/pointDraw/algorithm.ts

@@ -0,0 +1,8 @@
+import type { AllPlotI } from "../../interface";
+export const pointPlot: AllPlotI = {
+  version: "1.0.0",
+  createTime: "2023-2-6",
+  updateTime: "2023-2-7",
+  author: "c-lei-en",
+  algorithm: {},
+};

+ 172 - 0
src/views/map/plot/graphicsDraw/pointDraw/index.ts

@@ -0,0 +1,172 @@
+import {
+  Cartesian3,
+  HeadingPitchRoll,
+  HeightReference,
+  Matrix4,
+  Model,
+  ScreenSpaceEventHandler,
+  ScreenSpaceEventType,
+  Transforms,
+  VerticalOrigin,
+} from "cesium";
+import type { PlotFuncI, PointArr, BasePointI } from "../../interface";
+import { getCatesian3FromPX, cartesianToLonlat } from "../../tools";
+import emitter from "@/mitt";
+
+class BasePoint implements BasePointI {
+  type: string;
+  baseType: string;
+  objId: number;
+  handler: any;
+  state: number; //state用于区分当前的状态 0 为删除 1为添加 2为编辑
+  floatPoint: any;
+  pointPrimitive: any;
+  modifyHandler: any;
+  pointList: any;
+  constructor(obj: BasePointI) {
+    this.type = obj.type;
+    this.baseType = "point";
+    this.objId = obj.objId;
+    this.handler = obj.handler;
+    this.pointPrimitive = obj.pointPrimitive;
+    this.state = obj.state; //state用于区分当前的状态 0 为删除 1为添加 2为编辑
+    this.floatPoint = obj.floatPoint;
+    this.modifyHandler = obj.modifyHandler;
+    this.pointList = obj.pointList;
+  }
+}
+
+// * marker广告牌
+class Marker extends BasePoint implements PlotFuncI {
+  constructor() {
+    super({
+      type: "Marker",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      pointPrimitive: null,
+      floatPoint: null,
+      modifyHandler: null,
+      pointList: [],
+    });
+  }
+  disable() {
+    if (this.pointPrimitive) {
+      window.Viewer.billboards.remove(this.pointPrimitive);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.pointPrimitive = null;
+    }
+    this.state = -1;
+    this.stopDraw();
+  }
+  stopDraw() {
+    if (this.handler) {
+      this.handler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+      this.handler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
+      this.handler.destroy();
+      this.handler = null;
+    }
+    if (this.modifyHandler) {
+      this.modifyHandler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+      this.modifyHandler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
+      this.modifyHandler.destroy();
+      this.modifyHandler = null;
+    }
+  }
+  startDraw() {
+    this.state = 1;
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.state = -1;
+      this.pointList.push(cartesian);
+      this.pointPrimitive = this.showPrimitiveOnMap(cartesian);
+      this.floatPoint.show = false;
+      this.stopDraw();
+      emitter.emit("drawEnd");
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian;
+      else this.floatPoint = this.creatPoint(cartesian);
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.floatPoint.show = true;
+    this.modifyHandler.setInputAction((evt: any) => {
+      this.floatPoint.show = false;
+      this.state = -1;
+      this.pointPrimitive.position = getCatesian3FromPX(evt.position);
+      this.stopDraw();
+      emitter.emit("modifiedEnd");
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) {
+        this.floatPoint.position = cartesian;
+        this.pointList = cartesian;
+      } else {
+        return;
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr) {
+    this.pointList = [];
+    this.state = -1;
+    this.floatPoint = null;
+    this.modifyHandler = null;
+    this.pointList = Cartesian3.fromDegreesArray(data);
+    this.pointPrimitive = this.showPrimitiveOnMap(this.pointList);
+    this.pointPrimitive.objId = this.objId;
+  }
+  getLnglats() {
+    return cartesianToLonlat(this.pointList[0]);
+  }
+  getPositions() {
+    return this.pointList;
+  }
+  creatPoint(cartesian: PointArr) {
+    return window.Viewer.billboards.add({
+      position: cartesian,
+      image: "/src/assets/icon/point.png",
+      verticalOrigin: VerticalOrigin.BOTTOM,
+      heightReference: HeightReference.CLAMP_TO_GROUND,
+    });
+  }
+  showPrimitiveOnMap(positons: Cartesian3) {
+    return window.Viewer.billboards.add({
+      position: positons,
+      id: this.objId,
+      image: "/src/assets/icon/mark.png",
+      verticalOrigin: VerticalOrigin.BOTTOM,
+      heightReference: HeightReference.CLAMP_TO_GROUND,
+    });
+  }
+  showPrimitiveModelOnMap(url: string, modelMatrix?: Matrix4 | undefined) {
+    return window.Viewer.scene.primitives.add(
+      Model.fromGltf({
+        id: this.objId,
+        url,
+        modelMatrix:
+          modelMatrix ??
+          Transforms.headingPitchRollToFixedFrame(
+            this.pointList as Cartesian3,
+            new HeadingPitchRoll(0, 0, 0)
+          ),
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+        scene: window.Viewer.scene,
+      })
+    );
+  }
+}
+
+export { Marker };

+ 272 - 0
src/views/map/plot/index.ts

@@ -0,0 +1,272 @@
+import {
+  BillboardCollection,
+  defined,
+  ScreenSpaceEventHandler,
+  ScreenSpaceEventType,
+} from "cesium";
+import { Marker } from "./graphicsDraw/pointDraw";
+import {
+  Arc,
+  Curve,
+  FreeHandPolyline,
+  Polyline,
+} from "./graphicsDraw/lineDraw";
+import type { PlotClass } from "./interface";
+import type { PointArr } from "./interface";
+import emitter from "@/mitt";
+import {
+  Circle,
+  ClosedCurve,
+  Ellipse,
+  FreeHandPolygon,
+  GatheringPlace,
+  Lune,
+  Polygon,
+  Rectangle,
+  Sector,
+} from "./graphicsDraw/areaDraw";
+import {
+  AssaultDirection,
+  AttackArrow,
+  DoubleArrow,
+  FineArrow,
+  SquadCombat,
+  StraightArrow,
+  TailedAttackArrow,
+  TailedSquadCombat,
+} from "./graphicsDraw/arrowDraw";
+
+export default class PlotDraw {
+  drawArr: PlotClass[];
+  handler: any;
+  jsonData: any;
+  nowObj: PlotClass | null;
+  constructor() {
+    this.drawArr = [];
+    this.handler = null;
+    this.nowObj = null;
+    this.init();
+  }
+  init() {
+    this.jsonData = {
+      markerData: [],
+      arcData: [],
+      curveData: [],
+      polylineData: [],
+      freehandpolylineData: [],
+      circleData: [],
+      ellipseData: [],
+      luneData: [],
+      sectorData: [],
+      rectangleData: [],
+      closedcurveData: [],
+      polygonData: [],
+      freehandpolygonData: [],
+      gatheringplaceData: [],
+      doublearrowData: [],
+      straightarrowData: [],
+      finearrowData: [],
+      assaultdirectionData: [],
+      attackarrowData: [],
+      tailedattackarrowData: [],
+      squadcombatData: [],
+      tailedsquadcombatData: [],
+    };
+    this.drawArr = [];
+    emitter.on("drawEnd", () => {
+      this.drawArr.push(this.nowObj as PlotClass);
+      this.drawArr[this.drawArr.length - 1]?.stopDraw();
+      this.saveData();
+      this.nowObj = null;
+    });
+    emitter.on("modifiedEnd", () => {
+      this.startModified();
+      this.nowObj = null;
+    });
+    // * 将点位集合在此处创建,因为不论绘制/修改哪一个标绘都需要有移动点
+    if (!window.Viewer.billboards)
+      window.Viewer.billboards = window.Viewer.scene.primitives.add(
+        new BillboardCollection({
+          scene: window.Viewer.scene,
+        })
+      );
+  }
+  disable() {
+    if (this.handler) {
+      this.drawArr.splice(this.drawArr.indexOf(this.nowObj as PlotClass), 1);
+      this.nowObj?.disable();
+      this.nowObj = null;
+      this.handler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+      this.handler.destroy();
+      this.handler = null;
+    }
+  }
+  stopDraw() {
+    this.drawArr[-1].stopDraw();
+  }
+  draw(type: string) {
+    switch (type) {
+      case "marker":
+        this.nowObj = new Marker();
+        this.nowObj.startDraw();
+        break;
+      case "arc":
+        this.nowObj = new Arc();
+        this.nowObj.startDraw();
+        break;
+      case "curve":
+        this.nowObj = new Curve();
+        this.nowObj.startDraw();
+        break;
+      case "polyline":
+        this.nowObj = new Polyline();
+        this.nowObj.startDraw();
+        break;
+      case "freeHandPolyline":
+        this.nowObj = new FreeHandPolyline();
+        this.nowObj.startDraw();
+        break;
+      case "circle":
+        this.nowObj = new Circle();
+        this.nowObj.startDraw();
+        break;
+      case "ellipse":
+        this.nowObj = new Ellipse();
+        this.nowObj.startDraw();
+        break;
+      case "lune":
+        this.nowObj = new Lune();
+        this.nowObj.startDraw();
+        break;
+      case "sector":
+        this.nowObj = new Sector();
+        this.nowObj.startDraw();
+        break;
+      case "rectangle":
+        this.nowObj = new Rectangle();
+        this.nowObj.startDraw();
+        break;
+      case "closedCurve":
+        this.nowObj = new ClosedCurve();
+        this.nowObj.startDraw();
+        break;
+      case "polygon":
+        this.nowObj = new Polygon();
+        this.nowObj.startDraw();
+        break;
+      case "freeHandPolygon":
+        this.nowObj = new FreeHandPolygon();
+        this.nowObj.startDraw();
+        break;
+      case "gatheringPlace":
+        this.nowObj = new GatheringPlace();
+        this.nowObj.startDraw();
+        break;
+      case "doubleArrow":
+        this.nowObj = new DoubleArrow();
+        this.nowObj.startDraw();
+        break;
+      case "straightArrow":
+        this.nowObj = new StraightArrow();
+        this.nowObj.startDraw();
+        break;
+      case "fineArrow":
+        this.nowObj = new FineArrow();
+        this.nowObj.startDraw();
+        break;
+      case "assaultDirection":
+        this.nowObj = new AssaultDirection();
+        this.nowObj.startDraw();
+        break;
+      case "attackArrow":
+        this.nowObj = new AttackArrow();
+        this.nowObj.startDraw();
+        break;
+      case "tailedAttackArrow":
+        this.nowObj = new TailedAttackArrow();
+        this.nowObj.startDraw();
+        break;
+      case "squadCombat":
+        this.nowObj = new SquadCombat();
+        this.nowObj.startDraw();
+        break;
+      case "tailedSquadCombat":
+        this.nowObj = new TailedSquadCombat();
+        this.nowObj.startDraw();
+        break;
+      default:
+        break;
+    }
+  }
+  saveData() {
+    //保存用户数据
+    const positions: PointArr = this.nowObj?.getLnglats() as PointArr;
+    this.jsonData[this.nowObj?.type.toLowerCase() + "Data"].push(positions);
+    console.log("保存的数据:", this.jsonData);
+  }
+  showData() {
+    console.log(this.jsonData);
+  }
+  startModified() {
+    const $this = this;
+    this.handler = new ScreenSpaceEventHandler(window.Viewer.scene.canvas);
+    // 单击选中开始编辑
+    this.handler.setInputAction(function (evt: any) {
+      const pick = window.Viewer.scene.pick(evt.position);
+      if ($this.nowObj) {
+        if ($this.nowObj.state != -1) {
+          console.log("上一步操作未结束,请继续完成上一步!");
+          return;
+        }
+      } else {
+        if (defined(pick) && pick.id) {
+          for (let i = 0; i < $this.drawArr.length; i++) {
+            if (pick.id == $this.drawArr[i].objId) {
+              $this.nowObj = $this.drawArr[i];
+              $this.drawArr[i].startModify();
+              $this.endModify();
+              emitter.emit("seletedOne");
+              break;
+            }
+          }
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+  }
+  endModify() {
+    this.handler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+    this.handler.destroy();
+    this.handler = null;
+  }
+  seletedOne() {
+    const $this = this;
+    this.handler = new ScreenSpaceEventHandler(window.Viewer.scene.canvas);
+    // 单击选中开始编辑
+    this.handler.setInputAction(function (evt: any) {
+      const pick = window.Viewer.scene.pick(evt.position);
+      if (defined(pick) && pick.id) {
+        for (let i = 0; i < $this.drawArr.length; i++) {
+          if (pick.id == $this.drawArr[i].objId) {
+            $this.nowObj = $this.drawArr[i];
+            emitter.emit("seletedOne");
+            break;
+          }
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+  }
+  clearOne() {
+    if (this.nowObj) {
+      const index = this.drawArr.indexOf(this.nowObj);
+      this.drawArr[index]?.disable();
+      this.drawArr.splice(index, 1);
+      this.startModified();
+      this.nowObj = null;
+    }
+  }
+  clearAll() {
+    for (let i = 0; i < this.drawArr.length; i++) {
+      this.drawArr[i].disable();
+    }
+  }
+}

+ 190 - 0
src/views/map/plot/interface/index.ts

@@ -0,0 +1,190 @@
+import type { Primitive } from "cesium";
+import type {
+  Polygon,
+  Circle,
+  ClosedCurve,
+  Ellipse,
+  Lune,
+  Rectangle,
+  Sector,
+  FreeHandPolygon,
+  GatheringPlace,
+} from "../graphicsDraw/areaDraw";
+import type {
+  AssaultDirection,
+  AttackArrow,
+  DoubleArrow,
+  FineArrow,
+  SquadCombat,
+  StraightArrow,
+  TailedAttackArrow,
+  TailedSquadCombat,
+} from "../graphicsDraw/arrowDraw";
+import type {
+  Arc,
+  Curve,
+  FreeHandPolyline,
+  Polyline,
+} from "../graphicsDraw/lineDraw";
+import type { Marker } from "../graphicsDraw/pointDraw";
+
+export type PointArr = number[];
+
+// * 所有类型的algorithm所遵守的接口
+export interface AllPlotI {
+  version: string;
+  createTime: string;
+  updateTime: string;
+  author: string;
+  algorithm: {
+    [propsName: string]: any;
+  };
+}
+
+// * 基础点类接口
+export interface BasePointI {
+  type: string;
+  objId: number;
+  handler: any;
+  state: number; //state用于区分当前的状态 0 为删除 1为添加 2为编辑
+  pointPrimitive: any;
+  floatPoint: any;
+  modifyHandler: any;
+  pointList: any[];
+}
+
+// * 基础线类接口
+export interface BaseLineI {
+  type: string;
+  objId: number;
+  handler: any;
+  state: number; //state用于区分当前的状态 0 为删除 1为添加 2为编辑
+  step: number; // 表明选中了第几个点
+  linePrimitive: any;
+  lineEntity: any;
+  floatPoint: any;
+  floatPointArr: any[];
+  modifyHandler: any;
+  pointList: any[];
+  outlineMaterial: any;
+  selectPoint: any;
+  clickStep: number;
+}
+
+// * 基础面类接口
+export interface BaseAreaI {
+  type: string;
+  objId: number;
+  handler: any;
+  state: number; //state用于区分当前的状态 0 为删除 1为添加 2为编辑
+  step: number; // 表明选中了第几个点
+  areaPrimitive: any;
+  areaEntity: any;
+  floatPoint: any;
+  floatPointArr: any[];
+  modifyHandler: any;
+  pointList: any[];
+  material: any;
+  selectPoint: any;
+  clickStep: number;
+}
+
+// * 基础箭头类接口
+export interface BaseArrowI {
+  type: string;
+  objId: number;
+  handler: any;
+  state: number; //state用于区分当前的状态 0 为删除 1为添加 2为编辑
+  step: number; // 表明选中了第几个点
+  arrowPrimitive: any;
+  arrowEntity: any;
+  floatPoint: any;
+  floatPointArr: any[];
+  modifyHandler: any;
+  pointList: any[];
+  material: any;
+  selectPoint: any;
+  clickStep: number;
+}
+
+// * 所有绘制类型所需要遵守实现的函数
+export interface PlotFuncI {
+  // * 退出绘制
+  disable: () => void;
+  // * 销毁当前的绘制事件
+  stopDraw: () => void;
+  // * 开始绘制
+  startDraw: () => void;
+  // * 开始编辑
+  startModify: () => void;
+  // * 通过数据创建要素
+  createByData: (data: any) => void;
+  // * 获取当前要素的经纬度
+  getLnglats: () => any;
+  // * 获取当前要素的坐标
+  getPositions: () => any;
+  // * 创建点
+  creatPoint: (cartesian: PointArr) => Primitive;
+  // * 将实体对象添加进界面显示
+  showPrimitiveOnMap: (positons: any) => Primitive;
+}
+
+export interface PlotI {
+  version: string;
+  Constants: {
+    TWO_PI: number;
+    HALF_PI: number;
+    FITTING_COUNT: number;
+    ZERO_TOLERANCE: number;
+  };
+  PlotUtils: {
+    distance: Function;
+    wholeDistance: Function;
+    getBaseLength: Function;
+    mid: Function;
+    getCircleCenterOfThreePoints: Function;
+    getIntersectPoint: Function;
+    getAzimuth: Function;
+    getAngleOfThreePoints: Function;
+    isClockWise: Function;
+    getPointOnLine: Function;
+    getCubicValue: Function;
+    getThirdPoint: Function;
+    getArcPoints: Function;
+    getBisectorNormals: Function;
+    getNormal: Function;
+    getCurvePoints: Function;
+    getLeftMostControlPoint: Function;
+    getRightMostControlPoint: Function;
+    getBezierPoints: Function;
+    getBinomialFactor: Function;
+    getFactorial: Function;
+    getQBSplinePoints: Function;
+    getQuadricBSplineFactor: Function;
+  };
+}
+
+// * 定义所有绘制类型
+export type PlotClass =
+  | Marker
+  | Arc
+  | Curve
+  | Polyline
+  | FreeHandPolyline
+  | Circle
+  | Ellipse
+  | Lune
+  | Sector
+  | Rectangle
+  | ClosedCurve
+  | Polygon
+  | FreeHandPolygon
+  | GatheringPlace
+  | DoubleArrow
+  | StraightArrow
+  | FineArrow
+  | AssaultDirection
+  | AttackArrow
+  | TailedAttackArrow
+  | SquadCombat
+  | TailedSquadCombat;

+ 470 - 0
src/views/map/plot/tools/index.ts

@@ -0,0 +1,470 @@
+import {
+  Cesium3DTileFeature,
+  type Cartesian2,
+  Math as cesiumMath,
+  Cartesian3,
+} from "cesium";
+import type { PointArr, PlotI } from "../interface";
+
+export const P: PlotI = {
+  version: "1.0.0",
+  Constants: {
+    TWO_PI: Math.PI * 2,
+    HALF_PI: Math.PI / 2,
+    FITTING_COUNT: 100,
+    ZERO_TOLERANCE: 0.0001,
+  },
+  PlotUtils: {
+    distance: function (pnt1: PointArr, pnt2: PointArr): number {
+      return Math.sqrt(
+        Math.pow(pnt1[0] - pnt2[0], 2) + Math.pow(pnt1[1] - pnt2[1], 2)
+      );
+    },
+    wholeDistance: function (points: Array<PointArr>): number {
+      let distance = 0;
+      for (let i = 0; i < points.length - 1; i++)
+        distance += P.PlotUtils.distance(points[i], points[i + 1]);
+      return distance;
+    },
+    getBaseLength: function (points: Array<PointArr>): number {
+      return Math.pow(P.PlotUtils.wholeDistance(points), 0.99);
+    },
+    mid: function (pnt1: PointArr, pnt2: PointArr): PointArr {
+      return [(pnt1[0] + pnt2[0]) / 2, (pnt1[1] + pnt2[1]) / 2];
+    },
+    getIntersectPoint: function (
+      pntA: PointArr,
+      pntB: PointArr,
+      pntC: PointArr,
+      pntD: PointArr
+    ): PointArr {
+      if (pntA[1] == pntB[1]) {
+        const f = (pntD[0] - pntC[0]) / (pntD[1] - pntC[1]);
+        const x = f * (pntA[1] - pntC[1]) + pntC[0];
+        const y = pntA[1];
+        return [x, y];
+      }
+      if (pntC[1] == pntD[1]) {
+        const e = (pntB[0] - pntA[0]) / (pntB[1] - pntA[1]);
+        const x = e * (pntC[1] - pntA[1]) + pntA[0];
+        const y = pntC[1];
+        return [x, y];
+      }
+      const e = (pntB[0] - pntA[0]) / (pntB[1] - pntA[1]);
+      const f = (pntD[0] - pntC[0]) / (pntD[1] - pntC[1]);
+      const y = (e * pntA[1] - pntA[0] - f * pntC[1] + pntC[0]) / (e - f);
+      const x = e * y - e * pntA[1] + pntA[0];
+      return [x, y];
+    },
+    getCircleCenterOfThreePoints: function (
+      pnt1: PointArr,
+      pnt2: PointArr,
+      pnt3: PointArr
+    ): PointArr {
+      const pntA = [(pnt1[0] + pnt2[0]) / 2, (pnt1[1] + pnt2[1]) / 2];
+      const pntB = [pntA[0] - pnt1[1] + pnt2[1], pntA[1] + pnt1[0] - pnt2[0]];
+      const pntC = [(pnt1[0] + pnt3[0]) / 2, (pnt1[1] + pnt3[1]) / 2];
+      const pntD = [pntC[0] - pnt1[1] + pnt3[1], pntC[1] + pnt1[0] - pnt3[0]];
+      return P.PlotUtils.getIntersectPoint(pntA, pntB, pntC, pntD);
+    },
+    getAzimuth: function (startPnt: PointArr, endPnt: PointArr): number {
+      let azimuth = 0;
+      const angle = Math.asin(
+        Math.abs(endPnt[1] - startPnt[1]) /
+          P.PlotUtils.distance(startPnt, endPnt)
+      );
+      if (endPnt[1] >= startPnt[1] && endPnt[0] >= startPnt[0])
+        azimuth = angle + Math.PI;
+      else if (endPnt[1] >= startPnt[1] && endPnt[0] < startPnt[0])
+        azimuth = P.Constants.TWO_PI - angle;
+      else if (endPnt[1] < startPnt[1] && endPnt[0] < startPnt[0])
+        azimuth = angle;
+      else if (endPnt[1] < startPnt[1] && endPnt[0] >= startPnt[0])
+        azimuth = Math.PI - angle;
+      return azimuth;
+    },
+    getAngleOfThreePoints: function (
+      pntA: PointArr,
+      pntB: PointArr,
+      pntC: PointArr
+    ): number {
+      const angle =
+        P.PlotUtils.getAzimuth(pntB, pntA) - P.PlotUtils.getAzimuth(pntB, pntC);
+      return angle < 0 ? angle + P.Constants.TWO_PI : angle;
+    },
+    isClockWise: function (
+      pnt1: PointArr,
+      pnt2: PointArr,
+      pnt3: PointArr
+    ): boolean {
+      return (
+        (pnt3[1] - pnt1[1]) * (pnt2[0] - pnt1[0]) >
+        (pnt2[1] - pnt1[1]) * (pnt3[0] - pnt1[0])
+      );
+    },
+    getPointOnLine: function (
+      t: number,
+      startPnt: PointArr,
+      endPnt: PointArr
+    ): PointArr {
+      const x = startPnt[0] + t * (endPnt[0] - startPnt[0]);
+      const y = startPnt[1] + t * (endPnt[1] - startPnt[1]);
+      return [x, y];
+    },
+    getCubicValue: function (
+      t: number,
+      startPnt: PointArr,
+      cPnt1: PointArr,
+      cPnt2: PointArr,
+      endPnt: PointArr
+    ): PointArr {
+      t = Math.max(Math.min(t, 1), 0);
+      const tp = 1 - t;
+      const t2 = t * t;
+      const t3 = t2 * t;
+      const tp2 = tp * tp;
+      const tp3 = tp2 * tp;
+      const x =
+        tp3 * startPnt[0] +
+        3 * tp2 * t * cPnt1[0] +
+        3 * tp * t2 * cPnt2[0] +
+        t3 * endPnt[0];
+      const y =
+        tp3 * startPnt[1] +
+        3 * tp2 * t * cPnt1[1] +
+        3 * tp * t2 * cPnt2[1] +
+        t3 * endPnt[1];
+      return [x, y];
+    },
+    getThirdPoint: function (
+      startPnt: PointArr,
+      endPnt: PointArr,
+      angle: number,
+      distance: number,
+      clockWise: boolean
+    ): PointArr {
+      const azimuth = P.PlotUtils.getAzimuth(startPnt, endPnt);
+      const alpha = clockWise ? azimuth + angle : azimuth - angle;
+      const dx = distance * Math.cos(alpha);
+      const dy = distance * Math.sin(alpha);
+      return [endPnt[0] + dx, endPnt[1] + dy];
+    },
+    getArcPoints: function (
+      center: PointArr,
+      radius: number,
+      startAngle: number,
+      endAngle: number
+    ): Array<Cartesian3> {
+      let x,
+        y,
+        angleDiff = endAngle - startAngle;
+      angleDiff = angleDiff < 0 ? angleDiff + P.Constants.TWO_PI : angleDiff;
+      const pnts = [];
+      for (let i = 0; i <= P.Constants.FITTING_COUNT; i++) {
+        const angle = startAngle + (angleDiff * i) / P.Constants.FITTING_COUNT;
+        x = center[0] + radius * Math.cos(angle);
+        y = center[1] + radius * Math.sin(angle);
+        pnts.push(lonLatToCartesian([x, y]));
+      }
+      return pnts;
+    },
+    getNormal: function (
+      pnt1: PointArr,
+      pnt2: PointArr,
+      pnt3: PointArr
+    ): PointArr {
+      let dX1 = pnt1[0] - pnt2[0];
+      let dY1 = pnt1[1] - pnt2[1];
+      const d1 = Math.sqrt(dX1 * dX1 + dY1 * dY1);
+      dX1 /= d1;
+      dY1 /= d1;
+
+      let dX2 = pnt3[0] - pnt2[0];
+      let dY2 = pnt3[1] - pnt2[1];
+      const d2 = Math.sqrt(dX2 * dX2 + dY2 * dY2);
+      dX2 /= d2;
+      dY2 /= d2;
+
+      const uX = dX1 + dX2;
+      const uY = dY1 + dY2;
+      return [uX, uY];
+    },
+    getBisectorNormals: function (
+      t: number,
+      pnt1: PointArr,
+      pnt2: PointArr,
+      pnt3: PointArr
+    ): Array<PointArr> {
+      const normal = P.PlotUtils.getNormal(pnt1, pnt2, pnt3);
+      const dist = Math.sqrt(normal[0] * normal[0] + normal[1] * normal[1]);
+      const uX = normal[0] / dist;
+      const uY = normal[1] / dist;
+      const d1 = P.PlotUtils.distance(pnt1, pnt2);
+      const d2 = P.PlotUtils.distance(pnt2, pnt3);
+      let bisectorNormalRight, bisectorNormalLeft, dt, x, y;
+      if (dist > P.Constants.ZERO_TOLERANCE) {
+        if (P.PlotUtils.isClockWise(pnt1, pnt2, pnt3)) {
+          dt = t * d1;
+          x = pnt2[0] - dt * uY;
+          y = pnt2[1] + dt * uX;
+          bisectorNormalRight = [x, y];
+          dt = t * d2;
+          x = pnt2[0] + dt * uY;
+          y = pnt2[1] - dt * uX;
+          bisectorNormalLeft = [x, y];
+        } else {
+          dt = t * d1;
+          x = pnt2[0] + dt * uY;
+          y = pnt2[1] - dt * uX;
+          bisectorNormalRight = [x, y];
+          dt = t * d2;
+          x = pnt2[0] - dt * uY;
+          y = pnt2[1] + dt * uX;
+          bisectorNormalLeft = [x, y];
+        }
+      } else {
+        x = pnt2[0] + t * (pnt1[0] - pnt2[0]);
+        y = pnt2[1] + t * (pnt1[1] - pnt2[1]);
+        bisectorNormalRight = [x, y];
+        x = pnt2[0] + t * (pnt3[0] - pnt2[0]);
+        y = pnt2[1] + t * (pnt3[1] - pnt2[1]);
+        bisectorNormalLeft = [x, y];
+      }
+      return [bisectorNormalRight, bisectorNormalLeft];
+    },
+    getLeftMostControlPoint: function (
+      t: number = 1,
+      controlPoints: Array<PointArr>
+    ): PointArr {
+      const pnt1 = controlPoints[0];
+      const pnt2 = controlPoints[1];
+      const pnt3 = controlPoints[2];
+      const pnts = P.PlotUtils.getBisectorNormals(0, pnt1, pnt2, pnt3);
+      const normalRight = pnts[0];
+      const normal = P.PlotUtils.getNormal(pnt1, pnt2, pnt3);
+      const dist = Math.sqrt(normal[0] * normal[0] + normal[1] * normal[1]);
+      let controlX, controlY;
+      if (dist > P.Constants.ZERO_TOLERANCE) {
+        const mid = P.PlotUtils.mid(pnt1, pnt2);
+        const pX = pnt1[0] - mid[0];
+        const pY = pnt1[1] - mid[1];
+
+        const d1 = P.PlotUtils.distance(pnt1, pnt2);
+        // normal at midpoint
+        const n = 2.0 / d1;
+        const nX = -n * pY;
+        const nY = n * pX;
+
+        // upper triangle of symmetric transform matrix
+        const a11 = nX * nX - nY * nY;
+        const a12 = 2 * nX * nY;
+        const a22 = nY * nY - nX * nX;
+
+        const dX = normalRight[0] - mid[0];
+        const dY = normalRight[1] - mid[1];
+
+        // coordinates of reflected vector
+        controlX = mid[0] + a11 * dX + a12 * dY;
+        controlY = mid[1] + a12 * dX + a22 * dY;
+      } else {
+        controlX = pnt1[0] + t * (pnt2[0] - pnt1[0]);
+        controlY = pnt1[1] + t * (pnt2[1] - pnt1[1]);
+      }
+      return [controlX, controlY];
+    },
+    getRightMostControlPoint: function (
+      t: number = 1,
+      controlPoints: Array<PointArr>
+    ): PointArr {
+      const count = controlPoints.length;
+      const pnt1 = controlPoints[count - 3];
+      const pnt2 = controlPoints[count - 2];
+      const pnt3 = controlPoints[count - 1];
+      const pnts = P.PlotUtils.getBisectorNormals(0, pnt1, pnt2, pnt3);
+      const normalLeft = pnts[1];
+      const normal = P.PlotUtils.getNormal(pnt1, pnt2, pnt3);
+      const dist = Math.sqrt(normal[0] * normal[0] + normal[1] * normal[1]);
+      let controlX, controlY;
+      if (dist > P.Constants.ZERO_TOLERANCE) {
+        const mid = P.PlotUtils.mid(pnt2, pnt3);
+        const pX = pnt3[0] - mid[0];
+        const pY = pnt3[1] - mid[1];
+
+        const d1 = P.PlotUtils.distance(pnt2, pnt3);
+        // normal at midpoint
+        const n = 2.0 / d1;
+        const nX = -n * pY;
+        const nY = n * pX;
+
+        // upper triangle of symmetric transform matrix
+        const a11 = nX * nX - nY * nY;
+        const a12 = 2 * nX * nY;
+        const a22 = nY * nY - nX * nX;
+
+        const dX = normalLeft[0] - mid[0];
+        const dY = normalLeft[1] - mid[1];
+
+        // coordinates of reflected vector
+        controlX = mid[0] + a11 * dX + a12 * dY;
+        controlY = mid[1] + a12 * dX + a22 * dY;
+      } else {
+        controlX = pnt3[0] + t * (pnt2[0] - pnt3[0]);
+        controlY = pnt3[1] + t * (pnt2[1] - pnt3[1]);
+      }
+      return [controlX, controlY];
+    },
+    getCurvePoints: function (
+      t: number,
+      controlPoints: Array<PointArr>
+    ): Array<Cartesian3> {
+      const leftControl = P.PlotUtils.getLeftMostControlPoint(t, controlPoints);
+      let normals = [leftControl];
+      for (let i = 0; i < controlPoints.length - 2; i++) {
+        const pnt1 = controlPoints[i];
+        const pnt2 = controlPoints[i + 1];
+        const pnt3 = controlPoints[i + 2];
+        const normalPoints = P.PlotUtils.getBisectorNormals(
+          t,
+          pnt1,
+          pnt2,
+          pnt3
+        );
+        normals = normals.concat(normalPoints);
+      }
+      const rightControl = P.PlotUtils.getRightMostControlPoint(
+        t,
+        controlPoints
+      );
+      normals.push(rightControl);
+      const points = [];
+      for (let i = 0; i < controlPoints.length - 1; i++) {
+        const pnt1 = controlPoints[i];
+        const pnt2 = controlPoints[i + 1];
+        points.push(pnt1);
+        for (let t = 0; t < P.Constants.FITTING_COUNT; t++) {
+          const pnt = P.PlotUtils.getCubicValue(
+            t / P.Constants.FITTING_COUNT,
+            pnt1,
+            normals[i * 2],
+            normals[i * 2 + 1],
+            pnt2
+          );
+          points.push(pnt);
+        }
+        points.push(pnt2);
+      }
+      const pnts = [];
+      for (let i = 0; i <= points.length - 1; i++) {
+        pnts.push(Cartesian3.fromDegrees(points[i][0], points[i][1]));
+      }
+      return pnts;
+    },
+    getFactorial: function (n: number): number {
+      if (n <= 1) return 1;
+      if (n == 2) return 2;
+      if (n == 3) return 6;
+      if (n == 4) return 24;
+      if (n == 5) return 120;
+      let result = 1;
+      for (let i = 1; i <= n; i++) result *= i;
+      return result;
+    },
+    getBinomialFactor: function (n: number, index: number): number {
+      return (
+        P.PlotUtils.getFactorial(n) /
+        (P.PlotUtils.getFactorial(index) * P.PlotUtils.getFactorial(n - index))
+      );
+    },
+    getBezierPoints: function (points: Array<PointArr>): Array<PointArr> {
+      if (points.length <= 2) return points;
+
+      const bezierPoints = [];
+      const n = points.length - 1;
+      for (let t = 0; t <= 1; t += 0.01) {
+        let x = 0,
+          y = 0;
+        for (let index = 0; index <= n; index++) {
+          const factor = P.PlotUtils.getBinomialFactor(n, index);
+          const a = Math.pow(t, index);
+          const b = Math.pow(1 - t, n - index);
+          x += factor * a * b * points[index][0];
+          y += factor * a * b * points[index][1];
+        }
+        bezierPoints.push([x, y]);
+      }
+      bezierPoints.push(points[n]);
+      return bezierPoints;
+    },
+    getQuadricBSplineFactor: function (k: number, t: number): number {
+      if (k == 0) return Math.pow(t - 1, 2) / 2;
+      if (k == 1) return (-2 * Math.pow(t, 2) + 2 * t + 1) / 2;
+      if (k == 2) return Math.pow(t, 2) / 2;
+      return 0;
+    },
+    getQBSplinePoints: function (points: Array<PointArr>): Array<PointArr> {
+      if (points.length <= 2) return points;
+
+      const n = 2;
+
+      const bSplinePoints = [];
+      const m = points.length - n - 1;
+      bSplinePoints.push(points[0]);
+      for (let i = 0; i <= m; i++) {
+        for (let t = 0; t <= 1; t += 0.05) {
+          let x = 0,
+            y = 0;
+          for (let k = 0; k <= n; k++) {
+            const factor = P.PlotUtils.getQuadricBSplineFactor(k, t);
+            x += factor * points[i + k][0];
+            y += factor * points[i + k][1];
+          }
+          bSplinePoints.push([x, y]);
+        }
+      }
+      bSplinePoints.push(points[points.length - 1]);
+      return bSplinePoints;
+    },
+  },
+};
+
+// * 从当前坐标上获取3D笛卡尔坐标
+export function getCatesian3FromPX(px: Cartesian2) {
+  const picks = window.Viewer.scene.drillPick(px);
+  window.Viewer.render();
+  let cartesian;
+  let isOn3dtiles = false;
+  for (let i = 0; i < picks.length; i++) {
+    if (
+      picks[i] &&
+      picks[i].primitive &&
+      picks[i] instanceof Cesium3DTileFeature
+    ) {
+      //模型上拾取
+      isOn3dtiles = true;
+      break;
+    }
+  }
+  if (isOn3dtiles) {
+    cartesian = window.Viewer.scene.pickPosition(px, cartesian);
+  } else {
+    const ray = window.Viewer.camera.getPickRay(px);
+    if (!ray) return null;
+    cartesian = window.Viewer.scene.globe.pick(ray, window.Viewer.scene);
+  }
+  return cartesian;
+}
+
+// * 笛卡尔坐标转经纬度
+export function cartesianToLonlat(cartesian: any) {
+  const lonLat =
+    window.Viewer.scene.globe.ellipsoid.cartesianToCartographic(cartesian);
+  const lat = cesiumMath.toDegrees(lonLat.latitude);
+  const lng = cesiumMath.toDegrees(lonLat.longitude);
+  return [lng, lat];
+}
+
+// * 经纬度坐标转笛卡尔
+export function lonLatToCartesian(lonLat: any) {
+  const cartesian = Cartesian3.fromDegrees(lonLat[0], lonLat[1]);
+  return cartesian;
+}

+ 81 - 0
src/views/map/plotTools/AreaMaterial.vue

@@ -0,0 +1,81 @@
+<template>
+  <el-card style="margin-top: 10px; overflow: auto">
+    <el-input
+      style="margin-top: 10px"
+      v-for="(item, index) in areaConfig"
+      :key="index"
+      v-model="item.value"
+      :oninput="item.oninput"
+    >
+      <template #prepend>
+        {{ item.name }}
+      </template>
+    </el-input>
+    <el-button
+      type="primary"
+      style="margin-top: 5px; margin-left: 80%"
+      @click="areaMaterialClick"
+      >确定</el-button
+    >
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+import { Color, Material } from "cesium";
+import { reactive, toRaw } from "vue";
+import PlotDraw from "../plot";
+import type { BaseAreaI } from "../plot/interface";
+
+const props = defineProps({
+  draw: {
+    type: PlotDraw,
+    required: true,
+  },
+});
+let area = toRaw(props.draw.nowObj as BaseAreaI);
+
+// 线样式修改相关配置
+const areaConfig = reactive([
+  {
+    name: "速度",
+    value: 2,
+    oninput: "value=value.replace(/[^0-9.]/g,'')",
+  },
+  {
+    name: "颜色",
+    value: "#ffff00",
+    oninput: "value=value.replace(/^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/g,'')",
+  },
+]);
+
+function areaMaterialClick() {
+  console.log(area.areaPrimitive);
+  const areaFabric = {
+    type: "AreaFabric",
+    uniforms: {
+      color: Color.fromCssColorString(areaConfig[1].value as string),
+      speed: eval(areaConfig[0].value as string),
+    },
+    source: `czm_material czm_getMaterial(czm_materialInput materialInput) {
+          czm_material material = czm_getDefaultMaterial(materialInput);
+          vec2 st = materialInput.st;
+          float xx = fract(st.s * speed - czm_frameNumber/60.0);
+          float r = xx;
+          float g = sin(czm_frameNumber / 30.0);
+          float b = cos(czm_frameNumber / 30.0);
+          vec3 fragColor;
+          fragColor.rgb = color.rgb / 1.0;
+          fragColor = czm_gammaCorrect(fragColor); // 伽马校正
+          material.alpha = xx;
+          material.diffuse = vec3(r,g,b) / 2.0;
+          material.emission = fragColor.rgb;
+          return material;
+          }`,
+  };
+  area.areaPrimitive.appearance.material = new Material({
+    fabric: areaFabric,
+  });
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 81 - 0
src/views/map/plotTools/ArrowMaterial.vue

@@ -0,0 +1,81 @@
+<template>
+  <el-card style="margin-top: 10px; overflow: auto">
+    <el-input
+      style="margin-top: 10px"
+      v-for="(item, index) in arrowConfig"
+      :key="index"
+      v-model="item.value"
+      :oninput="item.oninput"
+    >
+      <template #prepend>
+        {{ item.name }}
+      </template>
+    </el-input>
+    <el-button
+      type="primary"
+      style="margin-top: 5px; margin-left: 80%"
+      @click="arrowMaterialClick"
+      >确定</el-button
+    >
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+import { Color, Material } from "cesium";
+import { reactive, toRaw } from "vue";
+import PlotDraw from "../plot";
+import type { BaseArrowI } from "../plot/interface";
+
+const props = defineProps({
+  draw: {
+    type: PlotDraw,
+    required: true,
+  },
+});
+let arrow = toRaw(props.draw.nowObj as BaseArrowI);
+
+// 线样式修改相关配置
+const arrowConfig = reactive([
+  {
+    name: "速度",
+    value: 2,
+    oninput: "value=value.replace(/[^0-9.]/g,'')",
+  },
+  {
+    name: "颜色",
+    value: "#ffff00",
+    oninput: "value=value.replace(/^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/g,'')",
+  },
+]);
+
+function arrowMaterialClick() {
+  console.log(arrow.arrowPrimitive);
+  const arrowFabric = {
+    type: "ArrowFabric",
+    uniforms: {
+      color: Color.fromCssColorString(arrowConfig[1].value as string),
+      speed: eval(arrowConfig[0].value as string),
+    },
+    source: `czm_material czm_getMaterial(czm_materialInput materialInput) {
+            czm_material material = czm_getDefaultMaterial(materialInput);
+            vec2 st = materialInput.st;
+            float xx = fract(st.s * speed - czm_frameNumber/60.0);
+            float r = xx;
+            float g = sin(czm_frameNumber / 30.0);
+            float b = cos(czm_frameNumber / 30.0);
+            vec3 fragColor;
+            fragColor.rgb = color.rgb / 1.0;
+            fragColor = czm_gammaCorrect(fragColor); // 伽马校正
+            material.alpha = xx;
+            material.diffuse = vec3(r,g,b) / 2.0;
+            material.emission = fragColor.rgb;
+            return material;
+            }`,
+  };
+  arrow.arrowPrimitive.appearance.material = new Material({
+    fabric: arrowFabric,
+  });
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 268 - 0
src/views/map/plotTools/DrawTool.vue

@@ -0,0 +1,268 @@
+<template>
+  <div class="drawTool">
+    <el-tabs @tab-click="tabClick" style="height: 100%" type="border-card">
+      <el-tab-pane label="标绘工具">
+        <el-space :fill="true" wrap>
+          <el-row>
+            <el-switch
+              v-model="isModified"
+              inline-prompt
+              style="
+                --el-switch-on-color: #13ce66;
+                --el-switch-off-color: #ff4949;
+              "
+              active-text="开始编辑"
+              inactive-text="关闭编辑"
+            ></el-switch>
+            <el-button
+              style="margin-left: 10px"
+              type="danger"
+              :icon="Delete"
+              @click="deleteObj"
+              :disabled="deleteBool"
+              circle
+            />
+          </el-row>
+          <el-card
+            style="max-height: 150px; overflow: auto"
+            v-for="item in cardArrays"
+            :key="item.id"
+          >
+            <template #header>
+              <div class="card-header">
+                <span>{{ item.name }}</span>
+              </div>
+            </template>
+            <el-space wrap>
+              <el-button
+                v-for="plot in item.children"
+                :key="plot.activeName"
+                @click="plotDraw(plot.activeName)"
+                text
+              >
+                {{ plot.name }}
+              </el-button>
+            </el-space>
+          </el-card>
+        </el-space></el-tab-pane
+      >
+      <el-tab-pane label="样式修改">
+        <div v-if="showTool == 'none'">请选择一个要素</div>
+        <PointMaterial :draw="draw" v-if="showTool == 'point'" />
+        <LineMaterial :draw="draw" v-if="showTool == 'line'" />
+        <AreaMaterial :draw="draw" v-if="showTool == 'area'" />
+        <ArrowMaterial :draw="draw" v-if="showTool == 'arrow'" />
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, onUnmounted, ref, watch } from "vue";
+import PointMaterial from "./PointMaterial.vue";
+import LineMaterial from "./LineMaterial.vue";
+import AreaMaterial from "./AreaMaterial.vue";
+import ArrowMaterial from "./ArrowMaterial.vue";
+import PlotDraw from "../plot";
+import emitter from "@/mitt";
+import { Delete } from "@element-plus/icons-vue";
+import type { TabsPaneContext } from "element-plus/es/tokens/tabs";
+
+const cardArrays = [
+  {
+    name: "点标绘",
+    id: 1,
+    children: [
+      {
+        name: "点",
+        activeName: "marker",
+      },
+    ],
+  },
+  {
+    name: "线标绘",
+    id: 2,
+    children: [
+      {
+        name: "弧线",
+        activeName: "arc",
+      },
+      {
+        name: "曲线",
+        activeName: "curve",
+      },
+      {
+        name: "折线",
+        activeName: "polyline",
+      },
+      {
+        name: "自由线",
+        activeName: "freeHandPolyline",
+      },
+    ],
+  },
+  {
+    name: "面标绘",
+    id: 3,
+    children: [
+      {
+        name: "圆",
+        activeName: "circle",
+      },
+      {
+        name: "椭圆",
+        activeName: "ellipse",
+      },
+      {
+        name: "弓形",
+        activeName: "lune",
+      },
+      {
+        name: "扇形",
+        activeName: "sector",
+      },
+      {
+        name: "矩形",
+        activeName: "rectangle",
+      },
+      {
+        name: "曲线面",
+        activeName: "closedCurve",
+      },
+      {
+        name: "多边形",
+        activeName: "polygon",
+      },
+      {
+        name: "自由面",
+        activeName: "freeHandPolygon",
+      },
+      {
+        name: "聚集地",
+        activeName: "gatheringPlace",
+      },
+    ],
+  },
+  {
+    name: "箭头标绘",
+    id: 4,
+    children: [
+      {
+        name: "钳击",
+        activeName: "doubleArrow",
+      },
+      {
+        name: "直箭头",
+        activeName: "straightArrow",
+      },
+      {
+        name: "细直箭头",
+        activeName: "fineArrow",
+      },
+      {
+        name: "突击方向",
+        activeName: "assaultDirection",
+      },
+      {
+        name: "进攻方向",
+        activeName: "attackArrow",
+      },
+      {
+        name: "进攻方向(尾)",
+        activeName: "tailedAttackArrow",
+      },
+      {
+        name: "分队战斗行动",
+        activeName: "squadCombat",
+      },
+      {
+        name: "分队战斗行动(尾)",
+        activeName: "tailedSquadCombat",
+      },
+    ],
+  },
+];
+
+const isModified = ref(false);
+
+let draw: PlotDraw;
+
+onMounted(() => {
+  draw = new PlotDraw();
+});
+
+watch(isModified, (newValue) => {
+  if (newValue) {
+    draw?.startModified();
+  } else {
+    draw?.endModify();
+  }
+});
+
+let showTool = ref("none");
+let deleteBool = ref(true);
+emitter.on("seletedOne", changeToolVisible);
+function changeToolVisible() {
+  deleteBool.value = false;
+  showTool.value = draw?.nowObj?.baseType as string;
+}
+function deleteObj() {
+  draw?.clearOne();
+  deleteBool.value = true;
+}
+
+function plotDraw(name: string) {
+  draw?.draw(name);
+}
+
+// * 标签页点击事件
+function tabClick(pane: TabsPaneContext) {
+  showTool.value = "none";
+  deleteBool.value = false;
+  switch (pane.props.label) {
+    case "样式修改":
+      draw?.seletedOne();
+      break;
+    default:
+      draw.nowObj = null;
+      if (draw.handler) {
+        draw.endModify();
+      }
+      break;
+  }
+}
+
+onUnmounted(() => {
+  emitter.off("seletedOne", changeToolVisible);
+});
+</script>
+
+<style lang="scss" scoped>
+.drawTool {
+  position: fixed;
+  top: 20%;
+  right: 0;
+  z-index: 2000;
+  height: 75%;
+  width: 20%;
+  background: #131e30;
+
+  ::-webkit-scrollbar {
+    /*滚动条整体样式*/
+    width: 2px; /*高宽分别对应横竖滚动条的尺寸*/
+    height: 1px;
+  }
+  ::-webkit-scrollbar-thumb {
+    /*滚动条里面小方块*/
+    border-radius: 10px;
+    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
+    background: #535353;
+  }
+  ::-webkit-scrollbar-track {
+    /*滚动条里面轨道*/
+    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
+    border-radius: 10px;
+    background: #ededed;
+  }
+}
+</style>

+ 88 - 0
src/views/map/plotTools/LineMaterial.vue

@@ -0,0 +1,88 @@
+<template>
+  <el-card style="margin-top: 10px; overflow: auto">
+    <el-input
+      style="margin-top: 10px"
+      v-for="(item, index) in lineConfig"
+      :key="index"
+      v-model="item.value"
+      :oninput="item.oninput"
+    >
+      <template #prepend>
+        {{ item.name }}
+      </template>
+    </el-input>
+    <el-button
+      type="primary"
+      style="margin-top: 5px; margin-left: 80%"
+      @click="lineMaterialClick"
+      >确定</el-button
+    >
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+import { Color, Material } from "cesium";
+import { reactive, toRaw } from "vue";
+import PlotDraw from "../plot";
+import type { BaseLineI } from "../plot/interface";
+
+const props = defineProps({
+  draw: {
+    type: PlotDraw,
+    required: true,
+  },
+});
+let line = toRaw(props.draw.nowObj as BaseLineI);
+
+// 线样式修改相关配置
+const lineConfig = reactive([
+  {
+    name: "宽度",
+    value: 2,
+    oninput: "value=value.replace(/[^0-9.]/g,'')",
+  },
+  {
+    name: "速度",
+    value: 2,
+    oninput: "value=value.replace(/[^0-9.]/g,'')",
+  },
+  {
+    name: "颜色",
+    value: "#ffff00",
+    oninput: "value=value.replace(/^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/g,'')",
+  },
+]);
+
+function lineMaterialClick() {
+  console.log(line.linePrimitive);
+  line.linePrimitive._primitiveOptions.geometryInstances[0].geometry.width =
+    eval(lineConfig[0].value as string);
+  const polylineFabric = {
+    type: "PolylineFabric",
+    uniforms: {
+      color: Color.fromCssColorString(lineConfig[2].value as string),
+      speed: eval(lineConfig[1].value as string),
+    },
+    source: `czm_material czm_getMaterial(czm_materialInput materialInput) {
+        czm_material material = czm_getDefaultMaterial(materialInput);
+        vec2 st = materialInput.st;
+        float xx = fract(st.s * speed - czm_frameNumber/60.0);
+        float r = xx;
+        float g = sin(czm_frameNumber / 30.0);
+        float b = cos(czm_frameNumber / 30.0);
+        vec3 fragColor;
+        fragColor.rgb = color.rgb / 1.0;
+        fragColor = czm_gammaCorrect(fragColor); // 伽马校正
+        material.alpha = xx;
+        material.diffuse = vec3(r,g,b) / 2.0;
+        material.emission = fragColor.rgb;
+        return material;
+        }`,
+  };
+  line.linePrimitive.appearance.material = new Material({
+    fabric: polylineFabric,
+  });
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 317 - 0
src/views/map/plotTools/PointMaterial.vue

@@ -0,0 +1,317 @@
+<template>
+  <el-card style="margin-top: 10px; overflow: auto">
+    <el-input
+      v-model="pointModel.modelUrl"
+      placeholder="请输入相应模型/图片地址"
+    >
+      <template #prepend>
+        <el-select v-model="pointModel.modelName" style="width: 80px">
+          <el-option label="模型" value="model" />
+          <el-option label="图片" value="image" />
+        </el-select>
+      </template>
+    </el-input>
+    <el-button
+      type="primary"
+      style="margin-top: 5px; margin-left: 80%"
+      @click="modelClick"
+      >确定</el-button
+    >
+  </el-card>
+  <el-card style="margin-top: 10px; overflow: auto; max-height: 500px">
+    <template #header>
+      <div class="card-header">
+        <span>粒子系统</span>
+      </div>
+    </template>
+    <el-select
+      v-model="pointParticle.particleType"
+      placeholder="Select"
+      style="width: 100%"
+      @change="particleChange"
+    >
+      <el-option label="盒发射器" value="BoxEmitter" />
+      <el-option label="圆形发射器" value="CircleEmitter" />
+      <el-option label="圆锥发射器" value="ConeEmitter" />
+      <el-option label="球发射器" value="SphereEmitter" />
+    </el-select>
+    <el-input
+      style="margin-top: 10px"
+      v-for="(item, index) in pointParticle.particleInput"
+      :key="index"
+      v-model="item.value"
+      :placeholder="item.title"
+      :oninput="item.oninput"
+    >
+      <template #prepend>
+        {{ item.name }}
+      </template>
+    </el-input>
+    <el-button
+      type="primary"
+      @click="particleClick"
+      style="margin-top: 5px; margin-left: 80%"
+      >确定</el-button
+    >
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+import {
+  Billboard,
+  BoxEmitter,
+  Cartesian2,
+  Cartesian3,
+  CircleEmitter,
+  Color,
+  ConeEmitter,
+  HeadingPitchRoll,
+  Matrix4,
+  Model,
+  ParticleSystem,
+  SphereEmitter,
+  Transforms,
+  Math as cesiumMath,
+} from "cesium";
+import { reactive } from "vue";
+import PlotDraw from "../plot";
+import type { Marker } from "../plot/graphicsDraw/pointDraw";
+
+const props = defineProps({
+  draw: {
+    type: PlotDraw,
+    required: true,
+  },
+});
+
+let point = props.draw.nowObj as Marker;
+
+let pointModel = reactive({
+  modelUrl: "",
+  modelName: "image",
+});
+function modelClick() {
+  if (pointModel.modelName == "image") {
+    if (point.pointPrimitive instanceof Billboard) {
+      point.pointPrimitive.image = pointModel.modelUrl;
+    } else {
+      const position = Matrix4.getTranslation(
+        point.pointPrimitive.modelMatrix,
+        new Cartesian3()
+      );
+      window.Viewer.scene.primitives.remove(point.pointPrimitive);
+      point.pointPrimitive = point.showPrimitiveOnMap(position);
+    }
+  } else {
+    if (
+      point.pointPrimitive instanceof Model ||
+      point.pointPrimitive instanceof ParticleSystem
+    ) {
+      window.Viewer.scene.primitives.remove(point.pointPrimitive);
+      point.pointPrimitive = point.showPrimitiveModelOnMap(
+        pointModel.modelUrl,
+        point.pointPrimitive.modelMatrix
+      );
+    } else {
+      const position = point.pointPrimitive.position;
+      const modelMatrix = Transforms.headingPitchRollToFixedFrame(
+        position,
+        new HeadingPitchRoll(0, 0, 0)
+      );
+      window.Viewer.billboards.remove(point.pointPrimitive);
+      point.pointPrimitive = point.showPrimitiveModelOnMap(
+        pointModel.modelUrl,
+        modelMatrix
+      );
+    }
+  }
+}
+
+let pointParticle = reactive({
+  particleType: "BoxEmitter",
+  particleInput: [
+    {
+      value: 1.0,
+      name: "初始比例",
+      realName: "startScale",
+      title: "请输入粒子初始时比例",
+      oninput: "value=value.replace(/[^0-9.]/g,'')",
+    },
+    {
+      value: 5.0,
+      name: "消失比例",
+      realName: "endScale",
+      title: "请输入粒子消失时比例",
+      oninput: "value=value.replace(/[^0-9.]/g,'')",
+    },
+    {
+      value: "#ffff00",
+      name: "粒子颜色",
+      realName: "startColor",
+      title: "请输入粒子初始时颜色",
+      oninput: "value=value.replace(/^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/g,'')",
+    },
+    {
+      value: "/src/assets/icon/smoke.png",
+      name: "图片地址",
+      realName: "image",
+      title: "请输入粒子对应图片地址",
+      oninput: "value=value.replace(/*/g,'')",
+    },
+    {
+      value: 3.0,
+      name: "图片尺寸",
+      realName: "image",
+      title: "请输入粒子对应图片尺寸",
+      oninput: "value=value.replace(/[^0-9.]/g,'')",
+    },
+    {
+      value: 1.0,
+      name: "最小速度",
+      realName: "minimumSpeed",
+      title: "请输入粒子最小速度",
+      oninput: "value=value.replace(/[^0-9.]/g,'')",
+    },
+    {
+      value: 3.0,
+      name: "最大速度",
+      realName: "maximumSpeed",
+      title: "请输入粒子最大速度",
+      oninput: "value=value.replace(/[^0-9.]/g,'')",
+    },
+    {
+      value: 10.0,
+      name: "每秒粒子数",
+      realName: "emissionRate",
+      title: "请输入每秒发射的粒子数",
+      oninput: "value=value.replace(/[^0-9.]/g,'')",
+    },
+    {
+      value: 1.0,
+      name: "最小存活时间",
+      realName: "minimumParticleLife",
+      title: "请输入粒子最小存活时间",
+      oninput: "value=value.replace(/[^0-9.]/g,'')",
+    },
+    {
+      value: 5.0,
+      name: "最大存活时间",
+      realName: "maximumParticleLife",
+      title: "请输入粒子最大存活时间",
+      oninput: "value=value.replace(/[^0-9.]/g,'')",
+    },
+  ],
+});
+function particleClick() {
+  let emitterParticle;
+  switch (pointParticle.particleType) {
+    case "BoxEmitter":
+      emitterParticle = new BoxEmitter(new Cartesian3(10.0, 10.0, 10.0));
+      break;
+    case "CircleEmitter":
+      emitterParticle = new CircleEmitter(2.0);
+      break;
+    case "ConeEmitter":
+      emitterParticle = new ConeEmitter(cesiumMath.toRadians(45.0));
+      break;
+    case "SphereEmitter":
+      emitterParticle = new SphereEmitter(2.5);
+      break;
+    default:
+      emitterParticle = new CircleEmitter(2.0);
+      break;
+  }
+  let modelMatrix;
+  if (point.pointPrimitive instanceof Billboard) {
+    modelMatrix = Transforms.headingPitchRollToFixedFrame(
+      point.pointPrimitive.position,
+      new HeadingPitchRoll(0, 0, 0)
+    );
+    window.Viewer.billboards.remove(point.pointPrimitive);
+  } else if (point.pointPrimitive instanceof Model) {
+    modelMatrix = point.pointPrimitive.modelMatrix;
+    window.Viewer.scene.primitives.remove(point.pointPrimitive);
+  } else {
+    modelMatrix = point.pointPrimitive.modelMatrix;
+  }
+  const gravityScratch = new Cartesian3();
+
+  if (!(point.pointPrimitive instanceof ParticleSystem)) {
+    point.pointPrimitive = window.Viewer.scene.primitives.add(
+      new ParticleSystem({
+        lifetime: 16.0,
+        updateCallback: function (p) {
+          const position = p.position;
+
+          Cartesian3.normalize(position, gravityScratch);
+          Cartesian3.multiplyByScalar(gravityScratch, 0, gravityScratch);
+
+          p.velocity = Cartesian3.add(p.velocity, gravityScratch, p.velocity);
+        },
+        emitterModelMatrix: modelMatrix,
+      })
+    );
+  }
+  point.pointPrimitive.emitter = emitterParticle;
+  point.pointPrimitive.image = pointParticle.particleInput[3].value;
+  point.pointPrimitive.startColor = Color.fromCssColorString(
+    pointParticle.particleInput[2].value as string
+  ).withAlpha(0.7);
+  point.pointPrimitive.endColor = Color.WHITE.withAlpha(0.0);
+  point.pointPrimitive.startScale = eval(
+    pointParticle.particleInput[0].value as string
+  ) as number;
+  point.pointPrimitive.endScale = eval(
+    pointParticle.particleInput[1].value as string
+  ) as number;
+  point.pointPrimitive.minimumParticleLife = eval(
+    pointParticle.particleInput[8].value as string
+  ) as number;
+  point.pointPrimitive.maximumParticleLife = eval(
+    pointParticle.particleInput[9].value as string
+  ) as number;
+  point.pointPrimitive.minimumSpeed = eval(
+    pointParticle.particleInput[5].value as string
+  ) as number;
+  point.pointPrimitive.maximumSpeed = eval(
+    pointParticle.particleInput[6].value as string
+  ) as number;
+  point.pointPrimitive.imageSize = new Cartesian2(
+    eval(pointParticle.particleInput[4].value as string) as number,
+    eval(pointParticle.particleInput[4].value as string) as number
+  );
+  point.pointPrimitive.emissionRate = eval(
+    pointParticle.particleInput[7].value as string
+  ) as number;
+}
+
+function particleChange(val: string) {
+  console.log(val);
+
+  if (point.pointPrimitive instanceof ParticleSystem) {
+    switch (val) {
+      case "BoxEmitter":
+        point.pointPrimitive.emitter = new BoxEmitter(
+          new Cartesian3(10.0, 10.0, 10.0)
+        );
+        break;
+      case "CircleEmitter":
+        point.pointPrimitive.emitter = new CircleEmitter(2.0);
+        break;
+      case "ConeEmitter":
+        point.pointPrimitive.emitter = new ConeEmitter(
+          cesiumMath.toRadians(45.0)
+        );
+        break;
+      case "SphereEmitter":
+        point.pointPrimitive.emitter = new SphereEmitter(2.5);
+        break;
+      default:
+        point.pointPrimitive.emitter = new CircleEmitter(2.0);
+        break;
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 63 - 0
src/views/map3d/analysis/index.vue

@@ -0,0 +1,63 @@
+<template>
+  <div class="analysisTool">
+    <MeasureTool v-if="props.analysisType == '测量工具'" />
+    <FloodTool v-else-if="props.analysisType == '淹没分析'" />
+    <VideoOn v-else-if="props.analysisType == '视频融合'" />
+    <ViewshedTool v-else-if="props.analysisType == '可视域分析'" />
+    <VisibilityTool v-else-if="props.analysisType == '透视分析'" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { defineComponent } from "vue";
+import MeasureTool from "./tools/MeasureTool.vue";
+import FloodTool from "./tools/FloodTool.vue";
+import VideoOn from "./tools/VideoOn.vue";
+import ViewshedTool from "./tools/ViewshedTool.vue";
+import VisibilityTool from "./tools/VisibilityTool.vue";
+
+defineComponent({
+  MeasureTool,
+  FloodTool,
+  VideoOn,
+  ViewshedTool,
+  VisibilityTool,
+});
+
+const props = defineProps({
+  analysisType: {
+    type: String,
+    required: true,
+  },
+});
+</script>
+
+<style lang="scss" scoped>
+.analysisTool {
+  position: fixed;
+  top: 20%;
+  right: 0;
+  z-index: 2000;
+  height: auto;
+  width: 20%;
+  background: #1d1e1f;
+
+  ::-webkit-scrollbar {
+    /*滚动条整体样式*/
+    width: 2px; /*高宽分别对应横竖滚动条的尺寸*/
+    height: 1px;
+  }
+  ::-webkit-scrollbar-thumb {
+    /*滚动条里面小方块*/
+    border-radius: 10px;
+    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
+    background: #535353;
+  }
+  ::-webkit-scrollbar-track {
+    /*滚动条里面轨道*/
+    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
+    border-radius: 10px;
+    background: #ededed;
+  }
+}
+</style>

+ 80 - 0
src/views/map3d/analysis/tools/FloodTool.vue

@@ -0,0 +1,80 @@
+<template>
+  <el-card style="overflow: auto">
+    <el-input
+      style="margin-top: 10px"
+      v-for="(item, index) in floodConfig"
+      :key="index"
+      v-model="item.value"
+      :oninput="item.oninput"
+    >
+      <template #prepend>
+        {{ item.name }}
+      </template>
+    </el-input>
+
+    <el-button-group>
+      <el-button type="primary" text :icon="Edit" @click="floodClick"
+        >绘制</el-button
+      >
+      <el-button type="primary" text :icon="Delete" @click="clearFloodClick"
+        >清除</el-button
+      >
+    </el-button-group>
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+import { Delete, Edit } from "@element-plus/icons-vue";
+import { reactive } from "vue";
+import emitter from "@/mitt";
+import { Polygon } from "../../plot/graphicsDraw/areaDraw";
+import { FloodAnalysis } from "./floodTool";
+
+const floodConfig = reactive([
+  {
+    name: "高度",
+    value: 200,
+    oninput: "value=value.replace(/[^0-9.]/g,'')",
+  },
+  {
+    name: "速度",
+    value: 2,
+    oninput: "value=value.replace(/[^0-9.]/g,'')",
+  },
+]);
+
+let floodTool: FloodAnalysis;
+var rect: Polygon | null = new Polygon();
+function floodClick() {
+  rect = new Polygon();
+  rect.startDraw();
+  emitter.on("drawEnd", createFlood);
+}
+
+function clearFloodClick() {
+  floodTool.clear();
+  emitter.off("drawEnd", createFlood);
+}
+
+function createFlood() {
+  setTimeout(() => {
+    rect.stopDraw();
+    const pointArr = rect.pointList.slice(0, rect.pointList.length);
+    floodTool = new FloodAnalysis(
+      eval(floodConfig[0].value as unknown as string),
+      0,
+      1,
+      pointArr,
+      0.05
+    );
+    if (rect.areaPrimitive) {
+      window.Viewer.scene.groundPrimitives.remove(rect.areaPrimitive);
+    }
+    rect.disable();
+    rect = null;
+    floodTool.start();
+  }, 300);
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 61 - 0
src/views/map3d/analysis/tools/MeasureTool.vue

@@ -0,0 +1,61 @@
+<template>
+  <el-card style="overflow: auto">
+    <el-button-group v-for="(item, index) of measureList" :key="index">
+      <el-button
+        type="primary"
+        text
+        :icon="Edit"
+        @click="measureClick(item.enName)"
+        >{{ item.name }}</el-button
+      >
+      <el-button
+        type="primary"
+        text
+        :icon="Delete"
+        @click="clearMeasureClick(item.enName)"
+        >清除</el-button
+      >
+    </el-button-group>
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+import { Delete, Edit } from "@element-plus/icons-vue";
+import MeasurementCalc from "./measureTool";
+
+const measureTool = new MeasurementCalc();
+// * 测量类型
+const measureList = [
+  { name: "平面长度", enName: "getPlaneLength" },
+  { name: "平面面积", enName: "getPlaneArea" },
+  { name: "贴地长度", enName: "getGroundLength" },
+  { name: "贴地面积", enName: "getGroundArea" },
+];
+
+// * 开始测量
+function measureClick(name: string) {
+  switch (name) {
+    case "getPlaneLength":
+      measureTool[name]();
+      break;
+    case "getPlaneArea":
+      measureTool[name]();
+      break;
+    case "getGroundLength":
+      measureTool[name]();
+      break;
+    case "getGroundArea":
+      measureTool[name]();
+      break;
+    default:
+      break;
+  }
+}
+
+// * 清除测量结果
+const clearMeasureClick = (name: string) => {
+  measureTool.clearOne(name);
+};
+</script>
+
+<style lang="scss" scoped></style>

+ 72 - 0
src/views/map3d/analysis/tools/VideoOn.vue

@@ -0,0 +1,72 @@
+<!-- eslint-disable prettier/prettier -->
+<template>
+  <el-card style="overflow: auto">
+    <el-input style="margin-top: 10px" v-model="videoUrl">
+      <template #prepend> 视频地址 </template>
+    </el-input>
+    <el-button-group>
+      <el-button type="primary" text :icon="Edit" @click="videoClick"
+        >视频融合</el-button
+      >
+      <el-button type="primary" text :icon="Delete" @click="clearVideoClick"
+        >清除</el-button
+      >
+    </el-button-group>
+  </el-card>
+  <video id="myVideo" muted="" autoplay="" loop="" crossorigin="" controls="">
+    <source :src="videoUrl" type="video/webm" />
+  </video>
+</template>
+
+<script lang="ts" setup>
+import { Delete, Edit } from "@element-plus/icons-vue";
+import { ref, onMounted } from "vue";
+import emitter from "@/mitt";
+import { Rectangle } from "../../plot/graphicsDraw/areaDraw";
+import { VideoSynchronizer } from "cesium";
+
+const videoUrl = ref(
+  "http://localhost:8091/Videos/big-buck-bunny-trailer-small.webm"
+);
+
+var rect: Rectangle | null = new Rectangle();
+let videoEntity;
+function videoClick() {
+  rect = new Rectangle();
+  rect.startDraw();
+  emitter.on("drawEnd", VideoOn);
+}
+function clearVideoClick() {
+  window.Viewer.entities.remove(videoEntity);
+  rect.disable();
+  rect = null;
+  videoEntity = null;
+  emitter.off("drawEnd", VideoOn);
+}
+let videoElement;
+onMounted(() => {
+  videoElement = document.getElementById("myVideo");
+  videoElement.style.display = "none";
+
+  new VideoSynchronizer({
+    clock: window.Viewer.clock,
+    element: videoElement,
+  });
+});
+function VideoOn() {
+  setTimeout(() => {
+    videoEntity = window.Viewer.entities.add({
+      rectangle: {
+        coordinates: rect.areaPrimitive.geometryInstances.geometry.rectangle,
+        material: videoElement,
+      },
+    });
+    if (rect.areaPrimitive) {
+      window.Viewer.scene.groundPrimitives.remove(rect.areaPrimitive);
+    }
+    window.Viewer.clock.shouldAnimate = true;
+  }, 300);
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 97 - 0
src/views/map3d/analysis/tools/ViewshedTool.vue

@@ -0,0 +1,97 @@
+<template>
+  <el-card style="overflow: auto">
+    <el-input
+      style="margin-top: 10px"
+      v-for="(item, index) in viewshedConfig"
+      :key="index"
+      v-model="item.value"
+      :oninput="item.oninput"
+    >
+      <template #prepend>
+        {{ item.name }}
+      </template>
+    </el-input>
+    <el-button-group>
+      <el-button type="primary" text :icon="Edit" @click="viewshedClick"
+        >可视域分析</el-button
+      >
+      <el-button
+        type="primary"
+        text
+        :icon="Delete"
+        @click="clearViewshedClick()"
+        >清除</el-button
+      >
+    </el-button-group>
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+import { Delete, Edit } from "@element-plus/icons-vue";
+import { reactive } from "vue";
+import { ViewshedAnalysis } from "./viewshedTool/index";
+import {
+  Color,
+  ScreenSpaceEventHandler,
+  ScreenSpaceEventType,
+  defined,
+} from "cesium";
+
+const viewshedConfig = reactive([
+  {
+    name: "航向角",
+    value: 0,
+    oninput: "value=value.replace(/[^0-9.]/g,'')",
+  },
+  {
+    name: "俯仰角",
+    value: 0,
+    oninput: "value=value.replace(/[^0-9.]/g,'')",
+  },
+  {
+    name: "可视域水平夹角",
+    value: 90,
+    oninput: "value=value.replace(/[^0-9.]/g,'')",
+  },
+  {
+    name: "可视域垂直夹角",
+    value: 60,
+    oninput: "value=value.replace(/[^0-9.]/g,'')",
+  },
+]);
+let viewshedList = [];
+let drawHandler = new ScreenSpaceEventHandler(window.Viewer.scene.canvas);
+function viewshedClick() {
+  drawHandler = new ScreenSpaceEventHandler(window.Viewer.scene.canvas);
+  // * 监测鼠标左击事件
+  drawHandler.setInputAction((event) => {
+    let position = event.position;
+    if (!defined(position)) return;
+    let ray = window.Viewer.camera.getPickRay(position);
+    if (!defined(ray)) return;
+    let cartesian = window.Viewer.scene.globe.pick(ray, window.Viewer.scene);
+    if (!defined(cartesian)) return;
+    const viewshed = new ViewshedAnalysis({
+      viewPosition: cartesian,
+      viewDistance: 1000,
+      viewHeading: viewshedConfig[0].value,
+      viewPitch: viewshedConfig[1].value,
+      horizontalViewAngle: viewshedConfig[2].value,
+      verticalViewAngle: viewshedConfig[3].value,
+      visibleAreaColor: Color.GREEN,
+      invisibleAreaColor: Color.RED,
+    });
+    viewshedList.push(viewshed);
+  }, ScreenSpaceEventType.LEFT_CLICK);
+}
+function clearViewshedClick() {
+  drawHandler.destroy();
+  viewshedList.forEach((viewshed, i) => {
+    viewshedList[i] = null;
+    viewshed.clear();
+  });
+  viewshedList = [];
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 38 - 0
src/views/map3d/analysis/tools/VisibilityTool.vue

@@ -0,0 +1,38 @@
+<template>
+  <el-card style="overflow: auto">
+    <el-button-group>
+      <el-button type="primary" text :icon="Edit" @click="visibilityClick"
+        >透视分析</el-button
+      >
+      <el-button
+        type="primary"
+        text
+        :icon="Delete"
+        @click="clearVisibilityClick()"
+        >清除</el-button
+      >
+    </el-button-group>
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+import { Delete, Edit } from "@element-plus/icons-vue";
+import { VisibilityAnalysis } from "./visibilityTool";
+import { onUnmounted } from "vue";
+
+const visibilityTool = new VisibilityAnalysis();
+
+function visibilityClick() {
+  visibilityTool.analysisVisible();
+}
+
+function clearVisibilityClick() {
+  visibilityTool.clearAll();
+}
+
+onUnmounted(() => {
+  visibilityTool.destory();
+});
+</script>
+
+<style lang="scss" scoped></style>

+ 11 - 0
src/views/map3d/analysis/tools/floodTool/index.d.ts

@@ -0,0 +1,11 @@
+export declare class FloodAnalysis {
+  constructor(
+    height_max: number,
+    height_min: number,
+    step: number,
+    positionsArr: Cartesian3[],
+    speed: number
+  );
+  start: () => {};
+  clear: () => {};
+}

+ 75 - 0
src/views/map3d/analysis/tools/floodTool/index.ts

@@ -0,0 +1,75 @@
+import {
+  CallbackProperty,
+  Cartesian3,
+  Color,
+  PolygonHierarchy,
+  type Entity,
+  HeightReference,
+} from "cesium";
+
+export class FloodAnalysis {
+  polygonEntities: null | Entity;
+  extrudedHeight: number;
+  height_max: number;
+  height_min: number;
+  step: number;
+  polygon_degrees: Cartesian3[];
+  speed: number;
+  timer: number | null;
+  constructor(
+    height_max: number,
+    height_min: number,
+    step: number,
+    positionsArr: Cartesian3[],
+    speed: number
+  ) {
+    this.polygonEntities = null;
+    this.extrudedHeight = height_min;
+    this.height_max = height_max;
+    this.height_min = height_min;
+    this.step = step;
+    this.polygon_degrees = positionsArr;
+    this.speed = speed;
+    this.timer = 0;
+  }
+  _drawPoly() {
+    this.polygonEntities = window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new PolygonHierarchy(this.polygon_degrees),
+        material: Color.fromBytes(64, 157, 253, 100),
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+        extrudedHeight: new CallbackProperty(() => this.extrudedHeight, false),
+      },
+    });
+  }
+  start() {
+    const that = this;
+    this.timer = window.setInterval(() => {
+      if (
+        that.height_max > that.extrudedHeight &&
+        that.extrudedHeight >= that.height_min
+      ) {
+        that.extrudedHeight = that.extrudedHeight + that.step;
+      } else {
+        that.extrudedHeight = that.height_min;
+      }
+    }, that.speed * 1000);
+    that._drawPoly();
+  }
+  clear() {
+    if (this.timer) {
+      window.clearInterval(this.timer);
+      this.timer = null;
+    }
+    this.extrudedHeight = this.height_min;
+    window.Viewer.entities.remove(this.polygonEntities);
+    this.polygonEntities = null;
+  }
+  changeMapType(type: boolean) {
+    if (!type) {
+      this.polygonEntities && (this.polygonEntities.show = false);
+    } else {
+      this.polygonEntities && (this.polygonEntities.show = true);
+    }
+  }
+}

+ 39 - 0
src/views/map3d/analysis/tools/measureTool/globeTooltip.ts

@@ -0,0 +1,39 @@
+import type { Cartesian2 } from "cesium";
+
+export class GlobeTooltip {
+  protected _frameDiv: HTMLElement | undefined;
+  protected _div: HTMLElement;
+  protected _titleDiv: HTMLElement;
+  constructor(frameDiv: HTMLElement) {
+    const div = document.createElement("div");
+    div.className = "twipsy-right";
+    div.style.position = "absolute";
+
+    const arrow = document.createElement("div");
+    arrow.className = "twipsy-arrow";
+    div.appendChild(arrow);
+
+    const title = document.createElement("div");
+    title.className = "twipsy-inner";
+    div.appendChild(title);
+
+    frameDiv.appendChild(div);
+
+    this._frameDiv = frameDiv;
+    this._div = div;
+    this._titleDiv = title;
+  }
+  setVisible(visible: boolean) {
+    this._div.style.display = visible ? "block" : "none";
+  }
+  showAt(position: Cartesian2, message: string) {
+    this.setVisible(true);
+    this._titleDiv.innerHTML = message;
+    this._div.style.left = position.x + 10 + "px";
+    this._div.style.top = position.y - this._div.clientHeight / 2 + "px";
+  }
+  destory() {
+    this._frameDiv?.removeChild(this._div);
+    this._frameDiv = undefined;
+  }
+}

+ 552 - 0
src/views/map3d/analysis/tools/measureTool/index.ts

@@ -0,0 +1,552 @@
+import {
+  CallbackProperty,
+  Cartesian3,
+  Color,
+  defined,
+  Ellipsoid,
+  Entity,
+  HeightReference,
+  PolygonHierarchy,
+  PolylineGraphics,
+  Property,
+  ScreenSpaceEventHandler,
+  ScreenSpaceEventType,
+  Math as cesiumMath,
+  EllipsoidGeodesic,
+  sampleTerrainMostDetailed,
+  createWorldTerrain,
+  Cartographic,
+} from "cesium";
+import { GlobeTooltip } from "./globeTooltip";
+import { getCatesian3FromPX } from "@/views/map/plot/tools";
+import { infoBox } from "./infoBox";
+import * as turf from "@turf/turf";
+
+export default class MeasurementCalc {
+  planeLengthEntityList: any[];
+  planeLengthDivList: any[];
+  planeLengthListenList: any[];
+  planeAreaEntityList: any[];
+  planeAreaDivList: any[];
+  planeAreaListenList: any[];
+  groundLengthEntityList: any[];
+  groundLengthDivList: any[];
+  groundLengthListenList: any[];
+  groundAreaEntityList: any[];
+  groundAreaDivList: any[];
+  groundAreaListenList: any[];
+  pointList: any[];
+  handler: any;
+  constructor() {
+    this.planeLengthListenList = [];
+    this.planeLengthDivList = [];
+    this.planeLengthEntityList = [];
+    this.planeAreaEntityList = [];
+    this.planeAreaDivList = [];
+    this.planeAreaListenList = [];
+    this.groundLengthListenList = [];
+    this.groundLengthDivList = [];
+    this.groundLengthEntityList = [];
+    this.groundAreaEntityList = [];
+    this.groundAreaDivList = [];
+    this.groundAreaListenList = [];
+    this.pointList = [];
+    this.handler = new ScreenSpaceEventHandler(window.Viewer.scene.canvas);
+  }
+
+  // * 创建中间线条entity以适应动态数据
+  createLineEntity(isGround: boolean): Entity {
+    const update = () => {
+      return this.pointList;
+    };
+    return window.Viewer.entities.add({
+      polyline: new PolylineGraphics({
+        positions: new CallbackProperty(update, false),
+        show: true,
+        material: Color.BLUE,
+        clampToGround: isGround,
+      }),
+    });
+  }
+
+  // * 创建中间多边形entity以适应动态数据
+  createAreaEntity(isGround: boolean): Entity {
+    const update = () => {
+      return new PolygonHierarchy(this.pointList);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: isGround
+          ? HeightReference.CLAMP_TO_GROUND
+          : HeightReference.NONE,
+      },
+    });
+  }
+
+  //   * 获取平面长度
+  getPlaneLength() {
+    this.pointList = [];
+    let lineEntity: null | Entity;
+    const tooltip = new GlobeTooltip(window.Viewer.container);
+    tooltip.setVisible(false);
+    // * 监测鼠标左击事件
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+
+    // * 监测鼠标移动事件
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!defined(cartesian)) return;
+
+      const position = evt.endPosition;
+      if (this.pointList.length < 1) {
+        tooltip.showAt(position, "<p>选择起点</p>");
+        return;
+      }
+
+      if (this.pointList.length == 2 && !lineEntity) {
+        lineEntity = this.createLineEntity(false);
+        this.planeLengthEntityList.push(lineEntity);
+      }
+
+      const num = this.pointList.length;
+      let tip = "<p>点击添加下一个点</p>";
+      if (num > 2) {
+        tip += "<p>点击鼠标右键结束绘制</p>";
+      }
+      tooltip.showAt(position, tip);
+
+      this.pointList.pop();
+      this.pointList.push(cartesian);
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 监测鼠标右击事件
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 3) return;
+      let total = 0;
+      for (let i = 1; i < this.pointList.length; i++) {
+        const p1 = this.pointList[i - 1];
+        const p2 = this.pointList[i];
+        const dis = Cartesian3.distance(p1, p2) / 1000;
+        total += dis;
+      }
+      lineEntity &&
+        lineEntity.polyline &&
+        (lineEntity.polyline.positions = this.pointList as unknown as Property);
+      const { infoDiv, listenerEvt } = infoBox(
+        window.Viewer.container,
+        this.pointList[this.pointList.length - 1],
+        total.toFixed(2) + "km"
+      );
+      this.planeLengthDivList.push(infoDiv);
+      this.planeLengthListenList.push(listenerEvt);
+      tooltip.destory();
+      this.destoryMeasure();
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+
+  //   * 获取平面面积
+  getPlaneArea() {
+    this.pointList = [];
+    let areaEntity: null | Entity;
+    const tooltip = new GlobeTooltip(window.Viewer.container);
+    tooltip.setVisible(false);
+    // * 监测鼠标左击事件
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+
+    // * 监测鼠标移动事件
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!defined(cartesian)) return;
+
+      const position = evt.endPosition;
+      if (this.pointList.length < 1) {
+        tooltip.showAt(position, "<p>选择起点</p>");
+        return;
+      }
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !areaEntity) {
+        areaEntity = this.createAreaEntity(false);
+        this.planeAreaEntityList.push(areaEntity);
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+
+      const num = this.pointList.length;
+      let tip = "<p>点击添加下一个点</p>";
+      if (num > 3) {
+        tip += "<p>点击鼠标右键结束绘制</p>";
+      }
+      tooltip.showAt(position, tip);
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 监测鼠标右击事件
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 3) return;
+      const elliposid = Ellipsoid.WGS84,
+        lonLatArray = [];
+      for (const item of this.pointList) {
+        const cartographic = elliposid.cartesianToCartographic(item);
+        lonLatArray.push([
+          cesiumMath.toDegrees(cartographic.longitude),
+          cesiumMath.toDegrees(cartographic.latitude),
+        ]);
+      }
+      lonLatArray.push(lonLatArray[0]);
+      const polygonGeoJson = turf.polygon([lonLatArray]);
+      const total = turf.area(polygonGeoJson);
+      areaEntity &&
+        areaEntity.polygon &&
+        (areaEntity.polygon.hierarchy = new PolygonHierarchy(
+          this.pointList
+        ) as unknown as Property);
+      const { infoDiv, listenerEvt } = infoBox(
+        window.Viewer.container,
+        this.pointList[this.pointList.length - 1],
+        total.toFixed(2) + "km²"
+      );
+      this.planeAreaDivList.push(infoDiv);
+      this.planeAreaListenList.push(listenerEvt);
+      tooltip.destory();
+      this.destoryMeasure();
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+
+  //   * 获取贴地长度
+  getGroundLength() {
+    this.pointList = [];
+    let lineEntity: null | Entity;
+    const tooltip = new GlobeTooltip(window.Viewer.container);
+    tooltip.setVisible(false);
+    // * 监测鼠标左击事件
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+
+    // * 监测鼠标移动事件
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!defined(cartesian)) return;
+
+      const position = evt.endPosition;
+      if (this.pointList.length < 1) {
+        tooltip.showAt(position, "<p>选择起点</p>");
+        return;
+      }
+
+      if (this.pointList.length == 2 && !lineEntity) {
+        lineEntity = this.createLineEntity(true);
+        this.groundLengthEntityList.push(lineEntity);
+      }
+
+      const num = this.pointList.length;
+      let tip = "<p>点击添加下一个点</p>";
+      if (num > 2) {
+        tip += "<p>点击鼠标右键结束绘制</p>";
+      }
+      tooltip.showAt(position, tip);
+
+      this.pointList.pop();
+      this.pointList.push(cartesian);
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 监测鼠标右击事件
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 3) return;
+
+      // * 进行插值
+      const pnts = [].concat(...this.pointList);
+      const num = pnts.length;
+      const tempPositions = [];
+      for (let i = 1; i < num; i++) {
+        const p1 = pnts[i - 1];
+        const p2 = pnts[i];
+        const ellipsoid = window.Viewer.scene.globe.ellipsoid;
+        const c1 = ellipsoid.cartesianToCartographic(p1),
+          c2 = ellipsoid.cartesianToCartographic(p2);
+        const cm = new EllipsoidGeodesic(c1, c2).interpolateUsingFraction(0.5);
+        const cp = ellipsoid.cartographicToCartesian(cm);
+        tempPositions.push(p1);
+        tempPositions.push(cp);
+      }
+      const last = pnts[num - 1];
+      tempPositions.push(last);
+      let total = 0;
+      for (let i = 1; i < tempPositions.length; i++) {
+        const p1 = tempPositions[i - 1];
+        const p2 = tempPositions[i];
+        const dis = Cartesian3.distance(p1, p2) / 1000;
+        total += dis;
+      }
+
+      lineEntity &&
+        lineEntity.polyline &&
+        (lineEntity.polyline.positions = this.pointList as unknown as Property);
+      const { infoDiv, listenerEvt } = infoBox(
+        window.Viewer.container,
+        this.pointList[this.pointList.length - 1],
+        total.toFixed(2) + "km"
+      );
+      this.groundLengthDivList.push(infoDiv);
+      this.groundLengthListenList.push(listenerEvt);
+      tooltip.destory();
+      this.destoryMeasure();
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+
+  //   * 获取贴地面积
+  getGroundArea() {
+    this.pointList = [];
+    let areaEntity: null | Entity;
+    const tooltip = new GlobeTooltip(window.Viewer.container);
+    tooltip.setVisible(false);
+    // * 监测鼠标左击事件
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+
+    // * 监测鼠标移动事件
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!defined(cartesian)) return;
+
+      const position = evt.endPosition;
+      if (this.pointList.length < 1) {
+        tooltip.showAt(position, "<p>选择起点</p>");
+        return;
+      }
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !areaEntity) {
+        areaEntity = this.createAreaEntity(false);
+        this.groundAreaEntityList.push(areaEntity);
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+
+      const num = this.pointList.length;
+      let tip = "<p>点击添加下一个点</p>";
+      if (num > 3) {
+        tip += "<p>点击鼠标右键结束绘制</p>";
+      }
+      tooltip.showAt(position, tip);
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 监测鼠标右击事件
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 3) return;
+      const elliposid = Ellipsoid.WGS84,
+        lonLatArray = [];
+      for (const item of this.pointList) {
+        const cartographic = elliposid.cartesianToCartographic(item);
+        lonLatArray.push([
+          cesiumMath.toDegrees(cartographic.longitude),
+          cesiumMath.toDegrees(cartographic.latitude),
+        ]);
+      }
+      lonLatArray.push(lonLatArray[0]);
+      // * 将多边形转三角集
+      const polygonGeoJson = turf.polygon([lonLatArray]);
+      const triangles = turf.tesselate(polygonGeoJson);
+      const promiseArray = [];
+      for (const triangle of triangles.features) {
+        const area = turf.area(triangle);
+        const cellSize = Math.sqrt(area / 1000);
+        // * 通过三个点形成一个矩形
+        const enveloped = turf.envelope(triangle);
+        // * 获取最大及最小的xy值
+        const bbox = turf.bbox(enveloped);
+        // * 通过最大最小点形成矩形内插值,返回点集
+        const grid = turf.pointGrid(bbox, cellSize, { units: "meters" });
+        // * 获取所有落在三角形内的点
+        const trianglePoint = turf.pointsWithinPolygon(grid, triangle);
+        const allPos = [];
+        for (const triPoint of trianglePoint.features) {
+          allPos.push(
+            Cartographic.fromDegrees(
+              triPoint.geometry.coordinates[0],
+              triPoint.geometry.coordinates[1]
+            )
+          );
+        }
+        const promisePos = sampleTerrainMostDetailed(
+          createWorldTerrain(),
+          allPos
+        );
+        promiseArray.push(promisePos);
+      }
+      Promise.all(promiseArray).then((updatedPositions) => {
+        let groundArea = 0;
+        for (let m = 0; m < updatedPositions.length; m++) {
+          const mapPos = new Map();
+          for (let i = 0; i < updatedPositions[m].length; i++) {
+            mapPos.set(
+              updatedPositions[m][i].longitude.toString() +
+                updatedPositions[m][i].latitude.toString(),
+              updatedPositions[m][i].height
+            );
+          }
+          const area = turf.area(triangles.features[m]);
+          const cellSize = Math.sqrt(area / 1000);
+          // * 通过三个点形成一个矩形
+          const enveloped = turf.envelope(triangles.features[m]);
+          // * 获取最大及最小的xy值
+          const bbox = turf.bbox(enveloped);
+          // * 通过最大最小点形成矩形内插值,返回点集
+          const grid = turf.pointGrid(bbox, cellSize, { units: "meters" });
+          // * 获取所有落在三角形内的点
+          const trianglePoint = turf.pointsWithinPolygon(
+            grid,
+            triangles.features[m]
+          );
+          const tin = turf.tin(trianglePoint);
+          for (let j = 0; j < tin.features.length; j++) {
+            const car3Array = [];
+            for (let k = 0; k < 3; k++) {
+              const lon = tin.features[j].geometry.coordinates[0][k][0];
+              const lat = tin.features[j].geometry.coordinates[0][k][1];
+              const car2g = Cartographic.fromDegrees(lon, lat);
+              const height = mapPos.get(
+                car2g.longitude.toString() + car2g.latitude.toString()
+              );
+              car2g.height = height;
+              car3Array.push(car2g);
+            }
+            const firstPoint2car3 = Cartesian3.fromRadians(
+              car3Array[0].longitude,
+              car3Array[0].latitude,
+              car3Array[0].height
+            );
+            const secondPoint2car3 = Cartesian3.fromRadians(
+              car3Array[1].longitude,
+              car3Array[1].latitude,
+              car3Array[1].height
+            );
+            const thirdPoint2car3 = Cartesian3.fromRadians(
+              car3Array[2].longitude,
+              car3Array[2].latitude,
+              car3Array[2].height
+            );
+            const a = Cartesian3.distance(firstPoint2car3, secondPoint2car3);
+            const b = Cartesian3.distance(thirdPoint2car3, secondPoint2car3);
+            const c = Cartesian3.distance(firstPoint2car3, thirdPoint2car3);
+            const p = (a + b + c) / 2;
+
+            groundArea += Math.sqrt(p * (p - a) * (p - b) * (p - c));
+          }
+        }
+        areaEntity &&
+          areaEntity.polygon &&
+          (areaEntity.polygon.hierarchy = new PolygonHierarchy(
+            this.pointList
+          ) as unknown as Property);
+        const { infoDiv, listenerEvt } = infoBox(
+          window.Viewer.container,
+          this.pointList[this.pointList.length - 1],
+          groundArea.toFixed(2) + "km²"
+        );
+        this.groundAreaDivList.push(infoDiv);
+        this.groundAreaListenList.push(listenerEvt);
+        tooltip.destory();
+        this.destoryMeasure();
+      });
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+
+  //   * 清除销毁测量
+  destoryMeasure() {
+    this.handler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+    this.handler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
+    this.handler.removeInputAction(ScreenSpaceEventType.RIGHT_CLICK);
+  }
+
+  //   * 清除某一类测量
+  clearOne(name: string) {
+    switch (name) {
+      case "getPlaneLength":
+        this.planeLengthDivList.forEach((item) => {
+          window.Viewer.container.removeChild(item);
+        });
+        this.planeLengthListenList.forEach((item) => {
+          window.Viewer.scene.postRender.addEventListener(item);
+        });
+        this.planeLengthEntityList.forEach((item) => {
+          window.Viewer.entities.remove(item);
+        });
+        this.planeLengthListenList = [];
+        this.planeLengthDivList = [];
+        this.planeLengthEntityList = [];
+        this.pointList = [];
+        break;
+      case "getPlaneArea":
+        this.planeAreaDivList.forEach((item) => {
+          window.Viewer.container.removeChild(item);
+        });
+        this.planeAreaListenList.forEach((item) => {
+          window.Viewer.scene.postRender.addEventListener(item);
+        });
+        this.planeAreaEntityList.forEach((item) => {
+          window.Viewer.entities.remove(item);
+        });
+        this.planeAreaListenList = [];
+        this.planeAreaDivList = [];
+        this.planeAreaEntityList = [];
+        this.pointList = [];
+        break;
+      case "getGroundLength":
+        this.groundLengthDivList.forEach((item) => {
+          window.Viewer.container.removeChild(item);
+        });
+        this.groundLengthListenList.forEach((item) => {
+          window.Viewer.scene.postRender.addEventListener(item);
+        });
+        this.groundLengthEntityList.forEach((item) => {
+          window.Viewer.entities.remove(item);
+        });
+        this.groundLengthListenList = [];
+        this.groundLengthDivList = [];
+        this.groundLengthEntityList = [];
+        this.pointList = [];
+        break;
+      case "getGroundArea":
+        this.groundAreaDivList.forEach((item) => {
+          window.Viewer.container.removeChild(item);
+        });
+        this.groundAreaListenList.forEach((item) => {
+          window.Viewer.scene.postRender.addEventListener(item);
+        });
+        this.groundAreaEntityList.forEach((item) => {
+          window.Viewer.entities.remove(item);
+        });
+        this.groundAreaListenList = [];
+        this.groundAreaDivList = [];
+        this.groundAreaEntityList = [];
+        this.pointList = [];
+        break;
+      default:
+        break;
+    }
+  }
+}

+ 64 - 0
src/views/map3d/analysis/tools/measureTool/infoBox.ts

@@ -0,0 +1,64 @@
+import { Cartesian2, Cartesian3, SceneTransforms } from "cesium";
+
+export function infoBox(
+  frameDiv: HTMLElement,
+  cartesain: Cartesian2,
+  info: string
+) {
+  const cartographic =
+    window.Viewer.scene.globe.ellipsoid.cartesianToCartographic(cartesain);
+  const height = window.Viewer.scene.globe.getHeight(cartographic);
+
+  const newCartesain = Cartesian3.fromRadians(
+    cartographic.longitude,
+    cartographic.latitude,
+    height
+  );
+
+  let coordinates = SceneTransforms.wgs84ToWindowCoordinates(
+    window.Viewer.scene,
+    newCartesain
+  );
+  const infoDiv = document.createElement("div");
+  infoDiv.className = "infoBox";
+  infoDiv.innerHTML = info;
+  frameDiv.appendChild(infoDiv);
+  positionPopUp(coordinates, infoDiv);
+
+  const listenerEvt = () => {
+    const new_cartesain = Cartesian3.fromRadians(
+      cartographic.longitude,
+      cartographic.latitude,
+      window.Viewer.scene.globe.getHeight(cartographic)
+    );
+    const changeCoordinates = SceneTransforms.wgs84ToWindowCoordinates(
+      window.Viewer.scene,
+      new_cartesain
+    );
+    if (
+      coordinates &&
+      changeCoordinates &&
+      coordinates.x &&
+      changeCoordinates.x &&
+      coordinates.y &&
+      changeCoordinates.y
+    ) {
+      if (
+        coordinates.x !== changeCoordinates.x ||
+        coordinates.y !== changeCoordinates.y
+      ) {
+        positionPopUp(changeCoordinates, infoDiv);
+        coordinates = changeCoordinates;
+      }
+    }
+  };
+  window.Viewer.scene.postRender.addEventListener(listenerEvt);
+  return { infoDiv, listenerEvt };
+}
+
+function positionPopUp(coordinates: Cartesian2, infoDiv: HTMLDivElement) {
+  const x = coordinates.x - infoDiv.offsetWidth / 2;
+  const y = coordinates.y - infoDiv.offsetHeight - 65;
+  infoDiv.style.left = x + "px";
+  infoDiv.style.top = y + "px";
+}

+ 113 - 0
src/views/map3d/analysis/tools/viewshedTool/glsl.ts

@@ -0,0 +1,113 @@
+export default `
+#define USE_CUBE_MAP_SHADOW true
+uniform sampler2D colorTexture;
+uniform sampler2D depthTexture;
+in vec2 v_textureCoordinates;
+uniform mat4 camera_projection_matrix;
+uniform mat4 camera_view_matrix;
+uniform samplerCube shadowMap_textureCube;
+uniform mat4 shadowMap_matrix;
+uniform vec4 shadowMap_lightPositionEC;
+uniform vec4 shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness;
+uniform vec4 shadowMap_texelSizeDepthBiasAndNormalShadingSmooth;
+uniform float helsing_viewDistance;
+uniform vec4 helsing_visibleAreaColor;
+uniform vec4 helsing_invisibleAreaColor;
+struct zx_shadowParameters
+{
+    vec3 texCoords;
+    float depthBias;
+    float depth;
+    float nDotL;
+    vec2 texelStepSize;
+    float normalShadingSmooth;
+    float darkness;
+};
+float czm_shadowVisibility(samplerCube shadowMap,zx_shadowParameters shadowParameters)
+{
+    float depthBias = shadowParameters.depthBias;
+    float depth = shadowParameters.depth;
+    float nDotL = shadowParameters.nDotL;
+    float normalShadingSmooth = shadowParameters.normalShadingSmooth;
+    float darkness = shadowParameters.darkness;
+    vec3 uvw = shadowParameters.texCoords;
+    depth -= depthBias;
+    float visibility = czm_shadowDepthCompare(shadowMap, uvw, depth);
+    return czm_private_shadowVisibility(visibility, nDotL, normalShadingSmooth, darkness);
+}
+vec4 getPositionEC(){
+    return czm_windowToEyeCoordinates(gl_FragCoord);
+}
+vec3 getNormalEC(){
+    return vec3(1.);
+}
+vec4 toEye(in vec2 uv,in float depth){
+    vec2 xy=vec2((uv.x*2.-1.),(uv.y*2.-1.));
+    vec4 posInCamera=czm_inverseProjection*vec4(xy,depth,1.);
+    posInCamera=posInCamera/posInCamera.w;
+    return posInCamera;
+}
+float getDepth(in vec4 depth){
+    float z_window=czm_unpackDepth(depth);
+    z_window=czm_reverseLogDepth(z_window);
+    float n_range=czm_depthRange.near;
+    float f_range=czm_depthRange.far;
+    return(2.*z_window-n_range-f_range)/(f_range-n_range);
+}
+float shadow(in vec4 positionEC,in float depth){
+    vec3 normalEC=getNormalEC();
+    zx_shadowParameters shadowParameters;
+    shadowParameters.texelStepSize=shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.xy;
+    shadowParameters.depthBias=shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.z;
+    shadowParameters.normalShadingSmooth=shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.w;
+    shadowParameters.darkness=shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness.w;
+    vec3 directionEC=positionEC.xyz-shadowMap_lightPositionEC.xyz;
+    float distance=length(directionEC);
+    directionEC=normalize(directionEC);
+    float radius=shadowMap_lightPositionEC.w;
+    if(distance>radius)
+    {
+        return 2.;
+    }
+    vec3 directionWC=czm_inverseViewRotation*directionEC;
+    shadowParameters.depth=distance/radius-.0003;
+    shadowParameters.nDotL=clamp(dot(normalEC,-directionEC),0.,1.);
+    shadowParameters.texCoords=directionWC;
+    float visibility=czm_shadowVisibility(shadowMap_textureCube,shadowParameters);
+    return visibility;
+}
+bool visible(in vec4 result)
+{
+    result.x/=result.w;
+    result.y/=result.w;
+    result.z/=result.w;
+    return result.x>=-1.&&result.x<=1.
+    &&result.y>=-1.&&result.y<=1.
+    &&result.z>=-1.&&result.z<=1.;
+}
+void main(){
+    out_FragColor=texture(colorTexture,v_textureCoordinates);
+    float depth=getDepth(texture(depthTexture,v_textureCoordinates));
+    vec4 viewPos=toEye(v_textureCoordinates,depth);
+    // 世界坐标
+    vec4 wordPos=czm_inverseView*viewPos;
+    // 虚拟相机中坐标
+    vec4 vcPos=camera_view_matrix*wordPos;
+    float near=.001*helsing_viewDistance;
+    float dis=length(vcPos.xyz);
+    if(dis>near&&dis<helsing_viewDistance){
+        // 透视投影
+        vec4 posInEye=camera_projection_matrix*vcPos;
+        // 可视区颜色
+        // vec4 helsing_visibleAreaColor=vec4(0.,1.,0.,.5);
+        // vec4 helsing_invisibleAreaColor=vec4(1.,0.,0.,.5);
+        if(visible(posInEye)){
+            float vis=shadow(viewPos,depth);
+            if(vis>.3){
+                out_FragColor=mix(out_FragColor,helsing_visibleAreaColor,.5);
+            }else{
+                out_FragColor=mix(out_FragColor,helsing_invisibleAreaColor,.5);
+            }
+        }
+    }
+}`;

+ 292 - 0
src/views/map3d/analysis/tools/viewshedTool/index.ts

@@ -0,0 +1,292 @@
+import {
+  Camera,
+  Cartesian2,
+  Cartesian3,
+  Cartesian4,
+  Color,
+  ColorGeometryInstanceAttribute,
+  Entity,
+  FrustumOutlineGeometry,
+  GeometryInstance,
+  HeadingPitchRoll,
+  Matrix3,
+  Matrix4,
+  PerInstanceColorAppearance,
+  PerspectiveFrustum,
+  PostProcessStage,
+  Primitive,
+  Quaternion,
+  ShadowMap,
+  ShowGeometryInstanceAttribute,
+  Transforms,
+  Math as cesiumMath,
+  ShadowMode,
+} from "cesium";
+import glsl from "./glsl.ts";
+
+export class ViewshedAnalysis {
+  private viewPosition: Cartesian3;
+  private viewPositionEnd: Cartesian3;
+  private viewDistance: number;
+  private viewHeading: number;
+  private viewPitch: number;
+  private horizontalViewAngle: number;
+  private verticalViewAngle: number;
+  private visibleAreaColor: any;
+  private invisibleAreaColor: any;
+  private sketch: null | Entity;
+  private postStage: null | PostProcessStage;
+  private frustumOutline: null | Primitive;
+  private lightCamera: Camera | undefined;
+  private shadowMap: any;
+  constructor(option: {
+    viewPosition: Cartesian3;
+    viewPositionEnd: Cartesian3;
+    viewDistance: number;
+    viewHeading: number;
+    viewPitch: number;
+    horizontalViewAngle: number;
+    verticalViewAngle: number;
+    visibleAreaColor: any;
+    invisibleAreaColor: any;
+  }) {
+    this.viewPosition = option.viewPosition;
+    this.viewPositionEnd = option.viewPositionEnd;
+    this.viewDistance = option.viewDistance;
+    this.viewHeading = option.viewHeading;
+    this.viewPitch = option.viewPitch;
+    this.horizontalViewAngle = option.horizontalViewAngle;
+    this.verticalViewAngle = option.verticalViewAngle;
+    this.visibleAreaColor = option.visibleAreaColor;
+    this.invisibleAreaColor = option.invisibleAreaColor;
+    this.sketch = null;
+    this.postStage = null;
+    this.frustumOutline = null;
+    this.update();
+  }
+  private add() {
+    this.createLightCamera();
+    this.createShadowMap();
+    this.createPostStage();
+    this.drawFrustumOutline();
+    this.drawSketch();
+  }
+
+  private update() {
+    this.clear();
+    this.add();
+  }
+  clear() {
+    if (this.sketch) {
+      window.Viewer.entities.removeById(this.sketch.id);
+      this.sketch = null;
+    }
+    if (this.frustumOutline) {
+      window.Viewer.scene.primitives.remove(this.frustumOutline);
+      this.frustumOutline = null;
+    }
+    if (this.postStage) {
+      window.Viewer.scene.postProcessStages.remove(this.postStage);
+      this.postStage = null;
+    }
+  }
+
+  private createLightCamera() {
+    this.lightCamera = new Camera(window.Viewer.scene);
+    this.lightCamera.position = this.viewPosition;
+    this.lightCamera.frustum.near = this.viewDistance * 0.001;
+    this.lightCamera.frustum.far = this.viewDistance;
+    const hr = cesiumMath.toRadians(this.horizontalViewAngle);
+    const vr = cesiumMath.toRadians(this.verticalViewAngle);
+    const aspectRatio =
+      (this.viewDistance * Math.tan(hr / 2) * 2) /
+      (this.viewDistance * Math.tan(vr / 2) * 2);
+    (this.lightCamera.frustum as PerspectiveFrustum).aspectRatio = aspectRatio;
+    (this.lightCamera.frustum as PerspectiveFrustum).fov = hr > vr ? hr : vr;
+    this.lightCamera.setView({
+      destination: this.viewPosition,
+      orientation: {
+        heading: cesiumMath.toRadians(this.viewHeading),
+        pitch: cesiumMath.toRadians(this.viewPitch),
+        roll: 0,
+      },
+    });
+  }
+  private createShadowMap() {
+    this.shadowMap = new ShadowMap({
+      context: window.Viewer.scene.context,
+      lightCamera: this.lightCamera,
+      enabled: true,
+      isPointLight: true,
+      pointLightRadius: this.viewDistance,
+      cascadesEnabled: false,
+      size: 256,
+      softShadows: true,
+      normalOffset: true,
+      fromLightSource: false,
+    });
+    window.Viewer.scene.shadowMap = this.shadowMap;
+    window.Viewer.scene.globe.shadows = ShadowMode.ENABLED;
+    window.Viewer.scene.globe.depthTestAgainstTerrain = true;
+    window.Viewer.scene.logarithmicDepthBuffer = true;
+  }
+  private createPostStage() {
+    const fs = glsl;
+    const postStage = new PostProcessStage({
+      fragmentShader: fs,
+      uniforms: {
+        shadowMap_textureCube: () => {
+          this.shadowMap.update(
+            Reflect.get(window.Viewer.scene, "_frameState")
+          );
+          return Reflect.get(this.shadowMap, "_shadowMapTexture");
+        },
+        shadowMap_matrix: () => {
+          this.shadowMap.update(
+            Reflect.get(window.Viewer.scene, "_frameState")
+          );
+          return Reflect.get(this.shadowMap, "_shadowMapMatrix");
+        },
+        shadowMap_lightPositionEC: () => {
+          this.shadowMap.update(
+            Reflect.get(window.Viewer.scene, "_frameState")
+          );
+          return Reflect.get(this.shadowMap, "_lightPositionEC");
+        },
+        shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness: () => {
+          this.shadowMap.update(
+            Reflect.get(window.Viewer.scene, "_frameState")
+          );
+          const bias = this.shadowMap._pointBias;
+          return Cartesian4.fromElements(
+            bias.normalOffsetScale,
+            this.shadowMap._distance,
+            this.shadowMap.maximumDistance,
+            0.0,
+            new Cartesian4()
+          );
+        },
+        shadowMap_texelSizeDepthBiasAndNormalShadingSmooth: () => {
+          this.shadowMap.update(
+            Reflect.get(window.Viewer.scene, "_frameState")
+          );
+          const bias = this.shadowMap._pointBias;
+          const scratchTexelStepSize = new Cartesian2();
+          const texelStepSize = scratchTexelStepSize;
+          texelStepSize.x = 1.0 / this.shadowMap._textureSize.x;
+          texelStepSize.y = 1.0 / this.shadowMap._textureSize.y;
+
+          return Cartesian4.fromElements(
+            texelStepSize.x,
+            texelStepSize.y,
+            bias.depthBias,
+            bias.normalShadingSmooth,
+            new Cartesian4()
+          );
+        },
+        camera_projection_matrix: this.lightCamera!.frustum.projectionMatrix,
+        camera_view_matrix: this.lightCamera!.viewMatrix,
+        helsing_viewDistance: () => {
+          return this.viewDistance;
+        },
+        helsing_visibleAreaColor: this.visibleAreaColor,
+        helsing_invisibleAreaColor: this.invisibleAreaColor,
+      },
+    });
+    this.postStage = window.Viewer.scene.postProcessStages.add(postStage);
+  }
+  private drawFrustumOutline() {
+    const scratchRight = new Cartesian3();
+    const scratchRotation = new Matrix3();
+    const scratchOrientation = new Quaternion();
+    const direction = this.lightCamera!.directionWC;
+    const up = this.lightCamera!.upWC;
+    let right = this.lightCamera!.rightWC;
+    right = Cartesian3.negate(right, scratchRight);
+    const rotation = scratchRotation;
+    Matrix3.setColumn(rotation, 0, right, rotation);
+    Matrix3.setColumn(rotation, 1, up, rotation);
+    Matrix3.setColumn(rotation, 2, direction, rotation);
+    const orientation = Quaternion.fromRotationMatrix(
+      rotation,
+      scratchOrientation
+    );
+
+    const instance = new GeometryInstance({
+      geometry: new FrustumOutlineGeometry({
+        frustum: this.lightCamera!.frustum,
+        origin: this.viewPosition,
+        orientation: orientation,
+      }),
+      id: Math.random().toString(36).substr(2),
+      attributes: {
+        color: ColorGeometryInstanceAttribute.fromColor(Color.YELLOWGREEN),
+        show: new ShowGeometryInstanceAttribute(true),
+      },
+    });
+
+    this.frustumOutline = window.Viewer.scene.primitives.add(
+      new Primitive({
+        geometryInstances: [instance],
+        appearance: new PerInstanceColorAppearance({
+          flat: true,
+          translucent: false,
+        }),
+      })
+    );
+  }
+  private drawSketch() {
+    this.sketch = window.Viewer.entities.add({
+      name: "sketch",
+      position: this.viewPosition,
+      orientation: Transforms.headingPitchRollQuaternion(
+        this.viewPosition,
+        HeadingPitchRoll.fromDegrees(
+          this.viewHeading - this.horizontalViewAngle,
+          this.viewPitch,
+          0.0
+        )
+      ),
+      ellipsoid: {
+        radii: new Cartesian3(
+          this.viewDistance,
+          this.viewDistance,
+          this.viewDistance
+        ),
+        minimumClock: cesiumMath.toRadians(-this.horizontalViewAngle / 2),
+        maximumClock: cesiumMath.toRadians(this.horizontalViewAngle / 2),
+        minimumCone: cesiumMath.toRadians(this.verticalViewAngle + 7.75),
+        maximumCone: cesiumMath.toRadians(180 - this.verticalViewAngle - 7.75),
+        fill: false,
+        outline: true,
+        subdivisions: 256,
+        stackPartitions: 64,
+        slicePartitions: 64,
+        outlineColor: Color.YELLOWGREEN,
+      },
+    });
+  }
+}
+
+function getHeadingOrPitch(
+  type: string,
+  fromPosition: Cartesian3,
+  toPosition: Cartesian3
+): number {
+  const finalPosition = new Cartesian3();
+  const matrix4 = Transforms.eastNorthUpToFixedFrame(fromPosition);
+  Matrix4.inverse(matrix4, matrix4);
+  Matrix4.multiplyByPoint(matrix4, toPosition, finalPosition);
+  Cartesian3.normalize(finalPosition, finalPosition);
+  switch (type) {
+    case "heading":
+      return cesiumMath.toDegrees(Math.atan2(finalPosition.x, finalPosition.y));
+      break;
+    case "pitch":
+      return cesiumMath.toDegrees(Math.asin(finalPosition.z));
+      break;
+    default:
+      return cesiumMath.toDegrees(Math.atan2(finalPosition.x, finalPosition.y));
+      break;
+  }
+}

+ 147 - 0
src/views/map3d/analysis/tools/visibilityTool/index.ts

@@ -0,0 +1,147 @@
+import { getCatesian3FromPX } from "@/views/map/plot/tools";
+import {
+  CallbackProperty,
+  Cartesian3,
+  Cesium3DTileset,
+  Color,
+  Entity,
+  HeadingPitchRange,
+  PolylineGraphics,
+  Ray,
+  ScreenSpaceEventHandler,
+  ScreenSpaceEventType,
+  defined,
+} from "cesium";
+
+export class VisibilityAnalysis {
+  handler: any;
+  planeLineEntityList: any[];
+  pointList: any[];
+  tileset: any;
+  constructor() {
+    this.planeLineEntityList = [];
+    this.pointList = [];
+    this.handler = new ScreenSpaceEventHandler(window.Viewer.scene.canvas);
+    this.add3DTile(
+      "http://localhost:8091/Cesium3DTiles/Tilesets/Tileset/tileset.json"
+    );
+  }
+
+  // * 添加3DTile
+  async add3DTile(url: string) {
+    this.tileset = await Cesium3DTileset.fromUrl(url, {});
+
+    window.Viewer.scene.primitives.add(this.tileset);
+    window.Viewer.zoomTo(this.tileset, new HeadingPitchRange(0.0, -0.3, 0.0));
+  }
+
+  // * 创建中间线条entity以适应动态数据
+  createLineEntity(isGround: boolean): Entity {
+    const update = () => {
+      return this.pointList;
+    };
+    return window.Viewer.entities.add({
+      polyline: new PolylineGraphics({
+        positions: new CallbackProperty(update, false),
+        show: true,
+        material: Color.BLUE,
+        clampToGround: isGround,
+      }),
+    });
+  }
+
+  // * 绘制线
+  drawLine(leftPoint: Cartesian3, secPoint: Cartesian3, color: Color) {
+    const line = window.Viewer.entities.add({
+      polyline: {
+        positions: [leftPoint, secPoint],
+        width: 1,
+        material: color,
+        depthFailMaterial: color,
+      },
+    });
+    this.planeLineEntityList.push(line);
+  }
+
+  analysisVisible() {
+    this.pointList = [];
+    let lineEntity: null | Entity;
+    // * 监测鼠标左击事件
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      const num = this.pointList.length;
+      if (num == 1) {
+        this.pointList.push(cartesian.clone());
+      } else {
+        this.pointList.push(cartesian.clone());
+        window.Viewer.entities.remove(lineEntity);
+
+        // 计算射线的方向
+        const direction = Cartesian3.normalize(
+          Cartesian3.subtract(
+            this.pointList[1],
+            this.pointList[0],
+            new Cartesian3()
+          ),
+          new Cartesian3()
+        );
+        // 建立射线
+        const ray = new Ray(this.pointList[0], direction);
+        const result = window.Viewer.scene.globe.pick(ray, window.Viewer.scene); // 计算交互点,返回第一个
+
+        if (result !== undefined && result !== null) {
+          this.drawLine(result, this.pointList[0], Color.GREEN); // 可视区域
+          this.drawLine(result, this.pointList[1], Color.RED); // 不可视区域
+        } else {
+          const tileResult = window.Viewer.scene.pickFromRay(ray);
+          if (defined(tileResult) && defined(tileResult.object)) {
+            this.drawLine(tileResult.position, this.pointList[0], Color.GREEN); // 可视区域
+            this.drawLine(tileResult.position, this.pointList[1], Color.RED); // 不可视区域
+          } else {
+            this.drawLine(this.pointList[0], this.pointList[1], Color.GREEN);
+          }
+        }
+        this.destoryHandler();
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+
+    // * 监测鼠标移动事件
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!defined(cartesian)) return;
+
+      if (this.pointList.length == 2 && !lineEntity) {
+        lineEntity = this.createLineEntity(false);
+      }
+
+      this.pointList.pop();
+      this.pointList.push(cartesian);
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+
+  //   * 清除销毁
+  destoryHandler() {
+    this.handler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+    this.handler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
+    this.handler.removeInputAction(ScreenSpaceEventType.RIGHT_CLICK);
+  }
+
+  //   * 清除所有已绘制图形
+  clearAll() {
+    this.destoryHandler();
+    this.planeLineEntityList.forEach((item) => {
+      window.Viewer.entities.remove(item);
+    });
+    this.planeLineEntityList = [];
+    this.pointList = [];
+  }
+
+  //   * 销毁实例
+  destory() {
+    this.clearAll();
+    window.Viewer.scene.primitives.remove(this.tileset);
+    this.handler.destroy();
+    this.handler = undefined;
+  }
+}

+ 179 - 0
src/views/map3d/index.vue

@@ -0,0 +1,179 @@
+<template>
+	<div id="cesiumContainer" class="jt-map"></div>
+	<!-- <div id="cesiumContainer2" class="mapview" style="display: none;"></div> -->
+</template>
+<script>
+	import * as jt3dSDK from '@/jtMap3d/index.js';
+
+	let jt3d = undefined;
+	
+	export default {
+
+		/* 数据 */
+		data() {
+			return {
+
+			}
+		},
+
+		/* 方法 */
+		methods: {
+			/**
+			 * 初始化
+			 */
+			init(el) {
+				//初始化大球
+				this.initMap3d(el);
+
+
+				//设置默认视图
+				this.setView(this.jt3d);
+
+				// 初始化项目区域范围视角
+				this.fullMap(this.jt3d);
+
+				//加载天空盒子
+				// this.addSkybox(this.jt3d);
+
+				//单击事件
+				// this.clickEntity(this.jt3d);
+
+			},
+
+			/**
+			 * 单击事件
+			 */
+			clickEntity() {
+
+				let _self = this;
+
+				if (jt3d.handlerLeftClick) {
+					jt3d.handlerLeftClick.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK); //移除事件
+				}
+
+				jt3d.handlerLeftClick = new Cesium.ScreenSpaceEventHandler(
+					jt3d._viewer.scene.canvas
+				);
+				//注册大球单机事件
+				jt3d.handlerLeftClick.setInputAction(function(e) {
+					var pick = jt3d._viewer.scene.pick(e.position); //拾取当前的entity对象
+					var cartesian = jt3d._viewer.scene.pickPosition(e.position); //获取当前点坐标
+					if (Cesium.defined(cartesian)) {
+						//将笛卡尔坐标转换为地理坐标
+						let cartographic = Cesium.Cartographic.fromCartesian(cartesian);
+						//将弧度转为度的十进制度表示
+						let lng = Cesium.Math.toDegrees(cartographic.longitude);
+						let lat = Cesium.Math.toDegrees(cartographic.latitude);
+						let alt = cartographic.height; //高度
+
+						_self.$parent.$refs._refQueryGraphics.initQuery({
+							spatialType: '点',
+							coordinate: lng + " " + lat,
+							buffer: ''
+						});
+					}
+				}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
+			},
+
+			/**
+			 * 创建大球
+			 */
+			initMap3d(el) {
+
+				let aa = new jt3dSDK.jtMap3d({
+					container: el,
+				});
+
+				// this.jt3d = new jt3dSDK.jtMap3d({
+				// 	container: el,
+				// });
+
+				debugger
+
+				this.jt3d = aa
+
+				// jt3d.statusBar.show = true;
+				window.viewer = this.jt3d._viewer;
+
+				this.statusBar = new jt3dSDK.StatusBar(window.viewer);
+				this.statusBar.show = true;
+			},
+
+			/**
+			 * 设置默认视图-初始化中国区域范围视角
+			 */
+			setView(jt3d) {
+				// jt3d.flytoChina();
+
+				//初始化中国区域范围视角
+				jt3d.setViewChina();
+
+				// jt3d.setView({
+				// 	longitude: 103.84, //经度
+				// 	latitude: 31.15, // 维度
+				// 	height: 24000000, // 高度
+				// 	heading: 0, // 偏航
+				// 	pitch: -90, // 俯仰,人如果在赤道上空,俯仰角为0可见地球。如果在北纬,俯仰角为负才能见地球
+				// 	roll: 0.0 // 翻滚
+				// });
+			},
+
+			/**
+			 * 全图-飞行到项目区域范围视角
+			 */
+			fullMap(jt3d) {
+				// 初始化项目区域范围视角
+				let optionsS = {
+					west: 121.563298,
+					south: 37.284514,
+					east: 121.565298,
+					north: 37.286514,
+					isRemove: false, //定位完成后是否删除
+					duration: 3, //飞行时间
+					heading: 0,
+					pitch: -90,
+					range: 115000
+				};
+
+				let optionsE = {
+					west: 121.563298,
+					south: 37.284514,
+					east: 121.565298,
+					north: 37.286514,
+					isRemove: true, //定位完成后是否删除
+					duration: 3, //飞行时间
+					heading: 0,
+					pitch: -60,
+					range: 115000
+				};
+
+				var fullMapPromise = jt3d.fullMap(optionsS);
+				fullMapPromise.then(function(flyPromise) {
+					jt3d.fullMap(optionsE);
+				});
+			},
+
+			/**
+			 * 设置天空盒子
+			 */
+			addSkybox(jt3d) {
+				//设置天空盒子,默认蓝天
+				jt3d.SceneEffects.SkyBox.setGroundSkyBox();
+			},
+		},
+
+		mounted() {
+			this.init("cesiumContainer");
+			// cons
+		}
+	};
+</script>
+
+<style scoped>
+	#cesiumContainer {
+		height: 100vh;
+		margin: 0;
+		padding: 0;
+		border: 1px solid red;
+	}
+</style>

+ 179 - 0
src/views/map3d/plot/graphicsDraw/areaDraw/algorithm.ts

@@ -0,0 +1,179 @@
+import type { Cartesian3 } from "cesium";
+import type { AllPlotI } from "../../interface";
+import type { PointArr } from "../../interface";
+import { P, lonLatToCartesian } from "../../tools";
+
+const curseParams = {
+  t: 0.3,
+};
+const gatheringPlaceParams = {
+  t: 0.4,
+};
+
+export const areaPlot: AllPlotI = {
+  version: "1.0.0",
+  createTime: "2023-2-26",
+  updateTime: "2023-2-26",
+  author: "c-lei-en",
+  algorithm: {
+    // * 获取弓形坐标
+    getArcPositions: (pnts: PointArr[]): Cartesian3[] => {
+      let pnt;
+      if (pnts.length == 2 || pnts[1].toString() == pnts[2].toString()) {
+        const mid = P.PlotUtils.mid(pnts[0], pnts[1]);
+        const d = P.PlotUtils.distance(pnts[0], mid);
+        pnt = P.PlotUtils.getThirdPoint(pnts[0], mid, P.Constants.HALF_PI, d);
+      }
+      const pnt1 = pnts[0];
+      const pnt2 = pnts[1];
+      const pnt3 = pnt ? pnt : pnts[2];
+      const center = P.PlotUtils.getCircleCenterOfThreePoints(pnt1, pnt2, pnt3);
+      const radius = P.PlotUtils.distance(pnt1, center);
+      const angle1 = P.PlotUtils.getAzimuth(pnt1, center);
+      const angle2 = P.PlotUtils.getAzimuth(pnt2, center);
+      let startAngle, endAngle;
+      if (P.PlotUtils.isClockWise(pnt1, pnt2, pnt3)) {
+        startAngle = angle2;
+        endAngle = angle1;
+      } else {
+        startAngle = angle1;
+        endAngle = angle2;
+      }
+      const pntArr = P.PlotUtils.getArcPoints(
+        center,
+        radius,
+        startAngle,
+        endAngle
+      );
+      return pntArr;
+    },
+    // * 获取扇形坐标
+    getSectorPositions: (pnts: PointArr[]): Cartesian3[] => {
+      const center = pnts[0];
+      const radius = P.PlotUtils.distance(pnts[1], center);
+      const startAngle = P.PlotUtils.getAzimuth(pnts[1], center);
+      const endAngle = P.PlotUtils.getAzimuth(pnts[2], center);
+      const pList = P.PlotUtils.getArcPoints(
+        center,
+        radius,
+        startAngle,
+        endAngle
+      );
+      pList.push(lonLatToCartesian(center), pList[0]);
+      return pList;
+    },
+    // * 获取矩形坐标
+    getRectanglePositions: (pnt1: PointArr, pnt2: PointArr): PointArr => {
+      const xmin = Math.min(pnt1[0], pnt2[0]);
+      const xmax = Math.max(pnt1[0], pnt2[0]);
+      const ymin = Math.min(pnt1[1], pnt2[1]);
+      const ymax = Math.max(pnt1[1], pnt2[1]);
+      return [xmin, xmax, ymin, ymax];
+    },
+    // * 获取曲线面坐标
+    getClosedCurvePositions: (pnts: PointArr[]): Cartesian3[] => {
+      if (pnts.length == 2 || pnts[1].toString() == pnts[2].toString()) {
+        const mid = P.PlotUtils.mid(pnts[0], pnts[1]);
+        const d = P.PlotUtils.distance(pnts[0], mid);
+        const pnt = P.PlotUtils.getThirdPoint(
+          pnts[0],
+          mid,
+          P.Constants.HALF_PI,
+          d
+        );
+        pnts.push(pnt);
+      }
+      pnts.push(pnts[0], pnts[1]);
+      let normals: any = [];
+      for (let i = 0; i < pnts.length - 2; i++) {
+        const normalPoints = P.PlotUtils.getBisectorNormals(
+          curseParams.t,
+          pnts[i],
+          pnts[i + 1],
+          pnts[i + 2]
+        );
+        normals = normals.concat(normalPoints);
+      }
+      const count = normals.length;
+      normals = [normals[count - 1]].concat(normals.slice(0, count - 1));
+
+      const pList = [];
+      for (let i = 0; i < pnts.length - 2; i++) {
+        const pnt1 = pnts[i];
+        const pnt2 = pnts[i + 1];
+        pList.push(pnt1);
+        for (let t = 0; t <= P.Constants.FITTING_COUNT; t++) {
+          const pnt = P.PlotUtils.getCubicValue(
+            t / P.Constants.FITTING_COUNT,
+            pnt1,
+            normals[i * 2],
+            normals[i * 2 + 1],
+            pnt2
+          );
+          pList.push(pnt);
+        }
+        pList.push(pnt2);
+      }
+      const cartesianList = [];
+      for (let i = 0, len = pList.length; i < len - 1; i++) {
+        cartesianList.push(lonLatToCartesian(pList[i]));
+      }
+      return cartesianList;
+    },
+    // * 获取聚集地坐标
+    getGatheringPlacePositions: (pnts: PointArr[]) => {
+      if (pnts.length == 2) {
+        const mid = P.PlotUtils.mid(pnts[0], pnts[1]);
+        const d = P.PlotUtils.distance(pnts[0], mid) / 0.9;
+        const pnt = P.PlotUtils.getThirdPoint(
+          pnts[0],
+          mid,
+          P.Constants.HALF_PI,
+          d,
+          true
+        );
+        pnts = [pnts[0], pnt, pnts[1]];
+      }
+      const mid = P.PlotUtils.mid(pnts[0], pnts[2]);
+      pnts.push(mid, pnts[0], pnts[1]);
+
+      let normals: any = [];
+      for (let i = 0; i < pnts.length - 2; i++) {
+        const pnt1 = pnts[i];
+        const pnt2 = pnts[i + 1];
+        const pnt3 = pnts[i + 2];
+        const normalPoints = P.PlotUtils.getBisectorNormals(
+          gatheringPlaceParams.t,
+          pnt1,
+          pnt2,
+          pnt3
+        );
+        normals = normals.concat(normalPoints);
+      }
+      const count = normals.length;
+      normals = [normals[count - 1]].concat(normals.slice(0, count - 1));
+      const pList = [];
+      for (let i = 0; i < pnts.length - 2; i++) {
+        const pnt1 = pnts[i];
+        const pnt2 = pnts[i + 1];
+        pList.push(pnt1);
+        for (let t = 0; t <= P.Constants.FITTING_COUNT; t++) {
+          const pnt = P.PlotUtils.getCubicValue(
+            t / P.Constants.FITTING_COUNT,
+            pnt1,
+            normals[i * 2],
+            normals[i * 2 + 1],
+            pnt2
+          );
+          pList.push(pnt);
+        }
+        pList.push(pnt2);
+      }
+      const cartesianList = [];
+      for (let i = 0, len = pList.length; i < len - 1; i++) {
+        cartesianList.push(lonLatToCartesian(pList[i]));
+      }
+      return cartesianList;
+    },
+  },
+};

+ 1824 - 0
src/views/map3d/plot/graphicsDraw/areaDraw/index.ts

@@ -0,0 +1,1824 @@
+import {
+  Appearance,
+  Billboard,
+  CallbackProperty,
+  Cartesian3,
+  Color,
+  defined,
+  EllipseGeometry,
+  Entity,
+  GeometryInstance,
+  GroundPrimitive,
+  HeightReference,
+  Material,
+  Primitive,
+  ScreenSpaceEventHandler,
+  ScreenSpaceEventType,
+  VerticalOrigin,
+  Math as cesiumMath,
+  PolygonGeometry,
+  PolygonHierarchy,
+  Rectangle as cesiumRectangle,
+  RectangleGeometry,
+  BillboardCollection,
+} from "cesium";
+import { getCatesian3FromPX, cartesianToLonlat } from "../../tools";
+import type { BaseAreaI, PlotFuncI, PointArr } from "./../../interface";
+import { areaPlot } from "./algorithm";
+import emitter from "@/mitt";
+
+class BaseArea implements BaseAreaI {
+  type: string;
+  baseType: string;
+  objId: number;
+  handler: any;
+  state: number;
+  step: number;
+  floatPoint: any;
+  floatPointArr: any;
+  areaPrimitive: any;
+  areaEntity: any;
+  modifyHandler: any;
+  pointList: any[];
+  material: any;
+  selectPoint: any;
+  clickStep: number;
+  constructor(obj: BaseAreaI) {
+    this.type = obj.type;
+    this.baseType = "area";
+    this.objId = obj.objId;
+    this.handler = obj.handler;
+    this.areaPrimitive = obj.areaPrimitive;
+    this.areaEntity = obj.areaEntity;
+    this.state = obj.state; //state用于区分当前的状态 0 为删除 1为添加 2为编辑
+    this.step = obj.step;
+    this.floatPoint = obj.floatPoint;
+    this.floatPointArr = obj.floatPointArr;
+    this.modifyHandler = obj.modifyHandler;
+    this.pointList = obj.pointList;
+    this.material = obj.material;
+    this.selectPoint = obj.selectPoint;
+    this.clickStep = obj.clickStep;
+  }
+  disable() {
+    if (this.areaEntity) {
+      window.Viewer.entities.remove(this.areaEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.floatPointArr.forEach((item: Billboard) => {
+        window.Viewer.billboards.remove(item);
+      });
+      this.areaEntity = null;
+    }
+    this.state = -1;
+    this.stopDraw();
+  }
+  stopDraw() {
+    if (this.handler) {
+      this.handler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+      this.handler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
+      this.handler.removeInputAction(ScreenSpaceEventType.RIGHT_CLICK);
+      this.handler.destroy();
+      this.handler = null;
+    }
+    if (this.modifyHandler) {
+      this.modifyHandler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+      this.modifyHandler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
+      this.modifyHandler.removeInputAction(ScreenSpaceEventType.RIGHT_CLICK);
+      this.modifyHandler.destroy();
+      this.modifyHandler = null;
+    }
+  }
+  creatPoint(cartesian: number[]): Primitive {
+    if (!window.Viewer.billboards)
+      window.Viewer.billboards = window.Viewer.scene.primitives.add(
+        new BillboardCollection({
+          scene: window.Viewer.scene,
+        })
+      );
+    return window.Viewer.billboards.add({
+      id: "moveBillboard",
+      position: cartesian,
+      image: "/src/assets/icon/point.png",
+      verticalOrigin: VerticalOrigin.BOTTOM,
+      heightReference: HeightReference.CLAMP_TO_GROUND,
+    });
+  }
+}
+
+// * circle 圆
+class Circle extends BaseArea implements PlotFuncI {
+  constructor() {
+    super({
+      type: "Circle",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      areaPrimitive: null,
+      areaEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+      if (this.pointList.length == 3) {
+        this.state = -1;
+        this.pointList.pop();
+        this.areaPrimitive = this.showPrimitiveOnMap();
+        window.Viewer.entities.remove(this.areaEntity);
+        window.Viewer.billboards.remove(this.floatPoint);
+        this.areaEntity = null;
+        this.floatPoint = null;
+        this.stopDraw();
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.areaEntity) {
+        this.areaEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.areaEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.areaPrimitive);
+    this.areaPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.areaPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.areaEntity);
+          this.areaEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.areaPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const distance = Cartesian3.distance(this.pointList[0], this.pointList[1]);
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new EllipseGeometry({
+        center: this.pointList[0],
+        semiMajorAxis: distance,
+        semiMinorAxis: distance,
+      }),
+    });
+    const primitive = window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+    emitter.emit("drawEnd");
+    return primitive;
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算曲线,若有两个点则应该直接返回两个点的连线,若有三个点则返回处理后的坐标集合
+      if (this.pointList.length == 2) {
+        return this.pointList[0];
+      }
+    };
+    // * 通过坐标计算半径
+    const computeDistance = () => {
+      return Cartesian3.distance(this.pointList[0], this.pointList[1]);
+    };
+    return window.Viewer.entities.add({
+      position: new CallbackProperty(update, false),
+      ellipse: {
+        material: Color.BLUE,
+        clampToGround: true,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+        semiMajorAxis: new CallbackProperty(computeDistance, false),
+        semiMinorAxis: new CallbackProperty(computeDistance, false),
+      },
+    });
+  }
+}
+
+// * ellipse 椭圆
+class Ellipse extends BaseArea implements PlotFuncI {
+  constructor() {
+    super({
+      type: "Ellipse",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      areaPrimitive: null,
+      areaEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+      if (this.pointList.length == 3) {
+        this.state = -1;
+        this.pointList.pop();
+        this.areaPrimitive = this.showPrimitiveOnMap();
+        window.Viewer.entities.remove(this.areaEntity);
+        window.Viewer.billboards.remove(this.floatPoint);
+        this.areaEntity = null;
+        this.floatPoint = null;
+        this.stopDraw();
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.areaEntity) {
+        this.areaEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.areaEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.areaPrimitive);
+    this.areaPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.areaPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.areaEntity);
+          this.areaEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.areaPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const maxDistance = Cartesian3.distance(
+      this.pointList[0],
+      this.pointList[1]
+    );
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new EllipseGeometry({
+        center: this.pointList[0],
+        semiMajorAxis: maxDistance,
+        semiMinorAxis: maxDistance / 2,
+        rotation: this.computeRoate(),
+      }),
+    });
+    const primitive = window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+    emitter.emit("drawEnd");
+    return primitive;
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算曲线,若有两个点则应该直接返回两个点的连线,若有三个点则返回处理后的坐标集合
+      if (this.pointList.length == 2) {
+        return this.pointList[0];
+      }
+    };
+    // * 通过坐标计算长半轴
+    const computeMaxDistance = () => {
+      const maxDistance = Cartesian3.distance(
+        this.pointList[0],
+        this.pointList[1]
+      );
+      return maxDistance;
+    };
+    // * 通过坐标计算短半轴
+    const computeMinDistance = () => {
+      const minDistance =
+        Cartesian3.distance(this.pointList[0], this.pointList[1]) / 2;
+      return minDistance;
+    };
+    // * 计算椭圆朝向
+    const computeRoate = () => {
+      return this.computeRoate.apply(this);
+    };
+    return window.Viewer.entities.add({
+      position: new CallbackProperty(update, false),
+      ellipse: {
+        material: Color.BLUE,
+        clampToGround: true,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+        semiMajorAxis: new CallbackProperty(computeMaxDistance, false),
+        semiMinorAxis: new CallbackProperty(computeMinDistance, false),
+        rotation: new CallbackProperty(computeRoate, false),
+      },
+    });
+  }
+  computeRoate() {
+    if (
+      (this.pointList[0].x >= this.pointList[1].x &&
+        this.pointList[0].y >= this.pointList[1].y) ||
+      (this.pointList[0].x < this.pointList[1].x &&
+        this.pointList[0].y < this.pointList[1].y)
+    ) {
+      return Math.atan2(
+        Math.abs(this.pointList[0].y - this.pointList[1].y),
+        Math.abs(this.pointList[0].x - this.pointList[1].x)
+      );
+    } else {
+      return (
+        cesiumMath.toRadians(cesiumMath.TWO_PI) -
+        Math.atan2(
+          Math.abs(this.pointList[0].y - this.pointList[1].y),
+          Math.abs(this.pointList[0].x - this.pointList[1].x)
+        )
+      );
+    }
+  }
+}
+
+// * lune 弓形
+class Lune extends BaseArea implements PlotFuncI {
+  constructor() {
+    super({
+      type: "Lune",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      areaPrimitive: null,
+      areaEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+      if (this.pointList.length == 4) {
+        this.state = -1;
+        this.pointList.pop();
+        this.areaPrimitive = this.showPrimitiveOnMap();
+        window.Viewer.entities.remove(this.areaEntity);
+        window.Viewer.billboards.remove(this.floatPoint);
+        this.areaEntity = null;
+        this.floatPoint = null;
+        this.stopDraw();
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.areaEntity) {
+        this.areaEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.areaEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.areaPrimitive);
+    this.areaPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.areaPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.areaEntity);
+          this.areaEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.areaPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(
+          areaPlot.algorithm.getArcPositions(lnglatArr)
+        ),
+      }),
+    });
+    const primitive = window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+    emitter.emit("drawEnd");
+    return primitive;
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      if (lnglatArr.length == 2) {
+        lnglatArr.push([lnglatArr[1][0] + 0.0000001, lnglatArr[1][1]]);
+      }
+      const res = areaPlot.algorithm.getArcPositions(lnglatArr);
+      return new PolygonHierarchy(res);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * Sector 扇形
+class Sector extends BaseArea implements PlotFuncI {
+  constructor() {
+    super({
+      type: "Sector",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      areaPrimitive: null,
+      areaEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+      if (this.pointList.length == 4) {
+        this.state = -1;
+        this.pointList.pop();
+        this.areaPrimitive = this.showPrimitiveOnMap();
+        window.Viewer.entities.remove(this.areaEntity);
+        window.Viewer.billboards.remove(this.floatPoint);
+        this.areaEntity = null;
+        this.floatPoint = null;
+        this.stopDraw();
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.areaEntity) {
+        this.areaEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.areaEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.areaPrimitive);
+    this.areaPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.areaPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.areaEntity);
+          this.areaEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.areaPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(
+          areaPlot.algorithm.getSectorPositions(lnglatArr)
+        ),
+      }),
+    });
+    const primitives = window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+    emitter.emit("drawEnd");
+    return primitives;
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      if (lnglatArr.length == 2) {
+        lnglatArr.push([lnglatArr[1][0] + 0.0000001, lnglatArr[1][1]]);
+      }
+      const res = areaPlot.algorithm.getSectorPositions(lnglatArr);
+      return new PolygonHierarchy(res);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * Rectangle 矩形
+class Rectangle extends BaseArea implements PlotFuncI {
+  constructor() {
+    super({
+      type: "Rectangle",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      areaPrimitive: null,
+      areaEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+      if (this.pointList.length == 3) {
+        this.state = -1;
+        this.pointList.pop();
+        this.areaPrimitive = this.showPrimitiveOnMap();
+        window.Viewer.entities.remove(this.areaEntity);
+        window.Viewer.billboards.remove(this.floatPoint);
+        this.areaEntity = null;
+        this.floatPoint = null;
+        this.stopDraw();
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.areaEntity) {
+        this.areaEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.areaEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.areaPrimitive);
+    this.areaPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.areaPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.areaEntity);
+          this.areaEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.areaPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const res = areaPlot.algorithm.getRectanglePositions(
+      lnglatArr[0],
+      lnglatArr[1]
+    );
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new RectangleGeometry({
+        rectangle: cesiumRectangle.fromDegrees(res[0], res[2], res[1], res[3]),
+      }),
+    });
+    const primitive = window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+    emitter.emit("drawEnd");
+    return primitive;
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      const res = areaPlot.algorithm.getRectanglePositions(
+        lnglatArr[0],
+        lnglatArr[1]
+      );
+      return cesiumRectangle.fromDegrees(res[0], res[2], res[1], res[3]);
+    };
+    return window.Viewer.entities.add({
+      rectangle: {
+        coordinates: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * closedCurve 曲线面
+class ClosedCurve extends BaseArea implements PlotFuncI {
+  constructor() {
+    super({
+      type: "ClosedCurve",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      areaPrimitive: null,
+      areaEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.areaEntity) {
+        this.areaEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 鼠标右击完成绘制
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 3) return;
+      this.state = -1;
+      this.pointList.pop();
+      this.areaPrimitive = this.showPrimitiveOnMap();
+      window.Viewer.entities.remove(this.areaEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.areaEntity = null;
+      this.floatPoint = null;
+      this.stopDraw();
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.areaEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.areaPrimitive);
+    this.areaPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.areaPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.areaEntity);
+          this.areaEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.areaPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const res = areaPlot.algorithm.getClosedCurvePositions(lnglatArr);
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(res),
+      }),
+    });
+    const primitive = window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+    emitter.emit("drawEnd");
+    return primitive;
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      const res = areaPlot.algorithm.getClosedCurvePositions(lnglatArr);
+      return new PolygonHierarchy(res);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * polygon 多边形
+class Polygon extends BaseArea implements PlotFuncI {
+  constructor() {
+    super({
+      type: "Polygon",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      areaPrimitive: null,
+      areaEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.areaEntity) {
+        this.areaEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 鼠标右击完成绘制
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 3) return;
+      this.state = -1;
+      this.pointList.pop();
+      this.areaPrimitive = this.showPrimitiveOnMap();
+      window.Viewer.entities.remove(this.areaEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.areaEntity = null;
+      this.floatPoint = null;
+      this.stopDraw();
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.areaEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.areaPrimitive);
+    this.areaPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.areaPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.areaEntity);
+          this.areaEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.areaPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(this.pointList),
+      }),
+    });
+    const primitive = window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+    emitter.emit("drawEnd");
+    return primitive;
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      return new PolygonHierarchy(this.pointList);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * freeHandPolygon 自由面
+class FreeHandPolygon extends BaseArea implements PlotFuncI {
+  constructor() {
+    super({
+      type: "FreeHandPolygon",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      areaPrimitive: null,
+      areaEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.areaEntity) {
+        this.areaEntity = this.createEntity();
+      } else if (this.pointList.length >= 2) {
+        // * 随着鼠标移动添加数据
+        this.pointList.push(cartesian.clone());
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 鼠标右击完成绘制
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 3) return;
+      this.state = -1;
+      this.pointList.pop();
+      this.areaPrimitive = this.showPrimitiveOnMap();
+      window.Viewer.entities.remove(this.areaEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.areaEntity = null;
+      this.floatPoint = null;
+      this.stopDraw();
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.areaEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.areaPrimitive);
+    this.areaPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.areaPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.areaEntity);
+          this.areaEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.areaPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(this.pointList),
+      }),
+    });
+    const primitive = window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+    emitter.emit("drawEnd");
+    return primitive;
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      return new PolygonHierarchy(this.pointList);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * gatheringPlace 聚集地
+class GatheringPlace extends BaseArea implements PlotFuncI {
+  constructor() {
+    super({
+      type: "GatheringPlace",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      areaPrimitive: null,
+      areaEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+      if (this.pointList.length == 4) {
+        this.state = -1;
+        this.pointList.pop();
+        this.areaPrimitive = this.showPrimitiveOnMap();
+        window.Viewer.entities.remove(this.areaEntity);
+        window.Viewer.billboards.remove(this.floatPoint);
+        this.areaEntity = null;
+        this.floatPoint = null;
+        this.stopDraw();
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.areaEntity) {
+        this.areaEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.areaEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.areaPrimitive);
+    this.areaPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.areaPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.areaEntity);
+          this.areaEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.areaPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    // * 计算坐标
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const res = areaPlot.algorithm.getGatheringPlacePositions(lnglatArr);
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(res),
+      }),
+    });
+    const primitive = window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+    emitter.emit("drawEnd");
+    return primitive;
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      if (lnglatArr.length == 2) {
+        lnglatArr.push([lnglatArr[1][0] + 0.0000001, lnglatArr[1][1]]);
+      }
+      const res = areaPlot.algorithm.getGatheringPlacePositions(lnglatArr);
+      return new PolygonHierarchy(res);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+export {
+  Circle,
+  Ellipse,
+  Lune,
+  Sector,
+  Rectangle,
+  ClosedCurve,
+  Polygon,
+  FreeHandPolygon,
+  GatheringPlace,
+};

+ 756 - 0
src/views/map3d/plot/graphicsDraw/arrowDraw/algorithm.ts

@@ -0,0 +1,756 @@
+import type { Cartesian3 } from "cesium";
+import type { AllPlotI } from "../../interface";
+import type { PointArr } from "../../interface";
+import { P, lonLatToCartesian } from "../../tools";
+
+const doubleArrowParams = {
+  headHeightFactor: 0.25,
+  headWidthFactor: 0.3,
+  neckHeightFactor: 0.85,
+  neckWidthFactor: 0.15,
+};
+
+// * 细直箭头与突击方向type
+type FineOrAssault = {
+  tailWidthFactor: number;
+  neckWidthFactor: number;
+  headWidthFactor: number;
+  headAngle: number;
+  neckAngle: number;
+};
+
+const fineArrowParams: FineOrAssault = {
+  tailWidthFactor: 0.15,
+  neckWidthFactor: 0.2,
+  headWidthFactor: 0.25,
+  headAngle: Math.PI / 8.5,
+  neckAngle: Math.PI / 13,
+};
+
+const assaultDirectionParams: FineOrAssault = {
+  tailWidthFactor: 0.2,
+  neckWidthFactor: 0.25,
+  headWidthFactor: 0.3,
+  headAngle: Math.PI / 4,
+  neckAngle: Math.PI * 0.17741,
+};
+
+const attackArrowParams = {
+  headHeightFactor: 0.18,
+  headWidthFactor: 0.3,
+  neckHeightFactor: 0.85,
+  neckWidthFactor: 0.15,
+  headTailFactor: 0.8,
+  tailWidthFactor: 0.1,
+  swallowTailFactor: 1,
+};
+
+const squadCombatParams = {
+  headHeightFactor: 0.18,
+  headWidthFactor: 0.3,
+  neckHeightFactor: 0.85,
+  neckWidthFactor: 0.15,
+  tailWidthFactor: 0.1,
+  swallowTailFactor: 1,
+};
+
+export const arrowPlot: AllPlotI = {
+  version: "1.0.0",
+  createTime: "2023-3-3",
+  updateTime: "2023-3-3",
+  author: "c-lei-en",
+  algorithm: {
+    // * 计算对称点
+    getTempPoint4: (
+      pnt1: PointArr,
+      pnt2: PointArr,
+      point: PointArr
+    ): PointArr => {
+      const midPnt = P.PlotUtils.mid(pnt1, pnt2);
+      const len = P.PlotUtils.distance(midPnt, point);
+      const angle = P.PlotUtils.getAngleOfThreePoints(pnt1, midPnt, point);
+      let symPnt, distance1, distance2, mid;
+      if (angle < P.Constants.HALF_PI) {
+        distance1 = len * Math.sin(angle);
+        distance2 = len * Math.cos(angle);
+        mid = P.PlotUtils.getThirdPoint(
+          pnt1,
+          midPnt,
+          P.Constants.HALF_PI,
+          distance1,
+          false
+        );
+        symPnt = P.PlotUtils.getThirdPoint(
+          midPnt,
+          mid,
+          P.Constants.HALF_PI,
+          distance2,
+          true
+        );
+      } else if (angle >= P.Constants.HALF_PI && angle < Math.PI) {
+        distance1 = len * Math.sin(Math.PI - angle);
+        distance2 = len * Math.cos(Math.PI - angle);
+        mid = P.PlotUtils.getThirdPoint(
+          pnt1,
+          midPnt,
+          P.Constants.HALF_PI,
+          distance1,
+          false
+        );
+        symPnt = P.PlotUtils.getThirdPoint(
+          midPnt,
+          mid,
+          P.Constants.HALF_PI,
+          distance2,
+          false
+        );
+      } else if (angle >= Math.PI && angle < Math.PI * 1.5) {
+        distance1 = len * Math.sin(angle - Math.PI);
+        distance2 = len * Math.cos(angle - Math.PI);
+        mid = P.PlotUtils.getThirdPoint(
+          pnt1,
+          midPnt,
+          P.Constants.HALF_PI,
+          distance1,
+          true
+        );
+        symPnt = P.PlotUtils.getThirdPoint(
+          midPnt,
+          mid,
+          P.Constants.HALF_PI,
+          distance2,
+          true
+        );
+      } else {
+        distance1 = len * Math.sin(Math.PI * 2 - angle);
+        distance2 = len * Math.cos(Math.PI * 2 - angle);
+        mid = P.PlotUtils.getThirdPoint(
+          pnt1,
+          midPnt,
+          P.Constants.HALF_PI,
+          distance1,
+          true
+        );
+        symPnt = P.PlotUtils.getThirdPoint(
+          midPnt,
+          mid,
+          P.Constants.HALF_PI,
+          distance2,
+          false
+        );
+      }
+      return symPnt;
+    },
+    // * 获取箭头坐标
+    getArrowHeadPoints: (points: PointArr): PointArr => {
+      const len = P.PlotUtils.getBaseLength(points);
+      const headHeight = len * doubleArrowParams.headHeightFactor;
+      const headPnt = points[points.length - 1];
+      const headWidth = headHeight * doubleArrowParams.headWidthFactor;
+      const neckWidth = headHeight * doubleArrowParams.neckWidthFactor;
+      const neckHeight = headHeight * doubleArrowParams.neckHeightFactor;
+      const headEndPnt = P.PlotUtils.getThirdPoint(
+        points[points.length - 2],
+        headPnt,
+        0,
+        headHeight,
+        true
+      );
+      const neckEndPnt = P.PlotUtils.getThirdPoint(
+        points[points.length - 2],
+        headPnt,
+        0,
+        neckHeight,
+        true
+      );
+      const headLeft = P.PlotUtils.getThirdPoint(
+        headPnt,
+        headEndPnt,
+        P.Constants.HALF_PI,
+        headWidth,
+        false
+      );
+      const headRight = P.PlotUtils.getThirdPoint(
+        headPnt,
+        headEndPnt,
+        P.Constants.HALF_PI,
+        headWidth,
+        true
+      );
+      const neckLeft = P.PlotUtils.getThirdPoint(
+        headPnt,
+        neckEndPnt,
+        P.Constants.HALF_PI,
+        neckWidth,
+        false
+      );
+      const neckRight = P.PlotUtils.getThirdPoint(
+        headPnt,
+        neckEndPnt,
+        P.Constants.HALF_PI,
+        neckWidth,
+        true
+      );
+      return [neckLeft, headLeft, headPnt, headRight, neckRight];
+    },
+    // * 获取钳击箭身坐标
+    getArrowBodyPoints: (
+      points: PointArr,
+      neckLeft: PointArr,
+      neckRight: PointArr,
+      tailWidthFactor: number
+    ): PointArr => {
+      const allLen = P.PlotUtils.wholeDistance(points);
+      const len = P.PlotUtils.getBaseLength(points);
+      const tailWidth = len * tailWidthFactor;
+      const neckWidth = P.PlotUtils.distance(neckLeft, neckRight);
+      const widthDif = (tailWidth - neckWidth) / 2;
+      let tempLen = 0;
+      const leftBodyPnts = [],
+        rightBodyPnts = [];
+      for (let i = 1; i < points.length - 1; i++) {
+        const angle =
+          P.PlotUtils.getAngleOfThreePoints(
+            points[i - 1],
+            points[i],
+            points[i + 1]
+          ) / 2;
+        tempLen += P.PlotUtils.distance(points[i - 1], points[i]);
+        const w =
+          (tailWidth / 2 - (tempLen / allLen) * widthDif) / Math.sin(angle);
+        const left = P.PlotUtils.getThirdPoint(
+          points[i - 1],
+          points[i],
+          Math.PI - angle,
+          w,
+          true
+        );
+        const right = P.PlotUtils.getThirdPoint(
+          points[i - 1],
+          points[i],
+          angle,
+          w,
+          false
+        );
+        leftBodyPnts.push(left);
+        rightBodyPnts.push(right);
+      }
+      return leftBodyPnts.concat(rightBodyPnts);
+    },
+    // * 获取箭头点
+    getArrowPoints: (
+      pnt1: PointArr,
+      pnt2: PointArr,
+      pnt3: PointArr,
+      clockWise: number
+    ): PointArr => {
+      const midPnt = P.PlotUtils.mid(pnt1, pnt2);
+      const len = P.PlotUtils.distance(midPnt, pnt3);
+      let midPnt1 = P.PlotUtils.getThirdPoint(pnt3, midPnt, 0, len * 0.3, true);
+      let midPnt2 = P.PlotUtils.getThirdPoint(pnt3, midPnt, 0, len * 0.5, true);
+      midPnt1 = P.PlotUtils.getThirdPoint(
+        midPnt,
+        midPnt1,
+        P.Constants.HALF_PI,
+        len / 5,
+        clockWise
+      );
+      midPnt2 = P.PlotUtils.getThirdPoint(
+        midPnt,
+        midPnt2,
+        P.Constants.HALF_PI,
+        len / 4,
+        clockWise
+      );
+
+      const points = [midPnt, midPnt1, midPnt2, pnt3];
+      // 计算箭头部分
+      const arrowPnts = arrowPlot.algorithm.getArrowHeadPoints(
+        points,
+        doubleArrowParams.headHeightFactor,
+        doubleArrowParams.headWidthFactor,
+        doubleArrowParams.neckHeightFactor,
+        doubleArrowParams.neckWidthFactor
+      );
+      const neckLeftPoint = arrowPnts[0];
+      const neckRightPoint = arrowPnts[4];
+      // 计算箭身部分
+      const tailWidthFactor =
+        P.PlotUtils.distance(pnt1, pnt2) /
+        P.PlotUtils.getBaseLength(points) /
+        2;
+      const bodyPnts = arrowPlot.algorithm.getArrowBodyPoints(
+        points,
+        neckLeftPoint,
+        neckRightPoint,
+        tailWidthFactor
+      );
+      const n = bodyPnts.length;
+      let lPoints = bodyPnts.slice(0, n / 2);
+      let rPoints = bodyPnts.slice(n / 2, n);
+      lPoints.push(neckLeftPoint);
+      rPoints.push(neckRightPoint);
+      lPoints = lPoints.reverse();
+      lPoints.push(pnt2);
+      rPoints = rPoints.reverse();
+      rPoints.push(pnt1);
+      return lPoints.reverse().concat(arrowPnts, rPoints);
+    },
+    // * 获取钳击箭头坐标
+    getDoubleArrow: (pnts: PointArr[]): Cartesian3[] => {
+      const pnt1 = pnts[0];
+      const pnt2 = pnts[1];
+      const pnt3 = pnts[2];
+      let tempPoint4, connPoint;
+      if (pnts.length == 3)
+        tempPoint4 = arrowPlot.algorithm.getTempPoint4(pnt1, pnt2, pnt3);
+      else tempPoint4 = pnts[3];
+      if (pnts.length == 3 || pnts.length == 4)
+        connPoint = P.PlotUtils.mid(pnt1, pnt2);
+      else connPoint = pnts[4];
+      let leftArrowPnts, rightArrowPnts;
+      if (P.PlotUtils.isClockWise(pnt1, pnt2, pnt3)) {
+        leftArrowPnts = arrowPlot.algorithm.getArrowPoints(
+          pnt1,
+          connPoint,
+          tempPoint4,
+          false
+        );
+        rightArrowPnts = arrowPlot.algorithm.getArrowPoints(
+          connPoint,
+          pnt2,
+          pnt3,
+          true
+        );
+      } else {
+        leftArrowPnts = arrowPlot.algorithm.getArrowPoints(
+          pnt2,
+          connPoint,
+          pnt3,
+          false
+        );
+        rightArrowPnts = arrowPlot.algorithm.getArrowPoints(
+          connPoint,
+          pnt1,
+          tempPoint4,
+          true
+        );
+      }
+      const m = leftArrowPnts.length;
+      const t = (m - 5) / 2;
+
+      const llBodyPnts = leftArrowPnts.slice(0, t);
+      const lArrowPnts = leftArrowPnts.slice(t, t + 5);
+      let lrBodyPnts = leftArrowPnts.slice(t + 5, m);
+
+      let rlBodyPnts = rightArrowPnts.slice(0, t);
+      const rArrowPnts = rightArrowPnts.slice(t, t + 5);
+      const rrBodyPnts = rightArrowPnts.slice(t + 5, m);
+
+      rlBodyPnts = P.PlotUtils.getBezierPoints(rlBodyPnts);
+      const bodyPnts = P.PlotUtils.getBezierPoints(
+        rrBodyPnts.concat(llBodyPnts.slice(1))
+      );
+      lrBodyPnts = P.PlotUtils.getBezierPoints(lrBodyPnts);
+
+      const positions = rlBodyPnts.concat(
+        rArrowPnts,
+        bodyPnts,
+        lArrowPnts,
+        lrBodyPnts
+      );
+      const res = [] as Cartesian3[];
+      positions.forEach((pos: PointArr) => {
+        res.push(lonLatToCartesian(pos));
+      });
+      return res;
+    },
+    // * 获取细直箭头或者突击方向箭头坐标
+    getFineOrAssault: (pnts: PointArr, param: FineOrAssault): Cartesian3[] => {
+      const pnt1 = pnts[0];
+      const pnt2 = pnts[1];
+      const len = P.PlotUtils.getBaseLength(pnts);
+      const tailWidth = len * param.tailWidthFactor;
+      const neckWidth = len * param.neckWidthFactor;
+      const headWidth = len * param.headWidthFactor;
+      const tailLeft = P.PlotUtils.getThirdPoint(
+        pnt2,
+        pnt1,
+        P.Constants.HALF_PI,
+        tailWidth,
+        true
+      );
+      const tailRight = P.PlotUtils.getThirdPoint(
+        pnt2,
+        pnt1,
+        P.Constants.HALF_PI,
+        tailWidth,
+        false
+      );
+      const headLeft = P.PlotUtils.getThirdPoint(
+        pnt1,
+        pnt2,
+        param.headAngle,
+        headWidth,
+        false
+      );
+      const headRight = P.PlotUtils.getThirdPoint(
+        pnt1,
+        pnt2,
+        param.headAngle,
+        headWidth,
+        true
+      );
+      const neckLeft = P.PlotUtils.getThirdPoint(
+        pnt1,
+        pnt2,
+        param.neckAngle,
+        neckWidth,
+        false
+      );
+      const neckRight = P.PlotUtils.getThirdPoint(
+        pnt1,
+        pnt2,
+        param.neckAngle,
+        neckWidth,
+        true
+      );
+      return [
+        lonLatToCartesian(tailLeft),
+        lonLatToCartesian(neckLeft),
+        lonLatToCartesian(headLeft),
+        lonLatToCartesian(pnt2),
+        lonLatToCartesian(headRight),
+        lonLatToCartesian(neckRight),
+        lonLatToCartesian(tailRight),
+      ];
+    },
+    // * 获取细直箭头坐标
+    getFineArrow: (pnts: PointArr): Cartesian3[] => {
+      return arrowPlot.algorithm.getFineOrAssault(pnts, fineArrowParams);
+    },
+    // * 获取突击方向坐标
+    getAssaultDirection: (pnts: PointArr): Cartesian3[] => {
+      return arrowPlot.algorithm.getFineOrAssault(pnts, assaultDirectionParams);
+    },
+    // * 获取进攻方向箭头坐标
+    getAttackArrowHeadPoints: (
+      points: PointArr,
+      tailLeft: PointArr,
+      tailRight: PointArr
+    ): PointArr => {
+      let len = P.PlotUtils.getBaseLength(points);
+      let headHeight = len * attackArrowParams.headHeightFactor;
+      const headPnt = points[points.length - 1];
+      len = P.PlotUtils.distance(headPnt, points[points.length - 2]);
+      const tailWidth = P.PlotUtils.distance(tailLeft, tailRight);
+      if (headHeight > tailWidth * attackArrowParams.headTailFactor) {
+        headHeight = tailWidth * attackArrowParams.headTailFactor;
+      }
+      const headWidth = headHeight * attackArrowParams.headWidthFactor;
+      const neckWidth = headHeight * attackArrowParams.neckWidthFactor;
+      headHeight = headHeight > len ? len : headHeight;
+      const neckHeight = headHeight * attackArrowParams.neckHeightFactor;
+      const headEndPnt = P.PlotUtils.getThirdPoint(
+        points[points.length - 2],
+        headPnt,
+        0,
+        headHeight,
+        true
+      );
+      const neckEndPnt = P.PlotUtils.getThirdPoint(
+        points[points.length - 2],
+        headPnt,
+        0,
+        neckHeight,
+        true
+      );
+      const headLeft = P.PlotUtils.getThirdPoint(
+        headPnt,
+        headEndPnt,
+        P.Constants.HALF_PI,
+        headWidth,
+        false
+      );
+      const headRight = P.PlotUtils.getThirdPoint(
+        headPnt,
+        headEndPnt,
+        P.Constants.HALF_PI,
+        headWidth,
+        true
+      );
+      const neckLeft = P.PlotUtils.getThirdPoint(
+        headPnt,
+        neckEndPnt,
+        P.Constants.HALF_PI,
+        neckWidth,
+        false
+      );
+      const neckRight = P.PlotUtils.getThirdPoint(
+        headPnt,
+        neckEndPnt,
+        P.Constants.HALF_PI,
+        neckWidth,
+        true
+      );
+      return [neckLeft, headLeft, headPnt, headRight, neckRight];
+    },
+    // * 获取进攻方向箭身坐标
+    getAttackArrowBodyPoints: (
+      points: PointArr,
+      neckLeft: PointArr,
+      neckRight: PointArr,
+      tailWidthFactor: number
+    ): PointArr => {
+      const allLen = P.PlotUtils.wholeDistance(points);
+      const len = P.PlotUtils.getBaseLength(points);
+      const tailWidth = len * tailWidthFactor;
+      const neckWidth = P.PlotUtils.distance(neckLeft, neckRight);
+      const widthDif = (tailWidth - neckWidth) / 2;
+      let tempLen = 0;
+      const leftBodyPnts = [],
+        rightBodyPnts = [];
+      for (let i = 1; i < points.length - 1; i++) {
+        const angle =
+          P.PlotUtils.getAngleOfThreePoints(
+            points[i - 1],
+            points[i],
+            points[i + 1]
+          ) / 2;
+        tempLen += P.PlotUtils.distance(points[i - 1], points[i]);
+        const w =
+          (tailWidth / 2 - (tempLen / allLen) * widthDif) / Math.sin(angle);
+        const left = P.PlotUtils.getThirdPoint(
+          points[i - 1],
+          points[i],
+          Math.PI - angle,
+          w,
+          true
+        );
+        const right = P.PlotUtils.getThirdPoint(
+          points[i - 1],
+          points[i],
+          angle,
+          w,
+          false
+        );
+        leftBodyPnts.push(left);
+        rightBodyPnts.push(right);
+      }
+      return leftBodyPnts.concat(rightBodyPnts);
+    },
+    // * 获取进攻方向坐标
+    getAttackArrow: (pnts: PointArr[]): Cartesian3[] => {
+      // 计算箭尾
+      let tailLeft = pnts[0];
+      let tailRight = pnts[1];
+      if (P.PlotUtils.isClockWise(pnts[0], pnts[1], pnts[2])) {
+        tailLeft = pnts[1];
+        tailRight = pnts[0];
+      }
+      const midTail = P.PlotUtils.mid(tailLeft, tailRight);
+      const bonePnts = [midTail].concat(pnts.slice(2));
+      // 计算箭头
+      const headPnts = arrowPlot.algorithm.getAttackArrowHeadPoints(
+        bonePnts,
+        tailLeft,
+        tailRight
+      );
+      const neckLeft = headPnts[0];
+      const neckRight = headPnts[4];
+      const tailWidthFactor =
+        P.PlotUtils.distance(tailLeft, tailRight) /
+        P.PlotUtils.getBaseLength(bonePnts);
+      // 计算箭身
+      const bodyPnts = arrowPlot.algorithm.getAttackArrowBodyPoints(
+        bonePnts,
+        neckLeft,
+        neckRight,
+        tailWidthFactor
+      );
+      // 整合
+      const count = bodyPnts.length;
+      let leftPnts = [tailLeft].concat(bodyPnts.slice(0, count / 2));
+      leftPnts.push(neckLeft);
+      let rightPnts = [tailRight].concat(bodyPnts.slice(count / 2, count));
+      rightPnts.push(neckRight);
+
+      leftPnts = P.PlotUtils.getQBSplinePoints(leftPnts);
+      rightPnts = P.PlotUtils.getQBSplinePoints(rightPnts);
+      const positions = leftPnts.concat(headPnts, rightPnts.reverse());
+      const res = [] as Cartesian3[];
+      positions.forEach((pos: PointArr) => {
+        res.push(lonLatToCartesian(pos));
+      });
+      return res;
+    },
+    // * 获取进攻方向尾坐标
+    getTailedAttackArrow: (pnts: PointArr[]): Cartesian3[] => {
+      let tailLeft = pnts[0];
+      let tailRight = pnts[1];
+      if (P.PlotUtils.isClockWise(pnts[0], pnts[1], pnts[2])) {
+        tailLeft = pnts[1];
+        tailRight = pnts[0];
+      }
+      const midTail = P.PlotUtils.mid(tailLeft, tailRight);
+      const bonePnts = [midTail].concat(pnts.slice(2));
+      const headPnts = arrowPlot.algorithm.getAttackArrowHeadPoints(
+        bonePnts,
+        tailLeft,
+        tailRight
+      );
+      const neckLeft = headPnts[0];
+      const neckRight = headPnts[4];
+      const tailWidth = P.PlotUtils.distance(tailLeft, tailRight);
+      const allLen = P.PlotUtils.getBaseLength(bonePnts);
+      const len =
+        allLen *
+        attackArrowParams.tailWidthFactor *
+        attackArrowParams.swallowTailFactor;
+      const swallowTailPnt = P.PlotUtils.getThirdPoint(
+        bonePnts[1],
+        bonePnts[0],
+        0,
+        len,
+        true
+      );
+      const factor = tailWidth / allLen;
+      const bodyPnts = arrowPlot.algorithm.getAttackArrowBodyPoints(
+        bonePnts,
+        neckLeft,
+        neckRight,
+        factor
+      );
+      const count = bodyPnts.length;
+      let leftPnts = [tailLeft].concat(bodyPnts.slice(0, count / 2));
+      leftPnts.push(neckLeft);
+      let rightPnts = [tailRight].concat(bodyPnts.slice(count / 2, count));
+      rightPnts.push(neckRight);
+
+      leftPnts = P.PlotUtils.getQBSplinePoints(leftPnts);
+      rightPnts = P.PlotUtils.getQBSplinePoints(rightPnts);
+      const positions = leftPnts.concat(headPnts, rightPnts.reverse(), [
+        swallowTailPnt,
+        leftPnts[0],
+      ]);
+      const res = [] as Cartesian3[];
+      positions.forEach((pos: PointArr) => {
+        res.push(lonLatToCartesian(pos));
+      });
+      return res;
+    },
+    // * 获取尾点
+    getTailPoints: (points: PointArr): PointArr[] => {
+      const allLen = P.PlotUtils.getBaseLength(points);
+      const tailWidth = allLen * squadCombatParams.tailWidthFactor;
+      const tailLeft = P.PlotUtils.getThirdPoint(
+        points[1],
+        points[0],
+        P.Constants.HALF_PI,
+        tailWidth,
+        false
+      );
+      const tailRight = P.PlotUtils.getThirdPoint(
+        points[1],
+        points[0],
+        P.Constants.HALF_PI,
+        tailWidth,
+        true
+      );
+      return [tailLeft, tailRight];
+    },
+    // * 获取分队战斗行动坐标
+    getSquadCombat: (pnts: PointArr[]): Cartesian3[] => {
+      const tailPnts = arrowPlot.algorithm.getTailPoints(pnts);
+      const headPnts = arrowPlot.algorithm.getAttackArrowHeadPoints(
+        pnts,
+        tailPnts[0],
+        tailPnts[1]
+      );
+      const neckLeft = headPnts[0];
+      const neckRight = headPnts[4];
+      const bodyPnts = arrowPlot.algorithm.getAttackArrowBodyPoints(
+        pnts,
+        neckLeft,
+        neckRight,
+        squadCombatParams.tailWidthFactor
+      );
+      const count = bodyPnts.length;
+      let leftPnts = [tailPnts[0]].concat(bodyPnts.slice(0, count / 2));
+      leftPnts.push(neckLeft);
+      let rightPnts = [tailPnts[1]].concat(bodyPnts.slice(count / 2, count));
+      rightPnts.push(neckRight);
+
+      leftPnts = P.PlotUtils.getQBSplinePoints(leftPnts);
+      rightPnts = P.PlotUtils.getQBSplinePoints(rightPnts);
+      const positions = leftPnts.concat(headPnts, rightPnts.reverse());
+      const res = [] as Cartesian3[];
+      positions.forEach((pos: PointArr) => {
+        res.push(lonLatToCartesian(pos));
+      });
+      return res;
+    },
+    // * 获取分队战斗行动尾尾点
+    getTailedTailPoints: (points: PointArr): PointArr[] => {
+      const allLen = P.PlotUtils.getBaseLength(points);
+      const tailWidth = allLen * squadCombatParams.tailWidthFactor;
+      const tailLeft = P.PlotUtils.getThirdPoint(
+        points[1],
+        points[0],
+        P.Constants.HALF_PI,
+        tailWidth,
+        false
+      );
+      const tailRight = P.PlotUtils.getThirdPoint(
+        points[1],
+        points[0],
+        P.Constants.HALF_PI,
+        tailWidth,
+        true
+      );
+      const len = tailWidth * squadCombatParams.swallowTailFactor;
+      const swallowTailPnt = P.PlotUtils.getThirdPoint(
+        points[1],
+        points[0],
+        0,
+        len,
+        true
+      );
+      return [tailLeft, swallowTailPnt, tailRight];
+    },
+    // * 获取分队战斗行动尾坐标
+    getTailedSquadCombat: (pnts: PointArr[]): Cartesian3[] => {
+      const tailPnts = arrowPlot.algorithm.getTailedTailPoints(pnts);
+      const headPnts = arrowPlot.algorithm.getAttackArrowHeadPoints(
+        pnts,
+        tailPnts[0],
+        tailPnts[2]
+      );
+      const neckLeft = headPnts[0];
+      const neckRight = headPnts[4];
+      const bodyPnts = arrowPlot.algorithm.getAttackArrowBodyPoints(
+        pnts,
+        neckLeft,
+        neckRight,
+        squadCombatParams.tailWidthFactor
+      );
+      const count = bodyPnts.length;
+      let leftPnts = [tailPnts[0]].concat(bodyPnts.slice(0, count / 2));
+      leftPnts.push(neckLeft);
+      let rightPnts = [tailPnts[2]].concat(bodyPnts.slice(count / 2, count));
+      rightPnts.push(neckRight);
+
+      leftPnts = P.PlotUtils.getQBSplinePoints(leftPnts);
+      rightPnts = P.PlotUtils.getQBSplinePoints(rightPnts);
+      const positions = leftPnts.concat(headPnts, rightPnts.reverse(), [
+        tailPnts[1],
+        leftPnts[0],
+      ]);
+      const res = [] as Cartesian3[];
+      positions.forEach((pos: PointArr) => {
+        res.push(lonLatToCartesian(pos));
+      });
+      return res;
+    },
+  },
+};

+ 1661 - 0
src/views/map3d/plot/graphicsDraw/arrowDraw/index.ts

@@ -0,0 +1,1661 @@
+import {
+  Appearance,
+  Billboard,
+  CallbackProperty,
+  Color,
+  defined,
+  Entity,
+  GeometryInstance,
+  GroundPrimitive,
+  HeightReference,
+  Material,
+  Primitive,
+  ScreenSpaceEventHandler,
+  ScreenSpaceEventType,
+  VerticalOrigin,
+  PolygonGeometry,
+  PolygonHierarchy,
+  GroundPolylineGeometry,
+  GroundPolylinePrimitive,
+  PolylineMaterialAppearance,
+  PolylineArrowMaterialProperty,
+} from "cesium";
+import { getCatesian3FromPX, cartesianToLonlat } from "../../tools";
+import type { BaseArrowI, PlotFuncI, PointArr } from "./../../interface";
+import { arrowPlot } from "./algorithm";
+import emitter from "@/mitt";
+
+class BaseArrow implements BaseArrowI {
+  type: string;
+  baseType: string;
+  objId: number;
+  handler: any;
+  state: number;
+  step: number;
+  floatPoint: any;
+  floatPointArr: any;
+  arrowPrimitive: any;
+  arrowEntity: any;
+  modifyHandler: any;
+  pointList: any[];
+  material: any;
+  selectPoint: any;
+  clickStep: number;
+  constructor(obj: BaseArrowI) {
+    this.type = obj.type;
+    this.baseType = "arrow";
+    this.objId = obj.objId;
+    this.handler = obj.handler;
+    this.arrowPrimitive = obj.arrowPrimitive;
+    this.arrowEntity = obj.arrowEntity;
+    this.state = obj.state; //state用于区分当前的状态 0 为删除 1为添加 2为编辑
+    this.step = obj.step;
+    this.floatPoint = obj.floatPoint;
+    this.floatPointArr = obj.floatPointArr;
+    this.modifyHandler = obj.modifyHandler;
+    this.pointList = obj.pointList;
+    this.material = obj.material;
+    this.selectPoint = obj.selectPoint;
+    this.clickStep = obj.clickStep;
+  }
+  disable() {
+    if (this.arrowEntity) {
+      window.Viewer.entities.remove(this.arrowEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.floatPointArr.forEach((item: Billboard) => {
+        window.Viewer.billboards.remove(item);
+      });
+      this.arrowEntity = null;
+    }
+    this.state = -1;
+    this.stopDraw();
+  }
+  stopDraw() {
+    if (this.handler) {
+      this.handler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+      this.handler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
+      this.handler.removeInputAction(ScreenSpaceEventType.RIGHT_CLICK);
+      this.handler.destroy();
+      this.handler = null;
+    }
+    if (this.modifyHandler) {
+      this.modifyHandler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+      this.modifyHandler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
+      this.modifyHandler.removeInputAction(ScreenSpaceEventType.RIGHT_CLICK);
+      this.modifyHandler.destroy();
+      this.modifyHandler = null;
+    }
+  }
+  creatPoint(cartesian: number[]): Primitive {
+    return window.Viewer.billboards.add({
+      id: "moveBillboard",
+      position: cartesian,
+      image: "/src/assets/icon/point.png",
+      verticalOrigin: VerticalOrigin.BOTTOM,
+      heightReference: HeightReference.CLAMP_TO_GROUND,
+    });
+  }
+}
+
+// * doubleArrow 钳击
+class DoubleArrow extends BaseArrow implements PlotFuncI {
+  constructor() {
+    super({
+      type: "DoubleArrow",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      arrowPrimitive: null,
+      arrowEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+      if (this.pointList.length == 5) {
+        this.state = -1;
+        this.pointList.pop();
+        this.arrowPrimitive = this.showPrimitiveOnMap();
+        window.Viewer.entities.remove(this.arrowEntity);
+        window.Viewer.billboards.remove(this.floatPoint);
+        this.arrowEntity = null;
+        this.floatPoint = null;
+        this.stopDraw();
+        emitter.emit("drawEnd");
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.arrowEntity) {
+        this.arrowEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.arrowEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.arrowPrimitive);
+    this.arrowPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.arrowPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.arrowEntity);
+          this.arrowEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.arrowPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(
+          arrowPlot.algorithm.getDoubleArrow(lnglatArr)
+        ),
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      if (
+        lnglatArr.length == 2 ||
+        lnglatArr[1].toString() == lnglatArr[2].toString()
+      ) {
+        lnglatArr.push([lnglatArr[1][0] + 0.0000001, lnglatArr[1][1]]);
+      }
+      const res = arrowPlot.algorithm.getDoubleArrow(lnglatArr);
+      return new PolygonHierarchy(res);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * straightArrow 直箭头
+class StraightArrow extends BaseArrow implements PlotFuncI {
+  constructor() {
+    super({
+      type: "StraightArrow",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      arrowPrimitive: null,
+      arrowEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("PolylineArrow", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.arrowEntity) {
+        this.arrowEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 鼠标右击完成绘制
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 2) return;
+      this.state = -1;
+      this.pointList.pop();
+      this.arrowPrimitive = this.showPrimitiveOnMap();
+      window.Viewer.entities.remove(this.arrowEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.arrowEntity = null;
+      this.floatPoint = null;
+      this.stopDraw();
+      emitter.emit("drawEnd");
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.arrowEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.arrowPrimitive);
+    this.arrowPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.arrowPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.arrowEntity);
+          this.arrowEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.arrowPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  showPrimitiveOnMap(): Primitive {
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new GroundPolylineGeometry({
+        positions: this.pointList,
+        width: 10,
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPolylinePrimitive({
+        geometryInstances: instance,
+        appearance: new PolylineMaterialAppearance({
+          material: this.material,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算曲线,若有两个点则应该直接返回两个点的连线,若有三个点则返回处理后的坐标集合
+      return this.pointList;
+    };
+    return window.Viewer.entities.add({
+      polyline: {
+        positions: new CallbackProperty(update, false),
+        show: true,
+        width: 10,
+        material: new PolylineArrowMaterialProperty(Color.BLUE.clone()),
+        clampToGround: true,
+      },
+    });
+  }
+}
+
+// * fineArrow 细直箭头
+class FineArrow extends BaseArrow implements PlotFuncI {
+  constructor() {
+    super({
+      type: "FineArrow",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      arrowPrimitive: null,
+      arrowEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+      if (this.pointList.length == 3) {
+        this.state = -1;
+        this.pointList.pop();
+        this.arrowPrimitive = this.showPrimitiveOnMap();
+        window.Viewer.entities.remove(this.arrowEntity);
+        window.Viewer.billboards.remove(this.floatPoint);
+        this.arrowEntity = null;
+        this.floatPoint = null;
+        this.stopDraw();
+        emitter.emit("drawEnd");
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.arrowEntity) {
+        this.arrowEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.arrowEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.arrowPrimitive);
+    this.arrowPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.arrowPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.arrowEntity);
+          this.arrowEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.arrowPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(
+          arrowPlot.algorithm.getFineArrow(lnglatArr)
+        ),
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      if (lnglatArr.length == 2) {
+        lnglatArr.push([lnglatArr[1][0] + 0.0000001, lnglatArr[1][1]]);
+      }
+      const res = arrowPlot.algorithm.getFineArrow(lnglatArr);
+      return new PolygonHierarchy(res);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * assaultDirection 突击方向
+class AssaultDirection extends BaseArrow implements PlotFuncI {
+  constructor() {
+    super({
+      type: "AssaultDirection",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      arrowPrimitive: null,
+      arrowEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+      if (this.pointList.length == 3) {
+        this.state = -1;
+        this.pointList.pop();
+        this.arrowPrimitive = this.showPrimitiveOnMap();
+        window.Viewer.entities.remove(this.arrowEntity);
+        window.Viewer.billboards.remove(this.floatPoint);
+        this.arrowEntity = null;
+        this.floatPoint = null;
+        this.stopDraw();
+        emitter.emit("drawEnd");
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.arrowEntity) {
+        this.arrowEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.arrowEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.arrowPrimitive);
+    this.arrowPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.arrowPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.arrowEntity);
+          this.arrowEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.arrowPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(
+          arrowPlot.algorithm.getAssaultDirection(lnglatArr)
+        ),
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      if (lnglatArr.length == 2) {
+        lnglatArr.push([lnglatArr[1][0] + 0.0000001, lnglatArr[1][1]]);
+      }
+      const res = arrowPlot.algorithm.getAssaultDirection(lnglatArr);
+      return new PolygonHierarchy(res);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * attackArrow 进攻方向
+class AttackArrow extends BaseArrow implements PlotFuncI {
+  constructor() {
+    super({
+      type: "AttackArrow",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      arrowPrimitive: null,
+      arrowEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.arrowEntity) {
+        this.arrowEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 鼠标右击完成绘制
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 2) return;
+      this.state = -1;
+      this.pointList.pop();
+      this.arrowPrimitive = this.showPrimitiveOnMap();
+      window.Viewer.entities.remove(this.arrowEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.arrowEntity = null;
+      this.floatPoint = null;
+      this.stopDraw();
+      emitter.emit("drawEnd");
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.arrowEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.arrowPrimitive);
+    this.arrowPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.arrowPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.arrowEntity);
+          this.arrowEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.arrowPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(
+          arrowPlot.algorithm.getAttackArrow(lnglatArr)
+        ),
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      if (lnglatArr.length == 2) {
+        lnglatArr.push([lnglatArr[1][0] + 0.0000001, lnglatArr[1][1]]);
+      } else if (
+        lnglatArr[lnglatArr.length - 1].toString() ==
+          lnglatArr[lnglatArr.length - 2].toString() &&
+        lnglatArr.length > 3
+      ) {
+        lnglatArr.pop();
+      } else {
+        lnglatArr[lnglatArr.length - 1][0] += 0.0000001;
+      }
+      const res = arrowPlot.algorithm.getAttackArrow(lnglatArr);
+      return new PolygonHierarchy(res);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * tailedAttackArrow 进攻方向(尾)
+class TailedAttackArrow extends BaseArrow implements PlotFuncI {
+  constructor() {
+    super({
+      type: "TailedAttackArrow",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      arrowPrimitive: null,
+      arrowEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.arrowEntity) {
+        this.arrowEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 鼠标右击完成绘制
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 2) return;
+      this.state = -1;
+      this.pointList.pop();
+      this.arrowPrimitive = this.showPrimitiveOnMap();
+      window.Viewer.entities.remove(this.arrowEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.arrowEntity = null;
+      this.floatPoint = null;
+      this.stopDraw();
+      emitter.emit("drawEnd");
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.arrowEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.arrowPrimitive);
+    this.arrowPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.arrowPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.arrowEntity);
+          this.arrowEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.arrowPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(
+          arrowPlot.algorithm.getTailedAttackArrow(lnglatArr)
+        ),
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      if (lnglatArr.length == 2) {
+        lnglatArr.push([lnglatArr[1][0] + 0.0000001, lnglatArr[1][1]]);
+      } else if (
+        lnglatArr[lnglatArr.length - 1].toString() ==
+          lnglatArr[lnglatArr.length - 2].toString() &&
+        lnglatArr.length > 3
+      ) {
+        lnglatArr.pop();
+      } else {
+        lnglatArr[lnglatArr.length - 1][0] += 0.0000001;
+      }
+      const res = arrowPlot.algorithm.getTailedAttackArrow(lnglatArr);
+      return new PolygonHierarchy(res);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * squadCombat 分队战斗行动
+class SquadCombat extends BaseArrow implements PlotFuncI {
+  constructor() {
+    super({
+      type: "SquadCombat",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      arrowPrimitive: null,
+      arrowEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.arrowEntity) {
+        this.arrowEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 鼠标右击完成绘制
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 2) return;
+      this.state = -1;
+      this.pointList.pop();
+      this.arrowPrimitive = this.showPrimitiveOnMap();
+      window.Viewer.entities.remove(this.arrowEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.arrowEntity = null;
+      this.floatPoint = null;
+      this.stopDraw();
+      emitter.emit("drawEnd");
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.arrowEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.arrowPrimitive);
+    this.arrowPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.arrowPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.arrowEntity);
+          this.arrowEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.arrowPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(
+          arrowPlot.algorithm.getSquadCombat(lnglatArr)
+        ),
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      if (lnglatArr.length == 2) {
+        lnglatArr.push([lnglatArr[1][0] + 0.0000001, lnglatArr[1][1]]);
+      } else if (
+        lnglatArr[lnglatArr.length - 1].toString() ==
+          lnglatArr[lnglatArr.length - 2].toString() &&
+        lnglatArr.length > 3
+      ) {
+        lnglatArr.pop();
+      } else {
+        lnglatArr[lnglatArr.length - 1][0] += 0.0000001;
+      }
+      const res = arrowPlot.algorithm.getSquadCombat(lnglatArr);
+      return new PolygonHierarchy(res);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+// * tailedSquadCombat 分队战斗行动(尾)
+class TailedSquadCombat extends BaseArrow implements PlotFuncI {
+  constructor() {
+    super({
+      type: "TailedSquadCombat",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      arrowPrimitive: null,
+      arrowEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      material: Material.fromType("Color", {
+        color: Color.PINK.clone(),
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.arrowEntity) {
+        this.arrowEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 鼠标右击完成绘制
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 2) return;
+      this.state = -1;
+      this.pointList.pop();
+      this.arrowPrimitive = this.showPrimitiveOnMap();
+      window.Viewer.entities.remove(this.arrowEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.arrowEntity = null;
+      this.floatPoint = null;
+      this.stopDraw();
+      emitter.emit("drawEnd");
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.arrowEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.arrowPrimitive);
+    this.arrowPrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.arrowPrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.arrowEntity);
+          this.arrowEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.arrowPrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  // * 创建primitive进行展示
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new PolygonGeometry({
+        polygonHierarchy: new PolygonHierarchy(
+          arrowPlot.algorithm.getTailedSquadCombat(lnglatArr)
+        ),
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPrimitive({
+        geometryInstances: instance,
+        appearance: new Appearance({
+          material: this.material,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算坐标
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      if (lnglatArr.length == 2) {
+        lnglatArr.push([lnglatArr[1][0] + 0.0000001, lnglatArr[1][1]]);
+      } else if (
+        lnglatArr[lnglatArr.length - 1].toString() ==
+          lnglatArr[lnglatArr.length - 2].toString() &&
+        lnglatArr.length > 3
+      ) {
+        lnglatArr.pop();
+      } else {
+        lnglatArr[lnglatArr.length - 1][0] += 0.0000001;
+      }
+      const res = arrowPlot.algorithm.getTailedSquadCombat(lnglatArr);
+      return new PolygonHierarchy(res);
+    };
+    return window.Viewer.entities.add({
+      polygon: {
+        hierarchy: new CallbackProperty(update, false),
+        material: Color.BLUE,
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+      },
+    });
+  }
+}
+
+export {
+  DoubleArrow,
+  StraightArrow,
+  FineArrow,
+  AssaultDirection,
+  AttackArrow,
+  TailedAttackArrow,
+  SquadCombat,
+  TailedSquadCombat,
+};

+ 40 - 0
src/views/map3d/plot/graphicsDraw/lineDraw/algorithm.ts

@@ -0,0 +1,40 @@
+import type { AllPlotI } from "../../interface";
+import type { PointArr } from "../../interface";
+import { P } from "../../tools";
+
+const curseParams = {
+  t: 0.3,
+};
+
+export const linePlot: AllPlotI = {
+  version: "1.0.0",
+  createTime: "2023-2-13",
+  updateTime: "2023-2-13",
+  author: "c-lei-en",
+  algorithm: {
+    // * 获取弧线坐标
+    getArcPositions: (
+      pnt1: PointArr,
+      pnt2: PointArr,
+      pnt3: PointArr
+    ): PointArr[] => {
+      const center = P.PlotUtils.getCircleCenterOfThreePoints(pnt1, pnt2, pnt3);
+      const radius = P.PlotUtils.distance(pnt1, center);
+      const angle1 = P.PlotUtils.getAzimuth(pnt1, center);
+      const angle2 = P.PlotUtils.getAzimuth(pnt2, center);
+      let startAngle, endAngle;
+      if (P.PlotUtils.isClockWise(pnt1, pnt2, pnt3)) {
+        startAngle = angle2;
+        endAngle = angle1;
+      } else {
+        startAngle = angle1;
+        endAngle = angle2;
+      }
+      return P.PlotUtils.getArcPoints(center, radius, startAngle, endAngle);
+    },
+    // * 获取曲线坐标
+    getCurvePoints: (controlPoints: Array<PointArr>): PointArr[] => {
+      return P.PlotUtils.getCurvePoints(curseParams.t, controlPoints);
+    },
+  },
+};

+ 844 - 0
src/views/map3d/plot/graphicsDraw/lineDraw/index.ts

@@ -0,0 +1,844 @@
+import {
+  Billboard,
+  CallbackProperty,
+  Color,
+  defined,
+  Entity,
+  GeometryInstance,
+  GroundPolylineGeometry,
+  GroundPolylinePrimitive,
+  HeightReference,
+  Material,
+  PolylineGraphics,
+  PolylineMaterialAppearance,
+  Primitive,
+  ScreenSpaceEventHandler,
+  ScreenSpaceEventType,
+  VerticalOrigin,
+} from "cesium";
+import { getCatesian3FromPX, cartesianToLonlat } from "../../tools";
+import type { BaseLineI, PlotFuncI, PointArr } from "./../../interface/index";
+import { linePlot } from "./algorithm";
+import emitter from "@/mitt";
+
+class BaseLine implements BaseLineI {
+  type: string;
+  baseType: string;
+  objId: number;
+  handler: any;
+  state: number;
+  step: number;
+  floatPoint: any;
+  floatPointArr: any;
+  linePrimitive: any;
+  lineEntity: any;
+  modifyHandler: any;
+  pointList: any[];
+  outlineMaterial: any;
+  selectPoint: any;
+  clickStep: number;
+  constructor(obj: BaseLineI) {
+    this.type = obj.type;
+    this.baseType = "line";
+    this.objId = obj.objId;
+    this.handler = obj.handler;
+    this.linePrimitive = obj.linePrimitive;
+    this.lineEntity = obj.lineEntity;
+    this.state = obj.state; //state用于区分当前的状态 0 为删除 1为添加 2为编辑
+    this.step = obj.step;
+    this.floatPoint = obj.floatPoint;
+    this.floatPointArr = obj.floatPointArr;
+    this.modifyHandler = obj.modifyHandler;
+    this.pointList = obj.pointList;
+    this.outlineMaterial = obj.outlineMaterial;
+    this.selectPoint = obj.selectPoint;
+    this.clickStep = obj.clickStep;
+  }
+  disable() {
+    if (this.lineEntity) {
+      window.Viewer.entities.remove(this.lineEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.floatPointArr.forEach((item: Billboard) => {
+        window.Viewer.billboards.remove(item);
+      });
+      this.lineEntity = null;
+    }
+    this.state = -1;
+    this.stopDraw();
+  }
+  stopDraw() {
+    if (this.handler) {
+      this.handler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+      this.handler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
+      this.handler.removeInputAction(ScreenSpaceEventType.RIGHT_CLICK);
+      this.handler.destroy();
+      this.handler = null;
+    }
+    if (this.modifyHandler) {
+      this.modifyHandler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+      this.modifyHandler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
+      this.modifyHandler.removeInputAction(ScreenSpaceEventType.RIGHT_CLICK);
+      this.modifyHandler.destroy();
+      this.modifyHandler = null;
+    }
+  }
+  creatPoint(cartesian: number[]): Primitive {
+    return window.Viewer.billboards.add({
+      id: "moveBillboard",
+      position: cartesian,
+      image: "/src/assets/icon/point.png",
+      verticalOrigin: VerticalOrigin.BOTTOM,
+      heightReference: HeightReference.CLAMP_TO_GROUND,
+    });
+  }
+}
+
+// * arc 弧线
+class Arc extends BaseLine implements PlotFuncI {
+  constructor() {
+    super({
+      type: "Arc",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      linePrimitive: null,
+      lineEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      outlineMaterial: Material.fromType("PolylineOutline", {
+        outlineWidth: 5,
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制,当点击第三次的时候完成绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+      if (this.pointList.length == 4) {
+        this.state = -1;
+        this.pointList.pop();
+        this.linePrimitive = this.showPrimitiveOnMap();
+        window.Viewer.entities.remove(this.lineEntity);
+        window.Viewer.billboards.remove(this.floatPoint);
+        this.lineEntity = null;
+        this.floatPoint = null;
+        this.stopDraw();
+        emitter.emit("drawEnd");
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.lineEntity) {
+        this.lineEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.lineEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.linePrimitive);
+    this.linePrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.linePrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.lineEntity);
+          this.lineEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.linePrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+
+  showPrimitiveOnMap(): Primitive {
+    const lnglatArr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const lnglat = cartesianToLonlat(this.pointList[i]);
+      lnglatArr.push(lnglat);
+    }
+    const res = linePlot.algorithm.getArcPositions(...lnglatArr);
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new GroundPolylineGeometry({
+        positions: res,
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPolylinePrimitive({
+        geometryInstances: instance,
+        appearance: new PolylineMaterialAppearance({
+          material: this.outlineMaterial,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算曲线,若有两个点则应该直接返回两个点的连线,若有三个点则返回处理后的坐标集合
+      if (this.pointList.length == 2) {
+        return this.pointList;
+      } else if (this.pointList.length == 3) {
+        if (
+          JSON.stringify(this.pointList[2]) == JSON.stringify(this.pointList[1])
+        ) {
+          return this.pointList;
+        }
+        const lnglatArr = [];
+        for (let i = 0; i < this.pointList.length; i++) {
+          const lnglat = cartesianToLonlat(this.pointList[i]);
+          lnglatArr.push(lnglat);
+        }
+        const res = linePlot.algorithm.getArcPositions(...lnglatArr);
+        const index = JSON.stringify(res).indexOf("null");
+        let returnData = [];
+        if (index == -1) returnData = res;
+        return returnData;
+      } else {
+        return [];
+      }
+    };
+    return window.Viewer.entities.add({
+      polyline: new PolylineGraphics({
+        positions: new CallbackProperty(update, false),
+        show: true,
+        material: Color.BLUE,
+        clampToGround: true,
+      }),
+    });
+  }
+}
+
+// * 曲线
+class Curve extends BaseLine implements PlotFuncI {
+  constructor() {
+    super({
+      type: "Curve",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      linePrimitive: null,
+      lineEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      outlineMaterial: Material.fromType("PolylineOutline", {
+        outlineWidth: 5,
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.lineEntity) {
+        this.lineEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 鼠标右击完成绘制
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 2) return;
+      this.state = -1;
+      this.linePrimitive = this.showPrimitiveOnMap();
+      window.Viewer.entities.remove(this.lineEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.lineEntity = null;
+      this.floatPoint = null;
+      this.stopDraw();
+      emitter.emit("drawEnd");
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.lineEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.linePrimitive);
+    this.linePrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.linePrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.lineEntity);
+          this.lineEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.linePrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  showPrimitiveOnMap(): Primitive {
+    let res;
+    if (this.pointList.length == 2) {
+      res = this.pointList;
+    } else {
+      const lnglatArr = [];
+      for (let i = 0; i < this.pointList.length; i++) {
+        const lnglat = cartesianToLonlat(this.pointList[i]);
+        lnglatArr.push(lnglat);
+      }
+      res = linePlot.algorithm.getCurvePoints(lnglatArr);
+    }
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new GroundPolylineGeometry({
+        positions: res,
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPolylinePrimitive({
+        geometryInstances: instance,
+        appearance: new PolylineMaterialAppearance({
+          material: this.outlineMaterial,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算曲线,若有两个点则应该直接返回两个点的连线,若有三个点则返回处理后的坐标集合
+      if (this.pointList.length == 2) {
+        return this.pointList;
+      } else {
+        const lnglatArr = [];
+        for (let i = 0; i < this.pointList.length; i++) {
+          const lnglat = cartesianToLonlat(this.pointList[i]);
+          lnglatArr.push(lnglat);
+        }
+        const res = linePlot.algorithm.getCurvePoints(lnglatArr);
+        const index = JSON.stringify(res).indexOf("null");
+        let returnData = [];
+        if (index == -1) returnData = res;
+        return returnData;
+      }
+    };
+    return window.Viewer.entities.add({
+      polyline: new PolylineGraphics({
+        positions: new CallbackProperty(update, false),
+        show: true,
+        material: Color.BLUE,
+        clampToGround: true,
+      }),
+    });
+  }
+}
+
+// * 折线
+class Polyline extends BaseLine implements PlotFuncI {
+  constructor() {
+    super({
+      type: "Polyline",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      linePrimitive: null,
+      lineEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      outlineMaterial: Material.fromType("PolylineOutline", {
+        outlineWidth: 5,
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.lineEntity) {
+        this.lineEntity = this.createEntity();
+      } else {
+        // * 当鼠标移动时,修改最后一个值
+        if (this.pointList.length >= 2)
+          this.pointList.splice(
+            this.pointList.length - 1,
+            1,
+            cartesian.clone()
+          );
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 鼠标右击完成绘制
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 2) return;
+      this.state = -1;
+      this.linePrimitive = this.showPrimitiveOnMap();
+      window.Viewer.entities.remove(this.lineEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.lineEntity = null;
+      this.floatPoint = null;
+      this.stopDraw();
+      emitter.emit("drawEnd");
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.lineEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.linePrimitive);
+    this.linePrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.linePrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.lineEntity);
+          this.lineEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.linePrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  showPrimitiveOnMap(): Primitive {
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new GroundPolylineGeometry({
+        positions: this.pointList,
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPolylinePrimitive({
+        geometryInstances: instance,
+        appearance: new PolylineMaterialAppearance({
+          material: this.outlineMaterial,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算曲线,若有两个点则应该直接返回两个点的连线,若有三个点则返回处理后的坐标集合
+      return this.pointList;
+    };
+    return window.Viewer.entities.add({
+      polyline: new PolylineGraphics({
+        positions: new CallbackProperty(update, false),
+        show: true,
+        material: Color.BLUE,
+        clampToGround: true,
+      }),
+    });
+  }
+}
+
+// * 自由线
+class FreeHandPolyline extends BaseLine implements PlotFuncI {
+  constructor() {
+    super({
+      type: "FreeHandPolyline",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      step: -1,
+      linePrimitive: null,
+      lineEntity: null,
+      floatPoint: null,
+      floatPointArr: [],
+      modifyHandler: null,
+      pointList: [],
+      outlineMaterial: Material.fromType("PolylineOutline", {
+        outlineWidth: 5,
+      }),
+      selectPoint: null,
+      clickStep: 0,
+    });
+  }
+
+  startDraw() {
+    this.state = 1;
+    // * 单击开始绘制
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.pointList.push(cartesian.clone());
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    // * 移动时改变物体positions
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian.clone();
+      else this.floatPoint = this.creatPoint(cartesian.clone());
+      // * 只有当第一次点击之后再给pointList添加值
+      if (this.pointList.length == 1) this.pointList.push(cartesian.clone());
+
+      // * 若点击了一次就创建entity使用callback进行数据更新
+      if (this.pointList.length == 2 && !this.lineEntity) {
+        this.lineEntity = this.createEntity();
+      } else if (this.pointList.length >= 2) {
+        // * 随着鼠标移动添加数据
+        this.pointList.push(cartesian.clone());
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+    // * 鼠标右击完成绘制
+    this.handler.setInputAction(() => {
+      if (this.pointList.length < 2) return;
+      this.state = -1;
+      this.linePrimitive = this.showPrimitiveOnMap();
+      window.Viewer.entities.remove(this.lineEntity);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.lineEntity = null;
+      this.floatPoint = null;
+      this.stopDraw();
+      emitter.emit("drawEnd");
+    }, ScreenSpaceEventType.RIGHT_CLICK);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.pointList.forEach((point) => {
+      const billboard = this.creatPoint(point);
+      this.floatPointArr.push(billboard);
+    });
+    // 进入编辑之后将创建好的primitive删除,添加entity用以适应动态数据变化
+    this.lineEntity = this.createEntity();
+    window.Viewer.scene.groundPrimitives.remove(this.linePrimitive);
+    this.linePrimitive = null;
+
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+
+      // 如果有移动点在跟随鼠标移动,则在单击的时候固定此点的位置
+      if (this.step != -1) {
+        this.pointList[this.step] = cartesian.clone();
+        this.step = -1;
+        this.floatPoint = null;
+        return;
+      } else {
+        // 如果没有移动点跟随鼠标移动,则在单击的时候查看是否有要素,如有则设置跟随点
+        const feature = window.Viewer.scene.pick(evt.position);
+        if (
+          defined(feature) &&
+          feature.primitive instanceof Billboard &&
+          feature.primitive.id == "moveBillboard"
+        ) {
+          this.floatPoint = feature.primitive;
+          this.step = this.floatPointArr.indexOf(this.floatPoint);
+        } else {
+          this.floatPoint = null;
+          this.state = -1;
+          this.floatPointArr.forEach((point: Billboard) => {
+            window.Viewer.billboards.remove(point);
+          });
+          this.floatPointArr = [];
+          this.linePrimitive = this.showPrimitiveOnMap();
+          window.Viewer.entities.remove(this.lineEntity);
+          this.lineEntity = null;
+          this.stopDraw();
+          emitter.emit("modifiedEnd");
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      if (this.step != -1 && this.floatPoint) {
+        const cartesian = getCatesian3FromPX(evt.endPosition);
+        if (!cartesian) return;
+        this.floatPoint.position = cartesian;
+        this.pointList[this.step] = cartesian.clone();
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr[]) {
+    this.pointList = data;
+    this.linePrimitive = this.showPrimitiveOnMap();
+  }
+  getLnglats(): PointArr[] {
+    const arr = [];
+    for (let i = 0; i < this.pointList.length; i++) {
+      const item = cartesianToLonlat(this.pointList[i]);
+      arr.push(item);
+    }
+    return arr;
+  }
+  getPositions(): any[] {
+    return this.pointList;
+  }
+  showPrimitiveOnMap(): Primitive {
+    const instance = new GeometryInstance({
+      id: this.objId,
+      geometry: new GroundPolylineGeometry({
+        positions: this.pointList,
+      }),
+    });
+
+    return window.Viewer.scene.groundPrimitives.add(
+      new GroundPolylinePrimitive({
+        geometryInstances: instance,
+        appearance: new PolylineMaterialAppearance({
+          material: this.outlineMaterial,
+        }),
+      })
+    );
+  }
+  // * 创建中间entity以适应动态数据
+  createEntity(): Entity {
+    const update = () => {
+      // * 计算曲线,若有两个点则应该直接返回两个点的连线,若有三个点则返回处理后的坐标集合
+      return this.pointList;
+    };
+    return window.Viewer.entities.add({
+      polyline: new PolylineGraphics({
+        positions: new CallbackProperty(update, false),
+        show: true,
+        material: Color.BLUE,
+        clampToGround: true,
+      }),
+    });
+  }
+}
+
+export { Arc, Curve, Polyline, FreeHandPolyline };

+ 8 - 0
src/views/map3d/plot/graphicsDraw/pointDraw/algorithm.ts

@@ -0,0 +1,8 @@
+import type { AllPlotI } from "../../interface";
+export const pointPlot: AllPlotI = {
+  version: "1.0.0",
+  createTime: "2023-2-6",
+  updateTime: "2023-2-7",
+  author: "c-lei-en",
+  algorithm: {},
+};

+ 172 - 0
src/views/map3d/plot/graphicsDraw/pointDraw/index.ts

@@ -0,0 +1,172 @@
+import {
+  Cartesian3,
+  HeadingPitchRoll,
+  HeightReference,
+  Matrix4,
+  Model,
+  ScreenSpaceEventHandler,
+  ScreenSpaceEventType,
+  Transforms,
+  VerticalOrigin,
+} from "cesium";
+import type { PlotFuncI, PointArr, BasePointI } from "../../interface";
+import { getCatesian3FromPX, cartesianToLonlat } from "../../tools";
+import emitter from "@/mitt";
+
+class BasePoint implements BasePointI {
+  type: string;
+  baseType: string;
+  objId: number;
+  handler: any;
+  state: number; //state用于区分当前的状态 0 为删除 1为添加 2为编辑
+  floatPoint: any;
+  pointPrimitive: any;
+  modifyHandler: any;
+  pointList: any;
+  constructor(obj: BasePointI) {
+    this.type = obj.type;
+    this.baseType = "point";
+    this.objId = obj.objId;
+    this.handler = obj.handler;
+    this.pointPrimitive = obj.pointPrimitive;
+    this.state = obj.state; //state用于区分当前的状态 0 为删除 1为添加 2为编辑
+    this.floatPoint = obj.floatPoint;
+    this.modifyHandler = obj.modifyHandler;
+    this.pointList = obj.pointList;
+  }
+}
+
+// * marker广告牌
+class Marker extends BasePoint implements PlotFuncI {
+  constructor() {
+    super({
+      type: "Marker",
+      objId: Number(
+        new Date().getTime() + "" + Number(Math.random() * 1000).toFixed(0)
+      ),
+      handler: new ScreenSpaceEventHandler(window.Viewer.scene.canvas),
+      state: -1,
+      pointPrimitive: null,
+      floatPoint: null,
+      modifyHandler: null,
+      pointList: [],
+    });
+  }
+  disable() {
+    if (this.pointPrimitive) {
+      window.Viewer.billboards.remove(this.pointPrimitive);
+      window.Viewer.billboards.remove(this.floatPoint);
+      this.pointPrimitive = null;
+    }
+    this.state = -1;
+    this.stopDraw();
+  }
+  stopDraw() {
+    if (this.handler) {
+      this.handler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+      this.handler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
+      this.handler.destroy();
+      this.handler = null;
+    }
+    if (this.modifyHandler) {
+      this.modifyHandler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+      this.modifyHandler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
+      this.modifyHandler.destroy();
+      this.modifyHandler = null;
+    }
+  }
+  startDraw() {
+    this.state = 1;
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.position);
+      if (!cartesian) return;
+      this.state = -1;
+      this.pointList.push(cartesian);
+      this.pointPrimitive = this.showPrimitiveOnMap(cartesian);
+      this.floatPoint.show = false;
+      this.stopDraw();
+      emitter.emit("drawEnd");
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.handler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) this.floatPoint.position = cartesian;
+      else this.floatPoint = this.creatPoint(cartesian);
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  startModify() {
+    if (!this.modifyHandler)
+      this.modifyHandler = new ScreenSpaceEventHandler(
+        window.Viewer.scene.canvas
+      );
+    this.state = 2;
+    this.floatPoint.show = true;
+    this.modifyHandler.setInputAction((evt: any) => {
+      this.floatPoint.show = false;
+      this.state = -1;
+      this.pointPrimitive.position = getCatesian3FromPX(evt.position);
+      this.stopDraw();
+      emitter.emit("modifiedEnd");
+    }, ScreenSpaceEventType.LEFT_CLICK);
+    this.modifyHandler.setInputAction((evt: any) => {
+      const cartesian = getCatesian3FromPX(evt.endPosition);
+      if (!cartesian) return;
+      if (this.floatPoint) {
+        this.floatPoint.position = cartesian;
+        this.pointList = cartesian;
+      } else {
+        return;
+      }
+    }, ScreenSpaceEventType.MOUSE_MOVE);
+  }
+  createByData(data: PointArr) {
+    this.pointList = [];
+    this.state = -1;
+    this.floatPoint = null;
+    this.modifyHandler = null;
+    this.pointList = Cartesian3.fromDegreesArray(data);
+    this.pointPrimitive = this.showPrimitiveOnMap(this.pointList);
+    this.pointPrimitive.objId = this.objId;
+  }
+  getLnglats() {
+    return cartesianToLonlat(this.pointList[0]);
+  }
+  getPositions() {
+    return this.pointList;
+  }
+  creatPoint(cartesian: PointArr) {
+    return window.Viewer.billboards.add({
+      position: cartesian,
+      image: "/src/assets/icon/point.png",
+      verticalOrigin: VerticalOrigin.BOTTOM,
+      heightReference: HeightReference.CLAMP_TO_GROUND,
+    });
+  }
+  showPrimitiveOnMap(positons: Cartesian3) {
+    return window.Viewer.billboards.add({
+      position: positons,
+      id: this.objId,
+      image: "/src/assets/icon/mark.png",
+      verticalOrigin: VerticalOrigin.BOTTOM,
+      heightReference: HeightReference.CLAMP_TO_GROUND,
+    });
+  }
+  showPrimitiveModelOnMap(url: string, modelMatrix?: Matrix4 | undefined) {
+    return window.Viewer.scene.primitives.add(
+      Model.fromGltf({
+        id: this.objId,
+        url,
+        modelMatrix:
+          modelMatrix ??
+          Transforms.headingPitchRollToFixedFrame(
+            this.pointList as Cartesian3,
+            new HeadingPitchRoll(0, 0, 0)
+          ),
+        heightReference: HeightReference.CLAMP_TO_GROUND,
+        scene: window.Viewer.scene,
+      })
+    );
+  }
+}
+
+export { Marker };

+ 272 - 0
src/views/map3d/plot/index.ts

@@ -0,0 +1,272 @@
+import {
+  BillboardCollection,
+  defined,
+  ScreenSpaceEventHandler,
+  ScreenSpaceEventType,
+} from "cesium";
+import { Marker } from "./graphicsDraw/pointDraw";
+import {
+  Arc,
+  Curve,
+  FreeHandPolyline,
+  Polyline,
+} from "./graphicsDraw/lineDraw";
+import type { PlotClass } from "./interface";
+import type { PointArr } from "./interface";
+import emitter from "@/mitt";
+import {
+  Circle,
+  ClosedCurve,
+  Ellipse,
+  FreeHandPolygon,
+  GatheringPlace,
+  Lune,
+  Polygon,
+  Rectangle,
+  Sector,
+} from "./graphicsDraw/areaDraw";
+import {
+  AssaultDirection,
+  AttackArrow,
+  DoubleArrow,
+  FineArrow,
+  SquadCombat,
+  StraightArrow,
+  TailedAttackArrow,
+  TailedSquadCombat,
+} from "./graphicsDraw/arrowDraw";
+
+export default class PlotDraw {
+  drawArr: PlotClass[];
+  handler: any;
+  jsonData: any;
+  nowObj: PlotClass | null;
+  constructor() {
+    this.drawArr = [];
+    this.handler = null;
+    this.nowObj = null;
+    this.init();
+  }
+  init() {
+    this.jsonData = {
+      markerData: [],
+      arcData: [],
+      curveData: [],
+      polylineData: [],
+      freehandpolylineData: [],
+      circleData: [],
+      ellipseData: [],
+      luneData: [],
+      sectorData: [],
+      rectangleData: [],
+      closedcurveData: [],
+      polygonData: [],
+      freehandpolygonData: [],
+      gatheringplaceData: [],
+      doublearrowData: [],
+      straightarrowData: [],
+      finearrowData: [],
+      assaultdirectionData: [],
+      attackarrowData: [],
+      tailedattackarrowData: [],
+      squadcombatData: [],
+      tailedsquadcombatData: [],
+    };
+    this.drawArr = [];
+    emitter.on("drawEnd", () => {
+      this.drawArr.push(this.nowObj as PlotClass);
+      this.drawArr[this.drawArr.length - 1]?.stopDraw();
+      this.saveData();
+      this.nowObj = null;
+    });
+    emitter.on("modifiedEnd", () => {
+      this.startModified();
+      this.nowObj = null;
+    });
+    // * 将点位集合在此处创建,因为不论绘制/修改哪一个标绘都需要有移动点
+    if (!window.Viewer.billboards)
+      window.Viewer.billboards = window.Viewer.scene.primitives.add(
+        new BillboardCollection({
+          scene: window.Viewer.scene,
+        })
+      );
+  }
+  disable() {
+    if (this.handler) {
+      this.drawArr.splice(this.drawArr.indexOf(this.nowObj as PlotClass), 1);
+      this.nowObj?.disable();
+      this.nowObj = null;
+      this.handler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+      this.handler.destroy();
+      this.handler = null;
+    }
+  }
+  stopDraw() {
+    this.drawArr[-1].stopDraw();
+  }
+  draw(type: string) {
+    switch (type) {
+      case "marker":
+        this.nowObj = new Marker();
+        this.nowObj.startDraw();
+        break;
+      case "arc":
+        this.nowObj = new Arc();
+        this.nowObj.startDraw();
+        break;
+      case "curve":
+        this.nowObj = new Curve();
+        this.nowObj.startDraw();
+        break;
+      case "polyline":
+        this.nowObj = new Polyline();
+        this.nowObj.startDraw();
+        break;
+      case "freeHandPolyline":
+        this.nowObj = new FreeHandPolyline();
+        this.nowObj.startDraw();
+        break;
+      case "circle":
+        this.nowObj = new Circle();
+        this.nowObj.startDraw();
+        break;
+      case "ellipse":
+        this.nowObj = new Ellipse();
+        this.nowObj.startDraw();
+        break;
+      case "lune":
+        this.nowObj = new Lune();
+        this.nowObj.startDraw();
+        break;
+      case "sector":
+        this.nowObj = new Sector();
+        this.nowObj.startDraw();
+        break;
+      case "rectangle":
+        this.nowObj = new Rectangle();
+        this.nowObj.startDraw();
+        break;
+      case "closedCurve":
+        this.nowObj = new ClosedCurve();
+        this.nowObj.startDraw();
+        break;
+      case "polygon":
+        this.nowObj = new Polygon();
+        this.nowObj.startDraw();
+        break;
+      case "freeHandPolygon":
+        this.nowObj = new FreeHandPolygon();
+        this.nowObj.startDraw();
+        break;
+      case "gatheringPlace":
+        this.nowObj = new GatheringPlace();
+        this.nowObj.startDraw();
+        break;
+      case "doubleArrow":
+        this.nowObj = new DoubleArrow();
+        this.nowObj.startDraw();
+        break;
+      case "straightArrow":
+        this.nowObj = new StraightArrow();
+        this.nowObj.startDraw();
+        break;
+      case "fineArrow":
+        this.nowObj = new FineArrow();
+        this.nowObj.startDraw();
+        break;
+      case "assaultDirection":
+        this.nowObj = new AssaultDirection();
+        this.nowObj.startDraw();
+        break;
+      case "attackArrow":
+        this.nowObj = new AttackArrow();
+        this.nowObj.startDraw();
+        break;
+      case "tailedAttackArrow":
+        this.nowObj = new TailedAttackArrow();
+        this.nowObj.startDraw();
+        break;
+      case "squadCombat":
+        this.nowObj = new SquadCombat();
+        this.nowObj.startDraw();
+        break;
+      case "tailedSquadCombat":
+        this.nowObj = new TailedSquadCombat();
+        this.nowObj.startDraw();
+        break;
+      default:
+        break;
+    }
+  }
+  saveData() {
+    //保存用户数据
+    const positions: PointArr = this.nowObj?.getLnglats() as PointArr;
+    this.jsonData[this.nowObj?.type.toLowerCase() + "Data"].push(positions);
+    console.log("保存的数据:", this.jsonData);
+  }
+  showData() {
+    console.log(this.jsonData);
+  }
+  startModified() {
+    const $this = this;
+    this.handler = new ScreenSpaceEventHandler(window.Viewer.scene.canvas);
+    // 单击选中开始编辑
+    this.handler.setInputAction(function (evt: any) {
+      const pick = window.Viewer.scene.pick(evt.position);
+      if ($this.nowObj) {
+        if ($this.nowObj.state != -1) {
+          console.log("上一步操作未结束,请继续完成上一步!");
+          return;
+        }
+      } else {
+        if (defined(pick) && pick.id) {
+          for (let i = 0; i < $this.drawArr.length; i++) {
+            if (pick.id == $this.drawArr[i].objId) {
+              $this.nowObj = $this.drawArr[i];
+              $this.drawArr[i].startModify();
+              $this.endModify();
+              emitter.emit("seletedOne");
+              break;
+            }
+          }
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+  }
+  endModify() {
+    this.handler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
+    this.handler.destroy();
+    this.handler = null;
+  }
+  seletedOne() {
+    const $this = this;
+    this.handler = new ScreenSpaceEventHandler(window.Viewer.scene.canvas);
+    // 单击选中开始编辑
+    this.handler.setInputAction(function (evt: any) {
+      const pick = window.Viewer.scene.pick(evt.position);
+      if (defined(pick) && pick.id) {
+        for (let i = 0; i < $this.drawArr.length; i++) {
+          if (pick.id == $this.drawArr[i].objId) {
+            $this.nowObj = $this.drawArr[i];
+            emitter.emit("seletedOne");
+            break;
+          }
+        }
+      }
+    }, ScreenSpaceEventType.LEFT_CLICK);
+  }
+  clearOne() {
+    if (this.nowObj) {
+      const index = this.drawArr.indexOf(this.nowObj);
+      this.drawArr[index]?.disable();
+      this.drawArr.splice(index, 1);
+      this.startModified();
+      this.nowObj = null;
+    }
+  }
+  clearAll() {
+    for (let i = 0; i < this.drawArr.length; i++) {
+      this.drawArr[i].disable();
+    }
+  }
+}

+ 190 - 0
src/views/map3d/plot/interface/index.ts

@@ -0,0 +1,190 @@
+import type { Primitive } from "cesium";
+import type {
+  Polygon,
+  Circle,
+  ClosedCurve,
+  Ellipse,
+  Lune,
+  Rectangle,
+  Sector,
+  FreeHandPolygon,
+  GatheringPlace,
+} from "../graphicsDraw/areaDraw";
+import type {
+  AssaultDirection,
+  AttackArrow,
+  DoubleArrow,
+  FineArrow,
+  SquadCombat,
+  StraightArrow,
+  TailedAttackArrow,
+  TailedSquadCombat,
+} from "../graphicsDraw/arrowDraw";
+import type {
+  Arc,
+  Curve,
+  FreeHandPolyline,
+  Polyline,
+} from "../graphicsDraw/lineDraw";
+import type { Marker } from "../graphicsDraw/pointDraw";
+
+export type PointArr = number[];
+
+// * 所有类型的algorithm所遵守的接口
+export interface AllPlotI {
+  version: string;
+  createTime: string;
+  updateTime: string;
+  author: string;
+  algorithm: {
+    [propsName: string]: any;
+  };
+}
+
+// * 基础点类接口
+export interface BasePointI {
+  type: string;
+  objId: number;
+  handler: any;
+  state: number; //state用于区分当前的状态 0 为删除 1为添加 2为编辑
+  pointPrimitive: any;
+  floatPoint: any;
+  modifyHandler: any;
+  pointList: any[];
+}
+
+// * 基础线类接口
+export interface BaseLineI {
+  type: string;
+  objId: number;
+  handler: any;
+  state: number; //state用于区分当前的状态 0 为删除 1为添加 2为编辑
+  step: number; // 表明选中了第几个点
+  linePrimitive: any;
+  lineEntity: any;
+  floatPoint: any;
+  floatPointArr: any[];
+  modifyHandler: any;
+  pointList: any[];
+  outlineMaterial: any;
+  selectPoint: any;
+  clickStep: number;
+}
+
+// * 基础面类接口
+export interface BaseAreaI {
+  type: string;
+  objId: number;
+  handler: any;
+  state: number; //state用于区分当前的状态 0 为删除 1为添加 2为编辑
+  step: number; // 表明选中了第几个点
+  areaPrimitive: any;
+  areaEntity: any;
+  floatPoint: any;
+  floatPointArr: any[];
+  modifyHandler: any;
+  pointList: any[];
+  material: any;
+  selectPoint: any;
+  clickStep: number;
+}
+
+// * 基础箭头类接口
+export interface BaseArrowI {
+  type: string;
+  objId: number;
+  handler: any;
+  state: number; //state用于区分当前的状态 0 为删除 1为添加 2为编辑
+  step: number; // 表明选中了第几个点
+  arrowPrimitive: any;
+  arrowEntity: any;
+  floatPoint: any;
+  floatPointArr: any[];
+  modifyHandler: any;
+  pointList: any[];
+  material: any;
+  selectPoint: any;
+  clickStep: number;
+}
+
+// * 所有绘制类型所需要遵守实现的函数
+export interface PlotFuncI {
+  // * 退出绘制
+  disable: () => void;
+  // * 销毁当前的绘制事件
+  stopDraw: () => void;
+  // * 开始绘制
+  startDraw: () => void;
+  // * 开始编辑
+  startModify: () => void;
+  // * 通过数据创建要素
+  createByData: (data: any) => void;
+  // * 获取当前要素的经纬度
+  getLnglats: () => any;
+  // * 获取当前要素的坐标
+  getPositions: () => any;
+  // * 创建点
+  creatPoint: (cartesian: PointArr) => Primitive;
+  // * 将实体对象添加进界面显示
+  showPrimitiveOnMap: (positons: any) => Primitive;
+}
+
+export interface PlotI {
+  version: string;
+  Constants: {
+    TWO_PI: number;
+    HALF_PI: number;
+    FITTING_COUNT: number;
+    ZERO_TOLERANCE: number;
+  };
+  PlotUtils: {
+    distance: Function;
+    wholeDistance: Function;
+    getBaseLength: Function;
+    mid: Function;
+    getCircleCenterOfThreePoints: Function;
+    getIntersectPoint: Function;
+    getAzimuth: Function;
+    getAngleOfThreePoints: Function;
+    isClockWise: Function;
+    getPointOnLine: Function;
+    getCubicValue: Function;
+    getThirdPoint: Function;
+    getArcPoints: Function;
+    getBisectorNormals: Function;
+    getNormal: Function;
+    getCurvePoints: Function;
+    getLeftMostControlPoint: Function;
+    getRightMostControlPoint: Function;
+    getBezierPoints: Function;
+    getBinomialFactor: Function;
+    getFactorial: Function;
+    getQBSplinePoints: Function;
+    getQuadricBSplineFactor: Function;
+  };
+}
+
+// * 定义所有绘制类型
+export type PlotClass =
+  | Marker
+  | Arc
+  | Curve
+  | Polyline
+  | FreeHandPolyline
+  | Circle
+  | Ellipse
+  | Lune
+  | Sector
+  | Rectangle
+  | ClosedCurve
+  | Polygon
+  | FreeHandPolygon
+  | GatheringPlace
+  | DoubleArrow
+  | StraightArrow
+  | FineArrow
+  | AssaultDirection
+  | AttackArrow
+  | TailedAttackArrow
+  | SquadCombat
+  | TailedSquadCombat;

+ 470 - 0
src/views/map3d/plot/tools/index.ts

@@ -0,0 +1,470 @@
+import {
+  Cesium3DTileFeature,
+  type Cartesian2,
+  Math as cesiumMath,
+  Cartesian3,
+} from "cesium";
+import type { PointArr, PlotI } from "../interface";
+
+export const P: PlotI = {
+  version: "1.0.0",
+  Constants: {
+    TWO_PI: Math.PI * 2,
+    HALF_PI: Math.PI / 2,
+    FITTING_COUNT: 100,
+    ZERO_TOLERANCE: 0.0001,
+  },
+  PlotUtils: {
+    distance: function (pnt1: PointArr, pnt2: PointArr): number {
+      return Math.sqrt(
+        Math.pow(pnt1[0] - pnt2[0], 2) + Math.pow(pnt1[1] - pnt2[1], 2)
+      );
+    },
+    wholeDistance: function (points: Array<PointArr>): number {
+      let distance = 0;
+      for (let i = 0; i < points.length - 1; i++)
+        distance += P.PlotUtils.distance(points[i], points[i + 1]);
+      return distance;
+    },
+    getBaseLength: function (points: Array<PointArr>): number {
+      return Math.pow(P.PlotUtils.wholeDistance(points), 0.99);
+    },
+    mid: function (pnt1: PointArr, pnt2: PointArr): PointArr {
+      return [(pnt1[0] + pnt2[0]) / 2, (pnt1[1] + pnt2[1]) / 2];
+    },
+    getIntersectPoint: function (
+      pntA: PointArr,
+      pntB: PointArr,
+      pntC: PointArr,
+      pntD: PointArr
+    ): PointArr {
+      if (pntA[1] == pntB[1]) {
+        const f = (pntD[0] - pntC[0]) / (pntD[1] - pntC[1]);
+        const x = f * (pntA[1] - pntC[1]) + pntC[0];
+        const y = pntA[1];
+        return [x, y];
+      }
+      if (pntC[1] == pntD[1]) {
+        const e = (pntB[0] - pntA[0]) / (pntB[1] - pntA[1]);
+        const x = e * (pntC[1] - pntA[1]) + pntA[0];
+        const y = pntC[1];
+        return [x, y];
+      }
+      const e = (pntB[0] - pntA[0]) / (pntB[1] - pntA[1]);
+      const f = (pntD[0] - pntC[0]) / (pntD[1] - pntC[1]);
+      const y = (e * pntA[1] - pntA[0] - f * pntC[1] + pntC[0]) / (e - f);
+      const x = e * y - e * pntA[1] + pntA[0];
+      return [x, y];
+    },
+    getCircleCenterOfThreePoints: function (
+      pnt1: PointArr,
+      pnt2: PointArr,
+      pnt3: PointArr
+    ): PointArr {
+      const pntA = [(pnt1[0] + pnt2[0]) / 2, (pnt1[1] + pnt2[1]) / 2];
+      const pntB = [pntA[0] - pnt1[1] + pnt2[1], pntA[1] + pnt1[0] - pnt2[0]];
+      const pntC = [(pnt1[0] + pnt3[0]) / 2, (pnt1[1] + pnt3[1]) / 2];
+      const pntD = [pntC[0] - pnt1[1] + pnt3[1], pntC[1] + pnt1[0] - pnt3[0]];
+      return P.PlotUtils.getIntersectPoint(pntA, pntB, pntC, pntD);
+    },
+    getAzimuth: function (startPnt: PointArr, endPnt: PointArr): number {
+      let azimuth = 0;
+      const angle = Math.asin(
+        Math.abs(endPnt[1] - startPnt[1]) /
+          P.PlotUtils.distance(startPnt, endPnt)
+      );
+      if (endPnt[1] >= startPnt[1] && endPnt[0] >= startPnt[0])
+        azimuth = angle + Math.PI;
+      else if (endPnt[1] >= startPnt[1] && endPnt[0] < startPnt[0])
+        azimuth = P.Constants.TWO_PI - angle;
+      else if (endPnt[1] < startPnt[1] && endPnt[0] < startPnt[0])
+        azimuth = angle;
+      else if (endPnt[1] < startPnt[1] && endPnt[0] >= startPnt[0])
+        azimuth = Math.PI - angle;
+      return azimuth;
+    },
+    getAngleOfThreePoints: function (
+      pntA: PointArr,
+      pntB: PointArr,
+      pntC: PointArr
+    ): number {
+      const angle =
+        P.PlotUtils.getAzimuth(pntB, pntA) - P.PlotUtils.getAzimuth(pntB, pntC);
+      return angle < 0 ? angle + P.Constants.TWO_PI : angle;
+    },
+    isClockWise: function (
+      pnt1: PointArr,
+      pnt2: PointArr,
+      pnt3: PointArr
+    ): boolean {
+      return (
+        (pnt3[1] - pnt1[1]) * (pnt2[0] - pnt1[0]) >
+        (pnt2[1] - pnt1[1]) * (pnt3[0] - pnt1[0])
+      );
+    },
+    getPointOnLine: function (
+      t: number,
+      startPnt: PointArr,
+      endPnt: PointArr
+    ): PointArr {
+      const x = startPnt[0] + t * (endPnt[0] - startPnt[0]);
+      const y = startPnt[1] + t * (endPnt[1] - startPnt[1]);
+      return [x, y];
+    },
+    getCubicValue: function (
+      t: number,
+      startPnt: PointArr,
+      cPnt1: PointArr,
+      cPnt2: PointArr,
+      endPnt: PointArr
+    ): PointArr {
+      t = Math.max(Math.min(t, 1), 0);
+      const tp = 1 - t;
+      const t2 = t * t;
+      const t3 = t2 * t;
+      const tp2 = tp * tp;
+      const tp3 = tp2 * tp;
+      const x =
+        tp3 * startPnt[0] +
+        3 * tp2 * t * cPnt1[0] +
+        3 * tp * t2 * cPnt2[0] +
+        t3 * endPnt[0];
+      const y =
+        tp3 * startPnt[1] +
+        3 * tp2 * t * cPnt1[1] +
+        3 * tp * t2 * cPnt2[1] +
+        t3 * endPnt[1];
+      return [x, y];
+    },
+    getThirdPoint: function (
+      startPnt: PointArr,
+      endPnt: PointArr,
+      angle: number,
+      distance: number,
+      clockWise: boolean
+    ): PointArr {
+      const azimuth = P.PlotUtils.getAzimuth(startPnt, endPnt);
+      const alpha = clockWise ? azimuth + angle : azimuth - angle;
+      const dx = distance * Math.cos(alpha);
+      const dy = distance * Math.sin(alpha);
+      return [endPnt[0] + dx, endPnt[1] + dy];
+    },
+    getArcPoints: function (
+      center: PointArr,
+      radius: number,
+      startAngle: number,
+      endAngle: number
+    ): Array<Cartesian3> {
+      let x,
+        y,
+        angleDiff = endAngle - startAngle;
+      angleDiff = angleDiff < 0 ? angleDiff + P.Constants.TWO_PI : angleDiff;
+      const pnts = [];
+      for (let i = 0; i <= P.Constants.FITTING_COUNT; i++) {
+        const angle = startAngle + (angleDiff * i) / P.Constants.FITTING_COUNT;
+        x = center[0] + radius * Math.cos(angle);
+        y = center[1] + radius * Math.sin(angle);
+        pnts.push(lonLatToCartesian([x, y]));
+      }
+      return pnts;
+    },
+    getNormal: function (
+      pnt1: PointArr,
+      pnt2: PointArr,
+      pnt3: PointArr
+    ): PointArr {
+      let dX1 = pnt1[0] - pnt2[0];
+      let dY1 = pnt1[1] - pnt2[1];
+      const d1 = Math.sqrt(dX1 * dX1 + dY1 * dY1);
+      dX1 /= d1;
+      dY1 /= d1;
+
+      let dX2 = pnt3[0] - pnt2[0];
+      let dY2 = pnt3[1] - pnt2[1];
+      const d2 = Math.sqrt(dX2 * dX2 + dY2 * dY2);
+      dX2 /= d2;
+      dY2 /= d2;
+
+      const uX = dX1 + dX2;
+      const uY = dY1 + dY2;
+      return [uX, uY];
+    },
+    getBisectorNormals: function (
+      t: number,
+      pnt1: PointArr,
+      pnt2: PointArr,
+      pnt3: PointArr
+    ): Array<PointArr> {
+      const normal = P.PlotUtils.getNormal(pnt1, pnt2, pnt3);
+      const dist = Math.sqrt(normal[0] * normal[0] + normal[1] * normal[1]);
+      const uX = normal[0] / dist;
+      const uY = normal[1] / dist;
+      const d1 = P.PlotUtils.distance(pnt1, pnt2);
+      const d2 = P.PlotUtils.distance(pnt2, pnt3);
+      let bisectorNormalRight, bisectorNormalLeft, dt, x, y;
+      if (dist > P.Constants.ZERO_TOLERANCE) {
+        if (P.PlotUtils.isClockWise(pnt1, pnt2, pnt3)) {
+          dt = t * d1;
+          x = pnt2[0] - dt * uY;
+          y = pnt2[1] + dt * uX;
+          bisectorNormalRight = [x, y];
+          dt = t * d2;
+          x = pnt2[0] + dt * uY;
+          y = pnt2[1] - dt * uX;
+          bisectorNormalLeft = [x, y];
+        } else {
+          dt = t * d1;
+          x = pnt2[0] + dt * uY;
+          y = pnt2[1] - dt * uX;
+          bisectorNormalRight = [x, y];
+          dt = t * d2;
+          x = pnt2[0] - dt * uY;
+          y = pnt2[1] + dt * uX;
+          bisectorNormalLeft = [x, y];
+        }
+      } else {
+        x = pnt2[0] + t * (pnt1[0] - pnt2[0]);
+        y = pnt2[1] + t * (pnt1[1] - pnt2[1]);
+        bisectorNormalRight = [x, y];
+        x = pnt2[0] + t * (pnt3[0] - pnt2[0]);
+        y = pnt2[1] + t * (pnt3[1] - pnt2[1]);
+        bisectorNormalLeft = [x, y];
+      }
+      return [bisectorNormalRight, bisectorNormalLeft];
+    },
+    getLeftMostControlPoint: function (
+      t: number = 1,
+      controlPoints: Array<PointArr>
+    ): PointArr {
+      const pnt1 = controlPoints[0];
+      const pnt2 = controlPoints[1];
+      const pnt3 = controlPoints[2];
+      const pnts = P.PlotUtils.getBisectorNormals(0, pnt1, pnt2, pnt3);
+      const normalRight = pnts[0];
+      const normal = P.PlotUtils.getNormal(pnt1, pnt2, pnt3);
+      const dist = Math.sqrt(normal[0] * normal[0] + normal[1] * normal[1]);
+      let controlX, controlY;
+      if (dist > P.Constants.ZERO_TOLERANCE) {
+        const mid = P.PlotUtils.mid(pnt1, pnt2);
+        const pX = pnt1[0] - mid[0];
+        const pY = pnt1[1] - mid[1];
+
+        const d1 = P.PlotUtils.distance(pnt1, pnt2);
+        // normal at midpoint
+        const n = 2.0 / d1;
+        const nX = -n * pY;
+        const nY = n * pX;
+
+        // upper triangle of symmetric transform matrix
+        const a11 = nX * nX - nY * nY;
+        const a12 = 2 * nX * nY;
+        const a22 = nY * nY - nX * nX;
+
+        const dX = normalRight[0] - mid[0];
+        const dY = normalRight[1] - mid[1];
+
+        // coordinates of reflected vector
+        controlX = mid[0] + a11 * dX + a12 * dY;
+        controlY = mid[1] + a12 * dX + a22 * dY;
+      } else {
+        controlX = pnt1[0] + t * (pnt2[0] - pnt1[0]);
+        controlY = pnt1[1] + t * (pnt2[1] - pnt1[1]);
+      }
+      return [controlX, controlY];
+    },
+    getRightMostControlPoint: function (
+      t: number = 1,
+      controlPoints: Array<PointArr>
+    ): PointArr {
+      const count = controlPoints.length;
+      const pnt1 = controlPoints[count - 3];
+      const pnt2 = controlPoints[count - 2];
+      const pnt3 = controlPoints[count - 1];
+      const pnts = P.PlotUtils.getBisectorNormals(0, pnt1, pnt2, pnt3);
+      const normalLeft = pnts[1];
+      const normal = P.PlotUtils.getNormal(pnt1, pnt2, pnt3);
+      const dist = Math.sqrt(normal[0] * normal[0] + normal[1] * normal[1]);
+      let controlX, controlY;
+      if (dist > P.Constants.ZERO_TOLERANCE) {
+        const mid = P.PlotUtils.mid(pnt2, pnt3);
+        const pX = pnt3[0] - mid[0];
+        const pY = pnt3[1] - mid[1];
+
+        const d1 = P.PlotUtils.distance(pnt2, pnt3);
+        // normal at midpoint
+        const n = 2.0 / d1;
+        const nX = -n * pY;
+        const nY = n * pX;
+
+        // upper triangle of symmetric transform matrix
+        const a11 = nX * nX - nY * nY;
+        const a12 = 2 * nX * nY;
+        const a22 = nY * nY - nX * nX;
+
+        const dX = normalLeft[0] - mid[0];
+        const dY = normalLeft[1] - mid[1];
+
+        // coordinates of reflected vector
+        controlX = mid[0] + a11 * dX + a12 * dY;
+        controlY = mid[1] + a12 * dX + a22 * dY;
+      } else {
+        controlX = pnt3[0] + t * (pnt2[0] - pnt3[0]);
+        controlY = pnt3[1] + t * (pnt2[1] - pnt3[1]);
+      }
+      return [controlX, controlY];
+    },
+    getCurvePoints: function (
+      t: number,
+      controlPoints: Array<PointArr>
+    ): Array<Cartesian3> {
+      const leftControl = P.PlotUtils.getLeftMostControlPoint(t, controlPoints);
+      let normals = [leftControl];
+      for (let i = 0; i < controlPoints.length - 2; i++) {
+        const pnt1 = controlPoints[i];
+        const pnt2 = controlPoints[i + 1];
+        const pnt3 = controlPoints[i + 2];
+        const normalPoints = P.PlotUtils.getBisectorNormals(
+          t,
+          pnt1,
+          pnt2,
+          pnt3
+        );
+        normals = normals.concat(normalPoints);
+      }
+      const rightControl = P.PlotUtils.getRightMostControlPoint(
+        t,
+        controlPoints
+      );
+      normals.push(rightControl);
+      const points = [];
+      for (let i = 0; i < controlPoints.length - 1; i++) {
+        const pnt1 = controlPoints[i];
+        const pnt2 = controlPoints[i + 1];
+        points.push(pnt1);
+        for (let t = 0; t < P.Constants.FITTING_COUNT; t++) {
+          const pnt = P.PlotUtils.getCubicValue(
+            t / P.Constants.FITTING_COUNT,
+            pnt1,
+            normals[i * 2],
+            normals[i * 2 + 1],
+            pnt2
+          );
+          points.push(pnt);
+        }
+        points.push(pnt2);
+      }
+      const pnts = [];
+      for (let i = 0; i <= points.length - 1; i++) {
+        pnts.push(Cartesian3.fromDegrees(points[i][0], points[i][1]));
+      }
+      return pnts;
+    },
+    getFactorial: function (n: number): number {
+      if (n <= 1) return 1;
+      if (n == 2) return 2;
+      if (n == 3) return 6;
+      if (n == 4) return 24;
+      if (n == 5) return 120;
+      let result = 1;
+      for (let i = 1; i <= n; i++) result *= i;
+      return result;
+    },
+    getBinomialFactor: function (n: number, index: number): number {
+      return (
+        P.PlotUtils.getFactorial(n) /
+        (P.PlotUtils.getFactorial(index) * P.PlotUtils.getFactorial(n - index))
+      );
+    },
+    getBezierPoints: function (points: Array<PointArr>): Array<PointArr> {
+      if (points.length <= 2) return points;
+
+      const bezierPoints = [];
+      const n = points.length - 1;
+      for (let t = 0; t <= 1; t += 0.01) {
+        let x = 0,
+          y = 0;
+        for (let index = 0; index <= n; index++) {
+          const factor = P.PlotUtils.getBinomialFactor(n, index);
+          const a = Math.pow(t, index);
+          const b = Math.pow(1 - t, n - index);
+          x += factor * a * b * points[index][0];
+          y += factor * a * b * points[index][1];
+        }
+        bezierPoints.push([x, y]);
+      }
+      bezierPoints.push(points[n]);
+      return bezierPoints;
+    },
+    getQuadricBSplineFactor: function (k: number, t: number): number {
+      if (k == 0) return Math.pow(t - 1, 2) / 2;
+      if (k == 1) return (-2 * Math.pow(t, 2) + 2 * t + 1) / 2;
+      if (k == 2) return Math.pow(t, 2) / 2;
+      return 0;
+    },
+    getQBSplinePoints: function (points: Array<PointArr>): Array<PointArr> {
+      if (points.length <= 2) return points;
+
+      const n = 2;
+
+      const bSplinePoints = [];
+      const m = points.length - n - 1;
+      bSplinePoints.push(points[0]);
+      for (let i = 0; i <= m; i++) {
+        for (let t = 0; t <= 1; t += 0.05) {
+          let x = 0,
+            y = 0;
+          for (let k = 0; k <= n; k++) {
+            const factor = P.PlotUtils.getQuadricBSplineFactor(k, t);
+            x += factor * points[i + k][0];
+            y += factor * points[i + k][1];
+          }
+          bSplinePoints.push([x, y]);
+        }
+      }
+      bSplinePoints.push(points[points.length - 1]);
+      return bSplinePoints;
+    },
+  },
+};
+
+// * 从当前坐标上获取3D笛卡尔坐标
+export function getCatesian3FromPX(px: Cartesian2) {
+  const picks = window.Viewer.scene.drillPick(px);
+  window.Viewer.render();
+  let cartesian;
+  let isOn3dtiles = false;
+  for (let i = 0; i < picks.length; i++) {
+    if (
+      picks[i] &&
+      picks[i].primitive &&
+      picks[i] instanceof Cesium3DTileFeature
+    ) {
+      //模型上拾取
+      isOn3dtiles = true;
+      break;
+    }
+  }
+  if (isOn3dtiles) {
+    cartesian = window.Viewer.scene.pickPosition(px, cartesian);
+  } else {
+    const ray = window.Viewer.camera.getPickRay(px);
+    if (!ray) return null;
+    cartesian = window.Viewer.scene.globe.pick(ray, window.Viewer.scene);
+  }
+  return cartesian;
+}
+
+// * 笛卡尔坐标转经纬度
+export function cartesianToLonlat(cartesian: any) {
+  const lonLat =
+    window.Viewer.scene.globe.ellipsoid.cartesianToCartographic(cartesian);
+  const lat = cesiumMath.toDegrees(lonLat.latitude);
+  const lng = cesiumMath.toDegrees(lonLat.longitude);
+  return [lng, lat];
+}
+
+// * 经纬度坐标转笛卡尔
+export function lonLatToCartesian(lonLat: any) {
+  const cartesian = Cartesian3.fromDegrees(lonLat[0], lonLat[1]);
+  return cartesian;
+}

+ 81 - 0
src/views/map3d/plotTools/AreaMaterial.vue

@@ -0,0 +1,81 @@
+<template>
+  <el-card style="margin-top: 10px; overflow: auto">
+    <el-input
+      style="margin-top: 10px"
+      v-for="(item, index) in areaConfig"
+      :key="index"
+      v-model="item.value"
+      :oninput="item.oninput"
+    >
+      <template #prepend>
+        {{ item.name }}
+      </template>
+    </el-input>
+    <el-button
+      type="primary"
+      style="margin-top: 5px; margin-left: 80%"
+      @click="areaMaterialClick"
+      >确定</el-button
+    >
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+import { Color, Material } from "cesium";
+import { reactive, toRaw } from "vue";
+import PlotDraw from "../plot";
+import type { BaseAreaI } from "../plot/interface";
+
+const props = defineProps({
+  draw: {
+    type: PlotDraw,
+    required: true,
+  },
+});
+let area = toRaw(props.draw.nowObj as BaseAreaI);
+
+// 线样式修改相关配置
+const areaConfig = reactive([
+  {
+    name: "速度",
+    value: 2,
+    oninput: "value=value.replace(/[^0-9.]/g,'')",
+  },
+  {
+    name: "颜色",
+    value: "#ffff00",
+    oninput: "value=value.replace(/^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/g,'')",
+  },
+]);
+
+function areaMaterialClick() {
+  console.log(area.areaPrimitive);
+  const areaFabric = {
+    type: "AreaFabric",
+    uniforms: {
+      color: Color.fromCssColorString(areaConfig[1].value as string),
+      speed: eval(areaConfig[0].value as string),
+    },
+    source: `czm_material czm_getMaterial(czm_materialInput materialInput) {
+          czm_material material = czm_getDefaultMaterial(materialInput);
+          vec2 st = materialInput.st;
+          float xx = fract(st.s * speed - czm_frameNumber/60.0);
+          float r = xx;
+          float g = sin(czm_frameNumber / 30.0);
+          float b = cos(czm_frameNumber / 30.0);
+          vec3 fragColor;
+          fragColor.rgb = color.rgb / 1.0;
+          fragColor = czm_gammaCorrect(fragColor); // 伽马校正
+          material.alpha = xx;
+          material.diffuse = vec3(r,g,b) / 2.0;
+          material.emission = fragColor.rgb;
+          return material;
+          }`,
+  };
+  area.areaPrimitive.appearance.material = new Material({
+    fabric: areaFabric,
+  });
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 81 - 0
src/views/map3d/plotTools/ArrowMaterial.vue

@@ -0,0 +1,81 @@
+<template>
+  <el-card style="margin-top: 10px; overflow: auto">
+    <el-input
+      style="margin-top: 10px"
+      v-for="(item, index) in arrowConfig"
+      :key="index"
+      v-model="item.value"
+      :oninput="item.oninput"
+    >
+      <template #prepend>
+        {{ item.name }}
+      </template>
+    </el-input>
+    <el-button
+      type="primary"
+      style="margin-top: 5px; margin-left: 80%"
+      @click="arrowMaterialClick"
+      >确定</el-button
+    >
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+import { Color, Material } from "cesium";
+import { reactive, toRaw } from "vue";
+import PlotDraw from "../plot";
+import type { BaseArrowI } from "../plot/interface";
+
+const props = defineProps({
+  draw: {
+    type: PlotDraw,
+    required: true,
+  },
+});
+let arrow = toRaw(props.draw.nowObj as BaseArrowI);
+
+// 线样式修改相关配置
+const arrowConfig = reactive([
+  {
+    name: "速度",
+    value: 2,
+    oninput: "value=value.replace(/[^0-9.]/g,'')",
+  },
+  {
+    name: "颜色",
+    value: "#ffff00",
+    oninput: "value=value.replace(/^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/g,'')",
+  },
+]);
+
+function arrowMaterialClick() {
+  console.log(arrow.arrowPrimitive);
+  const arrowFabric = {
+    type: "ArrowFabric",
+    uniforms: {
+      color: Color.fromCssColorString(arrowConfig[1].value as string),
+      speed: eval(arrowConfig[0].value as string),
+    },
+    source: `czm_material czm_getMaterial(czm_materialInput materialInput) {
+            czm_material material = czm_getDefaultMaterial(materialInput);
+            vec2 st = materialInput.st;
+            float xx = fract(st.s * speed - czm_frameNumber/60.0);
+            float r = xx;
+            float g = sin(czm_frameNumber / 30.0);
+            float b = cos(czm_frameNumber / 30.0);
+            vec3 fragColor;
+            fragColor.rgb = color.rgb / 1.0;
+            fragColor = czm_gammaCorrect(fragColor); // 伽马校正
+            material.alpha = xx;
+            material.diffuse = vec3(r,g,b) / 2.0;
+            material.emission = fragColor.rgb;
+            return material;
+            }`,
+  };
+  arrow.arrowPrimitive.appearance.material = new Material({
+    fabric: arrowFabric,
+  });
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 268 - 0
src/views/map3d/plotTools/DrawTool.vue

@@ -0,0 +1,268 @@
+<template>
+  <div class="drawTool">
+    <el-tabs @tab-click="tabClick" style="height: 100%" type="border-card">
+      <el-tab-pane label="标绘工具">
+        <el-space :fill="true" wrap>
+          <el-row>
+            <el-switch
+              v-model="isModified"
+              inline-prompt
+              style="
+                --el-switch-on-color: #13ce66;
+                --el-switch-off-color: #ff4949;
+              "
+              active-text="开始编辑"
+              inactive-text="关闭编辑"
+            ></el-switch>
+            <el-button
+              style="margin-left: 10px"
+              type="danger"
+              :icon="Delete"
+              @click="deleteObj"
+              :disabled="deleteBool"
+              circle
+            />
+          </el-row>
+          <el-card
+            style="max-height: 150px; overflow: auto"
+            v-for="item in cardArrays"
+            :key="item.id"
+          >
+            <template #header>
+              <div class="card-header">
+                <span>{{ item.name }}</span>
+              </div>
+            </template>
+            <el-space wrap>
+              <el-button
+                v-for="plot in item.children"
+                :key="plot.activeName"
+                @click="plotDraw(plot.activeName)"
+                text
+              >
+                {{ plot.name }}
+              </el-button>
+            </el-space>
+          </el-card>
+        </el-space></el-tab-pane
+      >
+      <el-tab-pane label="样式修改">
+        <div v-if="showTool == 'none'">请选择一个要素</div>
+        <PointMaterial :draw="draw" v-if="showTool == 'point'" />
+        <LineMaterial :draw="draw" v-if="showTool == 'line'" />
+        <AreaMaterial :draw="draw" v-if="showTool == 'area'" />
+        <ArrowMaterial :draw="draw" v-if="showTool == 'arrow'" />
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, onUnmounted, ref, watch } from "vue";
+import PointMaterial from "./PointMaterial.vue";
+import LineMaterial from "./LineMaterial.vue";
+import AreaMaterial from "./AreaMaterial.vue";
+import ArrowMaterial from "./ArrowMaterial.vue";
+import PlotDraw from "../plot";
+import emitter from "@/mitt";
+import { Delete } from "@element-plus/icons-vue";
+import type { TabsPaneContext } from "element-plus/es/tokens/tabs";
+
+const cardArrays = [
+  {
+    name: "点标绘",
+    id: 1,
+    children: [
+      {
+        name: "点",
+        activeName: "marker",
+      },
+    ],
+  },
+  {
+    name: "线标绘",
+    id: 2,
+    children: [
+      {
+        name: "弧线",
+        activeName: "arc",
+      },
+      {
+        name: "曲线",
+        activeName: "curve",
+      },
+      {
+        name: "折线",
+        activeName: "polyline",
+      },
+      {
+        name: "自由线",
+        activeName: "freeHandPolyline",
+      },
+    ],
+  },
+  {
+    name: "面标绘",
+    id: 3,
+    children: [
+      {
+        name: "圆",
+        activeName: "circle",
+      },
+      {
+        name: "椭圆",
+        activeName: "ellipse",
+      },
+      {
+        name: "弓形",
+        activeName: "lune",
+      },
+      {
+        name: "扇形",
+        activeName: "sector",
+      },
+      {
+        name: "矩形",
+        activeName: "rectangle",
+      },
+      {
+        name: "曲线面",
+        activeName: "closedCurve",
+      },
+      {
+        name: "多边形",
+        activeName: "polygon",
+      },
+      {
+        name: "自由面",
+        activeName: "freeHandPolygon",
+      },
+      {
+        name: "聚集地",
+        activeName: "gatheringPlace",
+      },
+    ],
+  },
+  {
+    name: "箭头标绘",
+    id: 4,
+    children: [
+      {
+        name: "钳击",
+        activeName: "doubleArrow",
+      },
+      {
+        name: "直箭头",
+        activeName: "straightArrow",
+      },
+      {
+        name: "细直箭头",
+        activeName: "fineArrow",
+      },
+      {
+        name: "突击方向",
+        activeName: "assaultDirection",
+      },
+      {
+        name: "进攻方向",
+        activeName: "attackArrow",
+      },
+      {
+        name: "进攻方向(尾)",
+        activeName: "tailedAttackArrow",
+      },
+      {
+        name: "分队战斗行动",
+        activeName: "squadCombat",
+      },
+      {
+        name: "分队战斗行动(尾)",
+        activeName: "tailedSquadCombat",
+      },
+    ],
+  },
+];
+
+const isModified = ref(false);
+
+let draw: PlotDraw;
+
+onMounted(() => {
+  draw = new PlotDraw();
+});
+
+watch(isModified, (newValue) => {
+  if (newValue) {
+    draw?.startModified();
+  } else {
+    draw?.endModify();
+  }
+});
+
+let showTool = ref("none");
+let deleteBool = ref(true);
+emitter.on("seletedOne", changeToolVisible);
+function changeToolVisible() {
+  deleteBool.value = false;
+  showTool.value = draw?.nowObj?.baseType as string;
+}
+function deleteObj() {
+  draw?.clearOne();
+  deleteBool.value = true;
+}
+
+function plotDraw(name: string) {
+  draw?.draw(name);
+}
+
+// * 标签页点击事件
+function tabClick(pane: TabsPaneContext) {
+  showTool.value = "none";
+  deleteBool.value = false;
+  switch (pane.props.label) {
+    case "样式修改":
+      draw?.seletedOne();
+      break;
+    default:
+      draw.nowObj = null;
+      if (draw.handler) {
+        draw.endModify();
+      }
+      break;
+  }
+}
+
+onUnmounted(() => {
+  emitter.off("seletedOne", changeToolVisible);
+});
+</script>
+
+<style lang="scss" scoped>
+.drawTool {
+  position: fixed;
+  top: 20%;
+  right: 0;
+  z-index: 2000;
+  height: 75%;
+  width: 20%;
+  background: #131e30;
+
+  ::-webkit-scrollbar {
+    /*滚动条整体样式*/
+    width: 2px; /*高宽分别对应横竖滚动条的尺寸*/
+    height: 1px;
+  }
+  ::-webkit-scrollbar-thumb {
+    /*滚动条里面小方块*/
+    border-radius: 10px;
+    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
+    background: #535353;
+  }
+  ::-webkit-scrollbar-track {
+    /*滚动条里面轨道*/
+    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
+    border-radius: 10px;
+    background: #ededed;
+  }
+}
+</style>

+ 88 - 0
src/views/map3d/plotTools/LineMaterial.vue

@@ -0,0 +1,88 @@
+<template>
+  <el-card style="margin-top: 10px; overflow: auto">
+    <el-input
+      style="margin-top: 10px"
+      v-for="(item, index) in lineConfig"
+      :key="index"
+      v-model="item.value"
+      :oninput="item.oninput"
+    >
+      <template #prepend>
+        {{ item.name }}
+      </template>
+    </el-input>
+    <el-button
+      type="primary"
+      style="margin-top: 5px; margin-left: 80%"
+      @click="lineMaterialClick"
+      >确定</el-button
+    >
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+import { Color, Material } from "cesium";
+import { reactive, toRaw } from "vue";
+import PlotDraw from "../plot";
+import type { BaseLineI } from "../plot/interface";
+
+const props = defineProps({
+  draw: {
+    type: PlotDraw,
+    required: true,
+  },
+});
+let line = toRaw(props.draw.nowObj as BaseLineI);
+
+// 线样式修改相关配置
+const lineConfig = reactive([
+  {
+    name: "宽度",
+    value: 2,
+    oninput: "value=value.replace(/[^0-9.]/g,'')",
+  },
+  {
+    name: "速度",
+    value: 2,
+    oninput: "value=value.replace(/[^0-9.]/g,'')",
+  },
+  {
+    name: "颜色",
+    value: "#ffff00",
+    oninput: "value=value.replace(/^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/g,'')",
+  },
+]);
+
+function lineMaterialClick() {
+  console.log(line.linePrimitive);
+  line.linePrimitive._primitiveOptions.geometryInstances[0].geometry.width =
+    eval(lineConfig[0].value as string);
+  const polylineFabric = {
+    type: "PolylineFabric",
+    uniforms: {
+      color: Color.fromCssColorString(lineConfig[2].value as string),
+      speed: eval(lineConfig[1].value as string),
+    },
+    source: `czm_material czm_getMaterial(czm_materialInput materialInput) {
+        czm_material material = czm_getDefaultMaterial(materialInput);
+        vec2 st = materialInput.st;
+        float xx = fract(st.s * speed - czm_frameNumber/60.0);
+        float r = xx;
+        float g = sin(czm_frameNumber / 30.0);
+        float b = cos(czm_frameNumber / 30.0);
+        vec3 fragColor;
+        fragColor.rgb = color.rgb / 1.0;
+        fragColor = czm_gammaCorrect(fragColor); // 伽马校正
+        material.alpha = xx;
+        material.diffuse = vec3(r,g,b) / 2.0;
+        material.emission = fragColor.rgb;
+        return material;
+        }`,
+  };
+  line.linePrimitive.appearance.material = new Material({
+    fabric: polylineFabric,
+  });
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 317 - 0
src/views/map3d/plotTools/PointMaterial.vue

@@ -0,0 +1,317 @@
+<template>
+  <el-card style="margin-top: 10px; overflow: auto">
+    <el-input
+      v-model="pointModel.modelUrl"
+      placeholder="请输入相应模型/图片地址"
+    >
+      <template #prepend>
+        <el-select v-model="pointModel.modelName" style="width: 80px">
+          <el-option label="模型" value="model" />
+          <el-option label="图片" value="image" />
+        </el-select>
+      </template>
+    </el-input>
+    <el-button
+      type="primary"
+      style="margin-top: 5px; margin-left: 80%"
+      @click="modelClick"
+      >确定</el-button
+    >
+  </el-card>
+  <el-card style="margin-top: 10px; overflow: auto; max-height: 500px">
+    <template #header>
+      <div class="card-header">
+        <span>粒子系统</span>
+      </div>
+    </template>
+    <el-select
+      v-model="pointParticle.particleType"
+      placeholder="Select"
+      style="width: 100%"
+      @change="particleChange"
+    >
+      <el-option label="盒发射器" value="BoxEmitter" />
+      <el-option label="圆形发射器" value="CircleEmitter" />
+      <el-option label="圆锥发射器" value="ConeEmitter" />
+      <el-option label="球发射器" value="SphereEmitter" />
+    </el-select>
+    <el-input
+      style="margin-top: 10px"
+      v-for="(item, index) in pointParticle.particleInput"
+      :key="index"
+      v-model="item.value"
+      :placeholder="item.title"
+      :oninput="item.oninput"
+    >
+      <template #prepend>
+        {{ item.name }}
+      </template>
+    </el-input>
+    <el-button
+      type="primary"
+      @click="particleClick"
+      style="margin-top: 5px; margin-left: 80%"
+      >确定</el-button
+    >
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+import {
+  Billboard,
+  BoxEmitter,
+  Cartesian2,
+  Cartesian3,
+  CircleEmitter,
+  Color,
+  ConeEmitter,
+  HeadingPitchRoll,
+  Matrix4,
+  Model,
+  ParticleSystem,
+  SphereEmitter,
+  Transforms,
+  Math as cesiumMath,
+} from "cesium";
+import { reactive } from "vue";
+import PlotDraw from "../plot";
+import type { Marker } from "../plot/graphicsDraw/pointDraw";
+
+const props = defineProps({
+  draw: {
+    type: PlotDraw,
+    required: true,
+  },
+});
+
+let point = props.draw.nowObj as Marker;
+
+let pointModel = reactive({
+  modelUrl: "",
+  modelName: "image",
+});
+function modelClick() {
+  if (pointModel.modelName == "image") {
+    if (point.pointPrimitive instanceof Billboard) {
+      point.pointPrimitive.image = pointModel.modelUrl;
+    } else {
+      const position = Matrix4.getTranslation(
+        point.pointPrimitive.modelMatrix,
+        new Cartesian3()
+      );
+      window.Viewer.scene.primitives.remove(point.pointPrimitive);
+      point.pointPrimitive = point.showPrimitiveOnMap(position);
+    }
+  } else {
+    if (
+      point.pointPrimitive instanceof Model ||
+      point.pointPrimitive instanceof ParticleSystem
+    ) {
+      window.Viewer.scene.primitives.remove(point.pointPrimitive);
+      point.pointPrimitive = point.showPrimitiveModelOnMap(
+        pointModel.modelUrl,
+        point.pointPrimitive.modelMatrix
+      );
+    } else {
+      const position = point.pointPrimitive.position;
+      const modelMatrix = Transforms.headingPitchRollToFixedFrame(
+        position,
+        new HeadingPitchRoll(0, 0, 0)
+      );
+      window.Viewer.billboards.remove(point.pointPrimitive);
+      point.pointPrimitive = point.showPrimitiveModelOnMap(
+        pointModel.modelUrl,
+        modelMatrix
+      );
+    }
+  }
+}
+
+let pointParticle = reactive({
+  particleType: "BoxEmitter",
+  particleInput: [
+    {
+      value: 1.0,
+      name: "初始比例",
+      realName: "startScale",
+      title: "请输入粒子初始时比例",
+      oninput: "value=value.replace(/[^0-9.]/g,'')",
+    },
+    {
+      value: 5.0,
+      name: "消失比例",
+      realName: "endScale",
+      title: "请输入粒子消失时比例",
+      oninput: "value=value.replace(/[^0-9.]/g,'')",
+    },
+    {
+      value: "#ffff00",
+      name: "粒子颜色",
+      realName: "startColor",
+      title: "请输入粒子初始时颜色",
+      oninput: "value=value.replace(/^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/g,'')",
+    },
+    {
+      value: "/src/assets/icon/smoke.png",
+      name: "图片地址",
+      realName: "image",
+      title: "请输入粒子对应图片地址",
+      oninput: "value=value.replace(/*/g,'')",
+    },
+    {
+      value: 3.0,
+      name: "图片尺寸",
+      realName: "image",
+      title: "请输入粒子对应图片尺寸",
+      oninput: "value=value.replace(/[^0-9.]/g,'')",
+    },
+    {
+      value: 1.0,
+      name: "最小速度",
+      realName: "minimumSpeed",
+      title: "请输入粒子最小速度",
+      oninput: "value=value.replace(/[^0-9.]/g,'')",
+    },
+    {
+      value: 3.0,
+      name: "最大速度",
+      realName: "maximumSpeed",
+      title: "请输入粒子最大速度",
+      oninput: "value=value.replace(/[^0-9.]/g,'')",
+    },
+    {
+      value: 10.0,
+      name: "每秒粒子数",
+      realName: "emissionRate",
+      title: "请输入每秒发射的粒子数",
+      oninput: "value=value.replace(/[^0-9.]/g,'')",
+    },
+    {
+      value: 1.0,
+      name: "最小存活时间",
+      realName: "minimumParticleLife",
+      title: "请输入粒子最小存活时间",
+      oninput: "value=value.replace(/[^0-9.]/g,'')",
+    },
+    {
+      value: 5.0,
+      name: "最大存活时间",
+      realName: "maximumParticleLife",
+      title: "请输入粒子最大存活时间",
+      oninput: "value=value.replace(/[^0-9.]/g,'')",
+    },
+  ],
+});
+function particleClick() {
+  let emitterParticle;
+  switch (pointParticle.particleType) {
+    case "BoxEmitter":
+      emitterParticle = new BoxEmitter(new Cartesian3(10.0, 10.0, 10.0));
+      break;
+    case "CircleEmitter":
+      emitterParticle = new CircleEmitter(2.0);
+      break;
+    case "ConeEmitter":
+      emitterParticle = new ConeEmitter(cesiumMath.toRadians(45.0));
+      break;
+    case "SphereEmitter":
+      emitterParticle = new SphereEmitter(2.5);
+      break;
+    default:
+      emitterParticle = new CircleEmitter(2.0);
+      break;
+  }
+  let modelMatrix;
+  if (point.pointPrimitive instanceof Billboard) {
+    modelMatrix = Transforms.headingPitchRollToFixedFrame(
+      point.pointPrimitive.position,
+      new HeadingPitchRoll(0, 0, 0)
+    );
+    window.Viewer.billboards.remove(point.pointPrimitive);
+  } else if (point.pointPrimitive instanceof Model) {
+    modelMatrix = point.pointPrimitive.modelMatrix;
+    window.Viewer.scene.primitives.remove(point.pointPrimitive);
+  } else {
+    modelMatrix = point.pointPrimitive.modelMatrix;
+  }
+  const gravityScratch = new Cartesian3();
+
+  if (!(point.pointPrimitive instanceof ParticleSystem)) {
+    point.pointPrimitive = window.Viewer.scene.primitives.add(
+      new ParticleSystem({
+        lifetime: 16.0,
+        updateCallback: function (p) {
+          const position = p.position;
+
+          Cartesian3.normalize(position, gravityScratch);
+          Cartesian3.multiplyByScalar(gravityScratch, 0, gravityScratch);
+
+          p.velocity = Cartesian3.add(p.velocity, gravityScratch, p.velocity);
+        },
+        emitterModelMatrix: modelMatrix,
+      })
+    );
+  }
+  point.pointPrimitive.emitter = emitterParticle;
+  point.pointPrimitive.image = pointParticle.particleInput[3].value;
+  point.pointPrimitive.startColor = Color.fromCssColorString(
+    pointParticle.particleInput[2].value as string
+  ).withAlpha(0.7);
+  point.pointPrimitive.endColor = Color.WHITE.withAlpha(0.0);
+  point.pointPrimitive.startScale = eval(
+    pointParticle.particleInput[0].value as string
+  ) as number;
+  point.pointPrimitive.endScale = eval(
+    pointParticle.particleInput[1].value as string
+  ) as number;
+  point.pointPrimitive.minimumParticleLife = eval(
+    pointParticle.particleInput[8].value as string
+  ) as number;
+  point.pointPrimitive.maximumParticleLife = eval(
+    pointParticle.particleInput[9].value as string
+  ) as number;
+  point.pointPrimitive.minimumSpeed = eval(
+    pointParticle.particleInput[5].value as string
+  ) as number;
+  point.pointPrimitive.maximumSpeed = eval(
+    pointParticle.particleInput[6].value as string
+  ) as number;
+  point.pointPrimitive.imageSize = new Cartesian2(
+    eval(pointParticle.particleInput[4].value as string) as number,
+    eval(pointParticle.particleInput[4].value as string) as number
+  );
+  point.pointPrimitive.emissionRate = eval(
+    pointParticle.particleInput[7].value as string
+  ) as number;
+}
+
+function particleChange(val: string) {
+  console.log(val);
+
+  if (point.pointPrimitive instanceof ParticleSystem) {
+    switch (val) {
+      case "BoxEmitter":
+        point.pointPrimitive.emitter = new BoxEmitter(
+          new Cartesian3(10.0, 10.0, 10.0)
+        );
+        break;
+      case "CircleEmitter":
+        point.pointPrimitive.emitter = new CircleEmitter(2.0);
+        break;
+      case "ConeEmitter":
+        point.pointPrimitive.emitter = new ConeEmitter(
+          cesiumMath.toRadians(45.0)
+        );
+        break;
+      case "SphereEmitter":
+        point.pointPrimitive.emitter = new SphereEmitter(2.5);
+        break;
+      default:
+        point.pointPrimitive.emitter = new CircleEmitter(2.0);
+        break;
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped></style>