From dae8d5ad8df88593261f6df3697fbfe2cab3047b Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Wed, 11 Jun 2025 17:13:00 +0800
Subject: [PATCH] feat:后台管理通用航线加载相关方法、hook、航线材质等处理

---
 src/hooks/useRouteLine/useRouteLine.js |  225 +++++++++++++++
 src/utils/cesium/Material/index.js     |  442 +++++++++++++++++++++++++++++
 src/utils/util.js                      |   26 +
 src/hooks/common.js                    |  172 +++++++++++
 4 files changed, 862 insertions(+), 3 deletions(-)

diff --git a/src/hooks/common.js b/src/hooks/common.js
new file mode 100644
index 0000000..aeb93be
--- /dev/null
+++ b/src/hooks/common.js
@@ -0,0 +1,172 @@
+import * as Cesium from 'cesium'
+
+import { analyzeKmzFile, removeTextKey, XMLToJSON } from '@/utils/cesium/kmz'
+import { camelToSnake } from '@/utils/util'
+import _, { cloneDeep, throttle } from 'lodash'
+import * as turf from '@turf/turf'
+
+/**
+ * 判断action里面是否有key
+ * @param action
+ * @param key
+ * @returns {{hasKey: boolean, val: *}|{hasKey: boolean, val: null}}
+ */
+export function actionHasKey (action, key) {
+	const curActionParams = action?.action_actuator_func_param
+	const val = curActionParams?.[key]
+	if (val !== undefined) {
+		return { hasKey: true, val }
+	} else {
+		return { hasKey: false, val: null }
+	}
+}
+
+// 解析kmz文件的时候处理pointList 写在这里
+export function handlePointListForKmz ({ placemark, startPoint, execute_height_mode, auto_flight_speed }) {
+	if (!placemark?.length) return []
+	const isWGS84 = execute_height_mode === 'WGS84'
+	const list = placemark.map(item => {
+		const [longitude, latitude] = item.point.coordinates.trim().split(',')
+		let action_modes = item?.action_group?.action || []
+		action_modes = Array.isArray(action_modes) ? action_modes : [action_modes]
+		return {
+			longitude: _.round(longitude, 8),
+			latitude: _.round(latitude, 8),
+			height: isWGS84 ? item.execute_height : item.execute_height + startPoint.height,
+			waypoint_speed: item.waypoint_speed,
+			action_modes,
+		}
+	})
+	const pointStart = {
+		...startPoint,
+		flyRotateYaw: 0,
+		pointType: 'start',
+		waypoint_speed: auto_flight_speed,
+		heading: 0, // 椎体 heading
+		arrowHeading: 0, // 飞行器 heading
+		pitch: 0, //椎体俯仰角
+		fov: 19.5, // 这个是椎体的展示截面大小
+	}
+	list.forEach(item => {
+		item?.action_modes?.forEach(item1 => {
+			let { hasKey: has_focal_length, val: focal_length } = actionHasKey(item1, 'focal_length')
+			// 处理focalLength需要/24  focalLength可能是‘1,024’
+			if (has_focal_length && focal_length) {
+				focal_length = typeof focal_length === 'string' ? focal_length.replace(/,/g, '') : focal_length
+				item1.action_actuator_func_param.focal_length = Number(focal_length) / 24
+			}
+		})
+		item.arrowHeading = 0
+	})
+	return [pointStart, ...list]
+}
+
+export async function analysisPointLineKmz (kmzUrl) {
+	const res = await analyzeKmzFile(`${kmzUrl}?_t=${new Date().getTime()}`)
+	const templateXML = await res.fileInfoObj['wpmz/template.kml']
+	const waylinesXML = await res.fileInfoObj['wpmz/waylines.wpml']
+	const templateXMLJSON = XMLToJSON(templateXML)?.['Document']
+	const waylinesXMLJSON = XMLToJSON(waylinesXML)?.['Document']
+	const templateXMLObj = camelToSnake(removeTextKey(templateXMLJSON))
+	const waylinesXMLObj = camelToSnake(removeTextKey(waylinesXMLJSON))
+	const {
+		mission_config: {
+			take_off_ref_point,
+			drone_info: { drone_enum_value, drone_sub_enum_value } = {}
+		} = {},
+		folder: {
+			payload_param: { image_format } = {},
+			placemark: {
+				polygon
+			},
+			global_height, auto_flight_speed,
+			template_type: templateType
+		} = {},
+	} = templateXMLObj
+
+	const {
+		mission_config: { take_off_security_height } = {},
+		folder: polygonPointFolder
+	} = waylinesXMLObj
+
+	let execute_height_mode = '', pointPlacemark
+
+	if (templateType === "mapping3d") {
+		execute_height_mode = polygonPointFolder[0].execute_height_mode
+		pointPlacemark = polygonPointFolder.map(i => i.placemark)
+	} else {
+		execute_height_mode = polygonPointFolder.execute_height_mode
+		pointPlacemark = polygonPointFolder.placemark
+	}
+
+	const [latitude, longitude, height] = take_off_ref_point.split(',').map(item => _.round(item, 6))
+	let startPoint = { latitude, longitude, height }
+
+	// 取出点位
+	const coordinates = polygon?.outer_boundary_is.linear_ring
+		.coordinates?.split('\n') || []
+
+	// 数组转换
+	let polygonList = coordinates.map((coordinate) =>
+		coordinate
+			.replace(/\s+/g, '')
+			.split(',')
+			.map((v) => Number(v)),
+	)
+
+	polygonList.pop()
+
+	return {
+		image_format,
+		startPoint,
+		global_height,
+		auto_flight_speed,
+		take_off_ref_point,
+		drone_enum_value,
+		drone_sub_enum_value,
+		take_off_security_height,
+		execute_height_mode,
+		templateType,
+		pointPlacemark,
+		polygonList
+	}
+}
+
+// 获取连接地面线
+export function getPolyLine (viewer, pointList, index) {
+	return {
+		positions: new Cesium.CallbackProperty(() => {
+			const point = pointList.value[index]
+			const pointPosition = Cesium.Cartesian3.fromDegrees(point.longitude, point.latitude, point.height)
+			if (!pointPosition) return []
+			// 获取点的位置
+			const cartographic = Cesium.Cartographic.fromCartesian(pointPosition)
+			// 获取地形高度
+			const terrainHeight = viewer.scene.globe.getHeight(cartographic) || 0
+			// 创建地面点位置
+			const groundPosition = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, terrainHeight)
+
+			return [pointPosition, groundPosition]
+		}, false),
+		width: 0.5,
+		material: Cesium.Color.WHITE,
+	}
+}
+
+export function getNewPolygonData (data) {
+	const polygonData = data.reduce((pre, cur) => {
+		let arr = cur.data.map(i => {
+			if ('point' in i) return i.point.coordinates.split(',')
+			if ('Point' in i) {
+				if (_.isObject(i.Point.coordinates) && '#text' in i.Point.coordinates) return i.Point.coordinates['#text'].split(',')
+				return i.Point.coordinates.split(',')
+			}
+		}).map(i => turf.point([Number(i[0]), Number(i[1])]))
+
+		return pre.push(...arr), pre
+	}, [])
+
+	const hull = turf.convex(turf.featureCollection(polygonData))
+
+	return hull.geometry.coordinates[0]
+}
\ No newline at end of file
diff --git a/src/hooks/useRouteLine/useRouteLine.js b/src/hooks/useRouteLine/useRouteLine.js
new file mode 100644
index 0000000..3397ee7
--- /dev/null
+++ b/src/hooks/useRouteLine/useRouteLine.js
@@ -0,0 +1,225 @@
+import * as Cesium from 'cesium'
+
+import {
+	analysisPointLineKmz,
+	handlePointListForKmz,
+	getPolyLine,
+	getNewPolygonData,
+} from '@/hooks/common'
+
+import rwqfdImg from '@/assets/images/signMachineNest/rwqfd.png'
+import endPointImg from '@/assets/images/EndPointicon.png'
+
+import { ArrowLineMaterialProperty } from '@/utils/cesium/Material'
+import { flyVisual } from '@/utils/cesium/mapUtil'
+
+let arrowLineMaterialProperty = new ArrowLineMaterialProperty({
+	color: new Cesium.Color(128 / 255, 215 / 255, 255 / 255, 1),
+	directionColor: new Cesium.Color(1, 1, 1, 1),
+	outlineColor: new Cesium.Color(1, 1, 1, 1),
+	outlineWidth: 0,
+	speed: 5,
+})
+
+export function useRouteLine () {
+	let viewer = null
+	let previewDataSource = null
+
+	const curRouteLineData = ref({
+		data: [],
+		polygonList: '',
+		templateType: '',
+		startPoint: '',
+		execute_height_mode: '',
+		auto_flight_speed: '',
+		wayline_type: '',
+	})
+
+	const routeLineListClick = async data => {
+		if (data.select) return
+		curRouteLineData.value.data.forEach(i => (i.select = false))
+		data.select = true
+		await renderRouteLine(data.data)
+	}
+
+	async function renderPreviewLine (kmzUrl, wayline_type, cb = () => { }) {
+		resetCurRouteLineData()
+
+		const { pointPlacemark, polygonList, templateType, startPoint, execute_height_mode, auto_flight_speed } =
+			await analysisPointLineKmz(kmzUrl)
+
+		curRouteLineData.value = {
+			polygonList,
+			templateType,
+			startPoint,
+			execute_height_mode,
+			auto_flight_speed,
+			wayline_type,
+		}
+
+		cb(curRouteLineData.value.polygonList.map(item => [Number(item[0]), Number(item[1])]))
+
+		if (templateType === 'mapping3d') {
+			curRouteLineData.value.data = pointPlacemark.map(item => ({
+				data: item,
+				select: false,
+			}))
+			routeLineListClick(curRouteLineData.value.data[0])
+		} else {
+			curRouteLineData.value.data = []
+			await renderRouteLine(pointPlacemark)
+		}
+	}
+
+	async function renderRouteLine (data) {
+		if (previewDataSource) {
+			removePreviewLine()
+		} else {
+			previewDataSource = new Cesium.CustomDataSource('previewDataSource')
+			await viewer.dataSources.add(previewDataSource)
+		}
+
+		let lineMaterial = [2, 4].includes(Number(curRouteLineData.value.wayline_type))
+			? Cesium.Color.fromCssColorString('#00D690')
+			: arrowLineMaterialProperty
+
+		if ([2, 4].includes(Number(curRouteLineData.value.wayline_type))) {
+			if (curRouteLineData.value.templateType === 'mapping3d') {
+				let newPolygonData = getNewPolygonData(curRouteLineData.value.data)
+
+				let newPolygonDisposePositions = newPolygonData.map(i =>
+					Cesium.Cartesian3.fromDegrees(Number(i[0]), Number(i[1]))
+				)
+
+				previewDataSource.entities.add({
+					polygon: {
+						hierarchy: new Cesium.CallbackProperty(() => {
+							return new Cesium.PolygonHierarchy(newPolygonDisposePositions)
+						}, false),
+						material: Cesium.Color.fromBytes(255, 228, 22, 44),
+						outline: false,
+						outlineWidth: 2,
+						heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+					},
+				})
+			}
+
+			let polygonDisposePositions = curRouteLineData.value.polygonList.map(item => {
+				return Cesium.Cartesian3.fromDegrees(Number(item[0]), Number(item[1]), Number(item[2]))
+			})
+
+			previewDataSource.entities.add({
+				polygon: {
+					hierarchy: new Cesium.CallbackProperty(() => {
+						return new Cesium.PolygonHierarchy(polygonDisposePositions)
+					}, false),
+					material: Cesium.Color.fromBytes(45, 140, 240, 99),
+					outline: false,
+					outlineWidth: 2,
+					heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+				},
+
+				polyline: {
+					width: 2,
+					material: Cesium.Color.fromBytes(45, 140, 240, 255),
+					clampToGround: true,
+					positions: new Cesium.CallbackProperty(() => {
+						return [...polygonDisposePositions, polygonDisposePositions[0]]
+					}, false),
+				},
+			})
+		}
+
+
+
+		let pointList = handlePointListForKmz({
+			placemark: data,
+			startPoint: curRouteLineData.value.startPoint,
+			execute_height_mode: curRouteLineData.value.execute_height_mode,
+			auto_flight_speed: curRouteLineData.value.auto_flight_speed,
+		})
+
+		pointList.shift()
+
+		const routePositions = pointList.map(i => Cesium.Cartesian3.fromDegrees(Number(i.longitude), Number(i.latitude), Number(i.height)))
+
+		previewDataSource.entities.add({
+			polyline: {
+				width: 4,
+				positions: routePositions,
+				material: lineMaterial,
+				clampToGround: false,
+			},
+		})
+
+			// 落点线
+			; (pointList || [])?.forEach((item, index) => {
+				const topPosition = Cesium.Cartesian3.fromDegrees(Number(item.longitude), Number(item.latitude), Number(item.height))
+				let setting = {
+					position: topPosition,
+					polyline: getPolyLine(viewer, { value: pointList }, index),
+				}
+				previewDataSource.entities.add(setting)
+			})
+
+		// 终点
+		previewDataSource.entities.add({
+			position: routePositions[routePositions.length - 1],
+			billboard: {
+				image: new Cesium.ConstantProperty(endPointImg),
+				width: 30,
+				height: 30,
+				verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 底部对齐
+			},
+		})
+		// 起点
+		previewDataSource.entities.add({
+			position: routePositions[0],
+			billboard: {
+				image: rwqfdImg,
+				width: 50,
+				height: 50,
+			},
+		})
+
+		flyVisual(
+			pointList.map(i => [Number(i.longitude), Number(i.latitude), Number(i.height)]),
+			viewer,
+			20
+		)
+	}
+
+	function removePreviewLine () {
+		previewDataSource?.entities.removeAll()
+	}
+
+	function resetCurRouteLineData () {
+		curRouteLineData.value = {
+			data: [],
+			polygonList: '',
+			templateType: '',
+			startPoint: '',
+			execute_height_mode: '',
+			auto_flight_speed: '',
+			wayline_type: '',
+		}
+	}
+
+	const initViewer = (v) => {
+		viewer = v
+	}
+
+	onBeforeUnmount(() => {
+		resetCurRouteLineData()
+		removePreviewLine()
+	})
+
+	return {
+		curRouteLineData,
+		routeLineListClick,
+		initViewer,
+		renderPreviewLine,
+		removePreviewLine,
+		resetCurRouteLineData
+	}
+}
\ No newline at end of file
diff --git a/src/utils/cesium/Material/index.js b/src/utils/cesium/Material/index.js
new file mode 100644
index 0000000..f96f7a8
--- /dev/null
+++ b/src/utils/cesium/Material/index.js
@@ -0,0 +1,442 @@
+import * as Cesium from 'cesium'
+
+// 多边形渐变材质
+let pM = "PolyGradientMaterial"
+Cesium.Material._materialCache.addMaterial(pM, {
+  strict: true,
+  fabric: {
+    type: pM,
+    uniforms: {
+      color: new Cesium.Color(1.0, 1.0, 0.0, 0.5),
+      diffusePower: 1.6,
+      alphaPower: 1.5,
+      center: new Cesium.Cartesian2(0.5, 0.5),
+      isInner: false,
+      globalAlpha: 1.0,
+    },
+    source: `
+            uniform vec4 color;
+            uniform float diffusePower;
+            uniform float alphaPower;
+            uniform float globalAlpha;
+            uniform vec2 center;
+            uniform bool isInner;
+
+            czm_material czm_getMaterial(czm_materialInput materialInput) {
+                czm_material material = czm_getDefaultMaterial(materialInput);
+                vec2 st = materialInput.st;
+                float alphaMars3D = distance(st, center);
+
+                if(isInner) {
+                    material.alpha = (1.0 - (color.a * alphaMars3D * alphaPower)) * globalAlpha;
+                    if(material.alpha < 0.0) material.alpha = 0.0;
+                } else {
+                    material.alpha = color.a * alphaMars3D * alphaPower * globalAlpha;
+                }
+
+                material.diffuse = color.rgb * diffusePower;
+                return material;
+            }
+        `,
+  },
+  translucent: true,
+})
+class MaterialProperty {
+  constructor(options = {}) {
+    this._definitionChanged = new Cesium.Event()
+
+    this._color = undefined
+    this._glowPower = undefined
+    this._taperPower = undefined
+    this._directionColor = undefined
+    this._outlineColor = undefined
+    this._outlineWidth = undefined
+    this._opacity = undefined
+    this._alphaPower = undefined
+    this._speed = undefined
+
+    this.color = options.color || Cesium.Color.fromBytes(0, 255, 255, 255)
+    this.glowPower = .25
+    this.taperPower = 1
+    this.directionColor = options.directionColor || Cesium.Color.fromBytes(255, 255, 255, 255)
+    this.outlineColor = options.outlineColor || Cesium.Color.fromBytes(255, 255, 255, 255)
+    this.outlineWidth = 0
+    this.opacity = options.opacity || 0.5
+    this.alphaPower = options.alphaPower || 1.5
+    this.speed = options.speed || 5
+  }
+
+  get isConstant () {
+    return false
+  }
+
+  get definitionChanged () {
+    return this._definitionChanged
+  }
+
+  getType (time) {
+    return null
+  }
+
+  getValue (time, result) {
+    result = Cesium.defaultValue(result, {})
+    return result
+  }
+
+  equals (other) {
+    return this === other
+  }
+}
+class PolyGradientMaterial extends MaterialProperty {
+  constructor(options = {}) {
+    super(options)
+  }
+
+  getType (time) {
+    return pM
+  }
+
+  getValue (time, result) {
+    if (!result) {
+      result = {}
+    }
+
+    result.color = Cesium.Property.getValueOrUndefined(this._color, time)
+    result.globalAlpha = Cesium.Property.getValueOrUndefined(this._opacity, time)
+    result.alphaPower = Cesium.Property.getValueOrUndefined(this._alphaPower, time)
+    return result
+  }
+
+  equals (other) {
+    return (
+      this === other ||
+      (other instanceof PolyGradientMaterial &&
+        Cesium.Property.equals(this._color, other._color) &&
+        Cesium.Property.equals(this._opacity, other._opacity) &&
+        Cesium.Property.equals(this._alphaPower, other._alphaPower))
+    )
+  }
+}
+
+Object.defineProperties(PolyGradientMaterial.prototype, {
+  color: Cesium.createPropertyDescriptor('color'),
+  opacity: Cesium.createPropertyDescriptor('opacity'),
+  alphaPower: Cesium.createPropertyDescriptor('alphaPower'),
+})
+
+// 轨迹线材质
+let lT = "LineTrailMaterial"
+Cesium.Material._materialCache.addMaterial(lT, {
+  fabric: {
+    uniforms: {
+      color: new Cesium.Color(1.0, 1.0, 0.0, 0.7),
+      bgColor: new Cesium.Color(0.0, 1.0, 0.0, 0.0),
+      speed: 5,
+      globalAlpha: 1.0
+    },
+    source: `
+          uniform vec4 bgColor;
+          uniform vec4 color;
+          uniform float speed;
+          uniform float globalAlpha;
+
+          czm_material czm_getMaterial(czm_materialInput materialInput) {
+              czm_material material = czm_getDefaultMaterial(materialInput);
+              vec2 st = materialInput.st;
+              float time = fract(czm_frameNumber * speed / 1000.0);
+              vec3 colorMars3D = color.rgb;
+
+              if (st.t > 0.45 && st.t < 0.55) {
+                  colorMars3D = vec3(1.0);
+              }
+
+              material.alpha = color.a * 1.5 * smoothstep(0.0, 1.0, fract(st.s - time));
+              material.diffuse = max(colorMars3D.rgb * material.alpha, colorMars3D.rgb);
+
+              if (material.alpha < bgColor.a) {
+                  material.alpha = bgColor.a;
+                  material.diffuse = bgColor.rgb;
+              }
+
+              material.alpha = material.alpha * globalAlpha;
+              return material;
+          }
+      `
+  },
+  translucent: true
+})
+class LineTrailMaterial extends MaterialProperty {
+  constructor(options = {}) {
+    super(options)
+  }
+
+  getType (time) {
+    return lT
+  }
+
+  getValue (time, result) {
+    if (!result) {
+      result = {}
+    }
+
+    result.color = Cesium.Property.getValueOrUndefined(this._color, time)
+    result.globalAlpha = Cesium.Property.getValueOrUndefined(this._opacity, time)
+    result.speed = Cesium.Property.getValueOrUndefined(this._speed, time)
+    return result
+  }
+
+  equals (other) {
+    return (
+      this === other ||
+      (other instanceof LineTrailMaterial &&
+        Cesium.Property.equals(this._color, other._color) &&
+        Cesium.Property.equals(this._opacity, other._opacity) &&
+        Cesium.Property.equals(this._speed, other._speed))
+    )
+  }
+}
+
+Object.defineProperties(LineTrailMaterial.prototype, {
+  color: Cesium.createPropertyDescriptor('color'),
+  opacity: Cesium.createPropertyDescriptor('opacity'),
+  speed: Cesium.createPropertyDescriptor('speed'),
+})
+
+
+let arrowLineMaterialType = "ArrowLineMaterial"
+Cesium.Material._materialCache.addMaterial(arrowLineMaterialType, {
+  fabric: {
+    type: arrowLineMaterialType,
+    uniforms: {
+      color: new Cesium.Color(0, 1, 1, 1),
+      directionColor: new Cesium.Color(1, 1, 1, 1),
+      outlineColor: new Cesium.Color(1, 1, 1, 1),
+      outlineWidth: 0,
+      speed: 0,  // Added speed uniform with default 0 (no animation)
+    },
+    source: `
+#ifdef GL_OES_standard_derivatives
+#extension GL_OES_standard_derivatives : enable
+#endif
+
+uniform vec4 color;
+uniform vec4 directionColor;
+uniform vec4 outlineColor;
+uniform float outlineWidth;
+uniform float speed;      // Added speed uniform
+
+in float v_width;
+in float v_polylineAngle;
+
+const float fragLength = 100.0;
+const float startPosition = 0.45;
+const float endPosition = 0.55;
+
+mat2 rotate(float rad) {
+    float c = cos(rad);
+    float s = sin(rad);
+    return mat2(c, s, -s, c);
+}
+
+float getPointOnLine(vec2 p0, vec2 p1, float x) {
+    float slope = (p0.y - p1.y) / (p0.x - p1.x);
+    return slope * (x - p0.x) + p0.y;
+}
+
+czm_material czm_getMaterial(czm_materialInput materialInput) {
+    czm_material material = czm_getDefaultMaterial(materialInput);
+    vec2 st = materialInput.st;
+    float time = fract(czm_frameNumber * speed / 1000.0);
+
+    float halfInteriorWidth = 0.5 * (v_width - outlineWidth) / v_width;
+    float b = step(0.5 - halfInteriorWidth, st.t);
+    b *= 1.0 - step(0.5 + halfInteriorWidth, st.t);
+
+    float d1 = abs(st.t - (0.5 - halfInteriorWidth));
+    float d2 = abs(st.t - (0.5 + halfInteriorWidth));
+    float dist = min(d1, d2);
+
+    vec4 currentColor = mix(outlineColor, color, b);
+    vec4 outColor = czm_antialias(outlineColor, color, currentColor, dist);
+    outColor = czm_gammaCorrect(outColor);
+
+    vec2 pos = rotate(v_polylineAngle) * gl_FragCoord.xy;
+    
+    // Modified to include animation based on speed and time
+    float animationOffset = speed * time;
+    float maskS = fract((pos.x / (fragLength * czm_pixelRatio)) - animationOffset);
+    
+    float maskT = st.t;
+    bool isDirection = (maskS > startPosition) && (maskS <= endPosition);
+
+    vec4 fragColor;
+    if (isDirection) {
+        float arrowWidth = (endPosition - startPosition) / 2.0;
+        float midS = startPosition + arrowWidth;
+
+        float t = 1.0;
+        if (maskS < midS) {
+            vec2 center = vec2(midS, 0.5);
+            float ptOnUpperLine = getPointOnLine(vec2(startPosition, 1.0), center, maskS);
+            float ptOnLowerLine = getPointOnLine(vec2(startPosition, 0.0), center, maskS);
+
+            t *= 1.0 - step(ptOnUpperLine, maskT);
+            t *= step(ptOnLowerLine, maskT);
+            t = 1.0 - t;
+        } else {
+            vec2 center = vec2(endPosition, 0.5);
+            float ptOnUpperLine = getPointOnLine(vec2(midS, 1.0), center, maskS);
+            float ptOnLowerLine = getPointOnLine(vec2(midS, 0.0), center, maskS);
+
+            t *= 1.0 - step(ptOnUpperLine, maskT);
+            t *= step(ptOnLowerLine, maskT);
+        }
+
+        vec4 outsideColor = outColor;
+        vec4 currentColor = mix(outsideColor, directionColor, clamp(t, 0.0, 1.0));
+        fragColor = currentColor;
+    } else {
+        fragColor = outColor;
+    }
+
+    fragColor = czm_gammaCorrect(fragColor);
+    material.diffuse = fragColor.rgb;
+    material.alpha = fragColor.a;
+    return material;
+}`
+  },
+  translucent: true
+})
+
+class ArrowLineMaterialProperty extends MaterialProperty {
+  constructor(options = {}) {
+    super(options)
+  }
+
+  getType (time) {
+    return arrowLineMaterialType
+  }
+
+  getType (time) {
+    return arrowLineMaterialType
+  }
+
+  getValue (time, result) {
+    if (!result) {
+      result = {}
+    }
+
+    result.color = Cesium.Property.getValueOrUndefined(this._color, time)
+    result.directionColor = Cesium.Property.getValueOrUndefined(this._directionColor, time)
+    result.outlineColor = Cesium.Property.getValueOrUndefined(this._outlineColor, time)
+    result.outlineWidth = Cesium.Property.getValueOrUndefined(this._outlineWidth, time)
+    result.speed = Cesium.Property.getValueOrUndefined(this._speed, time)
+
+    return result
+  }
+
+  equals (other) {
+    return (
+      this === other ||
+      (other instanceof ArrowLineMaterialProperty &&
+        Cesium.Property.equals(this._color, other._color) &&
+        Cesium.Property.equals(this._directionColor, other._directionColor) &&
+        Cesium.Property.equals(this._outlineColor, other._outlineColor) &&
+        Cesium.Property.equals(this._outlineWidth, other._outlineWidth) &&
+        Cesium.Property.equals(this._speed, other._speed))
+    )
+  }
+}
+
+Object.defineProperties(ArrowLineMaterialProperty.prototype, {
+  color: Cesium.createPropertyDescriptor('color'),
+  directionColor: Cesium.createPropertyDescriptor('directionColor'),
+  outlineColor: Cesium.createPropertyDescriptor('outlineColor'),
+  outlineWidth: Cesium.createPropertyDescriptor('outlineWidth'),
+  speed: Cesium.createPropertyDescriptor('speed')
+})
+
+
+// 轨迹线材质
+let pLG = "PolylineGlow"
+Cesium.Material._materialCache.addMaterial(pLG, {
+  fabric: {
+    uniforms: {
+      color: new Cesium.Color(0, .5, 1, 1),
+      glowPower: .25,
+      taperPower: 1
+    },
+
+    source: ` uniform vec4 color;
+              uniform float glowPower;
+              uniform float taperPower;
+
+              czm_material czm_getMaterial(czm_materialInput materialInput)
+              {
+                  czm_material material = czm_getDefaultMaterial(materialInput);
+
+                  vec2 st = materialInput.st;
+                  float glow = glowPower / abs(st.t - 0.5) - (glowPower / 0.5);
+
+                  if (taperPower <= 0.99999) {
+                      glow *= min(1.0, taperPower / (0.5 - st.s * 0.5) - (taperPower / 0.5));
+                  }
+
+                  vec4 fragColor;
+                  fragColor.rgb = max(vec3(glow - 1.0 + color.rgb), color.rgb);
+                  fragColor.a = clamp(0.0, 1.0, glow) * color.a;
+                  fragColor = czm_gammaCorrect(fragColor);
+
+                  material.emission = fragColor.rgb;
+                  material.alpha = fragColor.a;
+
+                  return material;
+              }
+              `
+  },
+  translucent: true
+})
+class PolylineGlowMaterial extends MaterialProperty {
+  constructor(options = {}) {
+    super(options)
+  }
+
+  getType (time) {
+    return pLG
+  }
+
+  getValue (time, result) {
+    if (!result) {
+      result = {}
+    }
+
+    result.color = Cesium.Property.getValueOrUndefined(this._color, time)
+    result.glowPower = Cesium.Property.getValueOrUndefined(this._glowPower, time)
+    result.taperPower = Cesium.Property.getValueOrUndefined(this._taperPower, time)
+    return result
+  }
+
+  equals (other) {
+    return (
+      this === other ||
+      (other instanceof LineTrailMaterial &&
+        Cesium.Property.equals(this._color, other._color) &&
+        Cesium.Property.equals(this._glowPower, other._glowPower) &&
+        Cesium.Property.equals(this._taperPower, other._taperPower))
+    )
+  }
+}
+
+Object.defineProperties(PolylineGlowMaterial.prototype, {
+  color: Cesium.createPropertyDescriptor('color'),
+  glowPower: Cesium.createPropertyDescriptor('glowPower'),
+  taperPower: Cesium.createPropertyDescriptor('taperPower'),
+})
+
+
+export {
+  PolyGradientMaterial,
+  LineTrailMaterial,
+  ArrowLineMaterialProperty,
+  PolylineGlowMaterial
+}
\ No newline at end of file
diff --git a/src/utils/util.js b/src/utils/util.js
index 94137a6..fc0bc9e 100644
--- a/src/utils/util.js
+++ b/src/utils/util.js
@@ -125,9 +125,9 @@
  * @returns {string}
  */
 export const getSmallImg = url => {
-	if (!url) return ''
-	const lastDotIndex = url.lastIndexOf('.')
-	return `${url.substring(0, lastDotIndex)}_small${url.substring(lastDotIndex)}`
+  if (!url) return ''
+  const lastDotIndex = url.lastIndexOf('.')
+  return `${url.substring(0, lastDotIndex)}_small${url.substring(lastDotIndex)}`
 }
 /**
  * 浏览器判断是否全屏
@@ -438,3 +438,23 @@
   // 设置默认范围 [上个月, 当前月]
   return [new Date(lastMonthYear, lastMonth, 1), new Date(currentYear, currentMonth, 1)]
 }
+
+// key驼峰转换为下划线
+export function camelToSnake (obj) {
+  if (typeof obj !== 'object' || obj === null) {
+    return obj
+  }
+
+  if (Array.isArray(obj)) {
+    return obj.map(item => camelToSnake(item))
+  }
+
+  const newObj = {}
+  for (const key in obj) {
+    if (Object.prototype.hasOwnProperty.call(obj, key)) {
+      const newKey = key.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase()
+      newObj[newKey] = camelToSnake(obj[key])
+    }
+  }
+  return newObj
+}
\ No newline at end of file

--
Gitblit v1.9.3