网站建设合同的要素seo优化推广流程
这是一个基于 Cesium.js 的交互式绘图工具库,包含两个主要类:PositionTransfer(坐标转换工具)和 DrawTool(绘图工具)。
1. PositionTransfer 类
功能:处理不同坐标系统间的转换,如经纬度、笛卡尔坐标、屏幕坐标等。
主要方法:
-
cartesian3ToLng(position)
将Cesium.Cartesian3
(三维笛卡尔坐标)转换为经纬度和高度(WGS84 坐标系)。 -
lngToCartesian3(position)
将经纬度对象{lng, lat, height}
转换为Cesium.Cartesian3
。 -
screenPositionToCartesian3(position)
将屏幕坐标(Cesium.Cartesian2
)转换为世界坐标(Cesium.Cartesian3
),考虑地形和 3D 模型。 -
generateCirclePoints(center, radius)
根据圆心和半径生成圆形边缘的经纬度点集合,用于绘制圆形。
2. DrawTool 类
功能:实现交互式绘制点、线、多边形、矩形、圆等几何图形,支持贴地或固定高度模式。
核心特性:
-
绘制模式
-
ClampToGround
: 图形贴附地形或 3D 模型表面。 -
None
: 固定高度(默认)。
-
-
支持的图形类型
-
点(Point)、折线(Polyline)、多边形(Polygon)、矩形(Rect)、圆(Circle)。
-
-
交互逻辑
-
左键单击:添加点或调整图形形状。
-
右键单击:结束绘制。
-
鼠标移动:实时预览未完成的图形。
-
关键方法:
-
active(drawType)
激活指定绘图类型,初始化事件监听和临时实体。 -
生成图形方法
-
generatePolyline()
: 动态更新折线顶点。 -
generateRect()
: 通过两点计算矩形四个角点。 -
generateCircle()
: 根据圆心和半径生成圆。 -
generatePolygon()
: 多边形自动闭合。
-
-
坐标拾取
getcartesian3FromScreen(px)
处理从屏幕坐标到世界坐标的转换,兼容地形和 3D 模型。 -
贴地处理
setClamp()
根据模式设置图形是否贴地,利用ClassificationType
实现。 -
事件触发
绘制结束时触发DrawEndEvent
,传递实体对象、坐标数据及图形类型。
3.使用示例
// 初始化 Viewer
const viewer = new Cesium.Viewer("cesiumContainer");// 创建绘图工具实例
const drawTool = new DrawTool(viewer, { drawMode: "clampToGround" });// 监听绘制完成事件
drawTool.DrawEndEvent.addEventListener((entity, positions, type, circlePoints) => {console.log("绘制完成:", type, positions);
});// 激活多边形绘制
drawTool.active(drawTool.DrawTypes.Polygon);
4.代码亮点
-
坐标系统无缝转换:支持复杂场景下的坐标精准拾取。
-
动态预览:利用
CallbackProperty
实现图形实时更新。 -
地形适配:通过
ClassificationType
确保图形贴合地形或模型表面。 -
事件驱动设计:便于扩展和集成到其他功能模块。
5.潜在改进
-
圆形精度:当前
getCirclePoint
假设地球为完美球体,可能在高纬度或大范围时产生误差,需改用测地线算法。 -
撤销/重做:可添加历史记录功能,支持操作回退。
-
UI 交互:增强绘制提示(如文字标签、撤销按钮)。
此工具适用于需要在地球表面交互式绘制标绘物的场景,如地理标注、测量、规划等。
6.具体实现
PositionTransfer.js
import * as Cesium from "cesium";class PositionTransfer {/*** 坐标转换工具* @param {Cesium.Viewer} viewer : viewer程序对象*/constructor(viewer) {this.viewer = viewer;}/*** 笛卡尔转经纬度* @param {Cesium.Cartesian3} position : 笛卡尔三阶坐标*/cartesian3ToLng(position) {// 获取当前椭球的坐标系统,其中包含了坐标转换工具const ellipsoid = this.viewer.scene.globe.ellipsoid;// 笛卡尔坐标转为弧度坐标const cartoGraphic = ellipsoid.cartesianToCartographic(position);// 将弧度坐标转为经纬度const lng = Cesium.Math.toDegrees(cartoGraphic.longitude);const lat = Cesium.Math.toDegrees(cartoGraphic.latitude);const height = cartoGraphic.height;return {lng,lat,height,};}/*** 笛卡尔转经纬度(方式2)* @param {Cesium.Cartesian3} position : 笛卡尔三阶坐标*/cartesian3ToDegreesHeight(position) {let c = Cesium.Cartographic.fromCartesian(position);return [Cesium.Math.toDegrees(c.longitude),Cesium.Math.toDegrees(c.latitude),c.height,];}/*** 根据笛卡尔获取位置高度* @param {Cesium.Cartesian3} position : 笛卡尔三阶坐标*/getPositionHeight(position) {const cartographic = Cesium.Cartographic.fromCartesian(position);return cartographic.height;}getCirclePoint(lon, lat, angle, radius) {let dx = radius * Math.sin((angle * Math.PI) / 180.0);let dy = radius * Math.cos((angle * Math.PI) / 180.0);let ec = 6356725 + ((6378137 - 6356725) * (90.0 - lat)) / 90.0;let ed = ec * Math.cos((lat * Math.PI) / 180);let newLon = ((dx / ed + (lon * Math.PI) / 180.0) * 180.0) / Math.PI;let newLat = ((dy / ec + (lat * Math.PI) / 180.0) * 180.0) / Math.PI;return [newLon, newLat];}/*** 获取圆的边缘坐标* @param {Cesium.Cartesian3} center : 笛卡尔三阶坐标* @param {Number} radius : 半径*/generateCirclePoints(center, radius) {let points = [];for (let i = 0; i < 360; i += 2) {points.push(this.getCirclePoint(center[0], center[1], i, radius));}return points;}/*** 经纬度转笛卡尔* @param {Object} position : 经纬度 {lng,lat,height}*/lngToCartesian3(position) {const { lng, lat, height } = position;return new Cesium.Cartesian3.fromDegrees(lng, lat, height);}/*** 经纬度转笛卡尔(批量)* @param {Array<{lng,lat,height}>|Array<{lng,lat}>} positions : 经纬度坐标数组* @param {Boolean} isHeight 是否包含了高度*/lngPositionsToCartesian3(positions, isHeight = false) {const resArr = [];if (Array.isArray(positions)) {positions.forEach((position) => {const { lng, lat, height } = position;if (isHeight) {resArr.push(lng, lat, height);} else {resArr.push(lng, lat);}});// 根据是否带高度,返回对应的数据return isHeight? new Cesium.Cartesian3.fromDegreesArrayHeights(resArr): new Cesium.Cartesian3.fromDegreesArray(resArr);}}/*** 屏幕坐标和笛卡尔坐标转换* @param {Cesium.Cartesian2} position : 屏幕坐标,笛卡尔2阶数据*/screenPositionToCartesian3(position) {return this.viewer.scene.globe.pick(this.viewer.camera.getPickRay(position),this.viewer.scene);}/*** 世界坐标转屏幕坐标* @param {Cesium.Cartesian3} position : 世界坐标*/cartesian3ToScreenPosition(position) {return Cesium.SceneTransforms.wgs84ToWindowCoordinates(this.viewer.scene,position);}// todo:世界坐标转为带地形高度的经纬度
}export default PositionTransfer;
DrawTool.js
import * as Cesium from "cesium";
import PositionTransfer from "./PositionTrans";class DrawTool {/*** 绘制工具* @param {Cesium.Viewer} viewer : viewer程序对象*/constructor(viewer, options = {}) {this.viewer = viewer;// 开启深度检测//this.viewer.scene.globe.depthTestAgainstTerrain = true;viewer.scene.globe.depthTestAgainstTerrain = true;// 设置线性深度// viewer.scene.logarithmicDepthBuffer = false; // 禁用对数深度缓冲// viewer.scene.globe.linearDepth = true; // 启用线性深度this.positionTool=new PositionTransfer(viewer)// 默认按照贴地模式绘制图元this.drawMode = Cesium.defaultValue(options.drawMode, this.DrawModes.ClampToGround);// 初始化handler,eventsthis.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas);this.DrawEndEvent = new Cesium.Event(); //结束绘制事件}// 绘制模式,支持贴地和固定高度DrawModes = {ClampToGround: "clampToGround",None: "none",};DrawTypes = {Polyline: "Polyline",Rect: "Rect",Point: "Point",Circle: "Circle",Polygon: "Polygon",};// 激活工具,传入DrawTypeactive(drawType) {// 如果在我们的绘制工具集合中,存在这个工具if (Object.keys(this.DrawTypes).includes(drawType)) {this.drawType = drawType;// 最终的坐标this.positions = [];// 绘制过程中的坐标this.curPositions = [];// 每次点击有一个标点,需要存储实体this.points = [];// 注册鼠标事件this.registerEvents();//设置鼠标状态this.viewer.enableCursorStyle = false;this.viewer._element.style.cursor = "default";this.createMarker(drawType);} else {return;}}createMarker(drawType) {this.marker = document.createElement("div");this.marker.innerHTML = `左键点击绘制${drawType},右键结束绘制`;this.marker.className = "marker-draw";this.viewer.cesiumWidget.container.appendChild(this.marker);}destoryMarker() {this.marker && this.viewer.cesiumWidget.container.removeChild(this.marker);this.marker = null;}registerEvents() {// 分别注册左键画点,右键结束画点,鼠标移动事件this.leftClickEvent();this.rightClickEvent();this.mouseMoveEvent();}/*** 屏幕坐标转笛卡尔,分别从模型,地形,地球表面进行讨论** @param {Cesium.Cartesian2} px 屏幕坐标** @return {Cesium.Cartesian3} Cartesian3 三维坐标*/getcartesian3FromScreen(px) {if (this.viewer && px) {// 使用pick获取当前射线穿透的第一个物体const pick = this.viewer.scene.pick(px);let isOn3dtiles = false;let isOnTerrain = false;// 最终得到的笛卡尔坐标let cartesianRes = null;if (pick && pick.primitive.isCesium3DTileset) {isOn3dtiles = true;}// 判断是否加载了地形let boolTerrain =this.viewer.terrainProvider instanceof Cesium.EllipsoidTerrainProvider;// 如果没有点击到模型,并且加载了地形的话,走地形碰撞逻辑if (!isOn3dtiles && !boolTerrain) {const ray = this.viewer.scene.camera.getPickRay(px);if (!ray) return null;cartesianRes = this.viewer.scene.globe.pick(ray, this.viewer.scene);isOnTerrain = true;}// 如果没有加载地形,也没有3dtiles,就是拾取到地球上的坐标if (!isOn3dtiles && !isOnTerrain && boolTerrain) {cartesianRes = this.viewer.scene.camera.pickEllipsoid(px,this.viewer.scene.globe.ellipsoid);}// 3dtilesif (isOn3dtiles) {cartesianRes = this.viewer.scene.pickPosition(px);}// 如果有笛卡尔坐标,避免笛卡尔坐标的高度小于0if (cartesianRes) {let position = this.positionTool.cartesian3ToDegreesHeight(cartesianRes);// 可以将坐标高度设置为>=0// if(position[2]<0){// }return cartesianRes;}return false;}}leftClickEvent() {// 单机鼠标左键画点this.handler.setInputAction((e) => {// 屏幕坐标转为笛卡尔坐标,分三种情况let position = this.getcartesian3FromScreen(e.position);if (!position) return;this.positions.push(position);this.curPositions.push(position);// 如果是第一个点,就开始根据drawType,绘制图案if (this.positions.length === 1) {this.startDraw();this.setClamp()}// 如果是画线或者画点,每次点击左键,都在同一位置画一个点,绘制线的端点if (this.drawType === this.DrawTypes.Polyline ||this.drawType === this.DrawTypes.Point) {this.generatePoint(position);}}, Cesium.ScreenSpaceEventType.LEFT_CLICK);}// 鼠标移动事件mouseMoveEvent() {this.handler.setInputAction((e) => {this.marker.style.left = e.endPosition.x + 20 + "px";this.marker.style.top = e.endPosition.y - 20 + "px";this.viewer._element.style.cursor = "default"; //由于鼠标移动时 Cesium会默认将鼠标样式修改为手柄 所以移动时手动设置回来let position = this.getcartesian3FromScreen(e.endPosition);if (!position || !this.drawEntity) return;// tempPositions是每次鼠标移动时,我们得到的坐标,this.position是我们左键点击才能得到的坐标this.curPositions = [...this.positions, position];}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);}// 右键点击,结束绘制rightClickEvent() {this.handler.setInputAction(() => {// 如果还没有开始绘制,直接结束绘制状态if (!this.drawEntity) {this.deactive();return;}// 如果当前的坐标数量少于最小数量,直接结束if (this.positions.length < this.minPositionCount) {this.deactive();return;}// 根据各种绘制类型,要重新给坐标赋值,吧callback变为constantswitch (this.drawType) {case this.DrawTypes.Polyline:this.drawEntity.polyline.positions = this.positions;break;case this.DrawTypes.Rect:this.drawEntity.polygon.hierarchy = new Cesium.PolygonHierarchy(this.getRectFourPoints());this.drawEntity.polyline.positions = this.getRectFourPoints();this.positions = this.getRectFourPoints();this.positions.pop();break;case this.DrawTypes.Circle:this.drawEntity.ellipse.semiMinorAxis = this.getAxis();this.drawEntity.ellipse.semiMajorAxis = this.getAxis();this.minPositionCount = 2;break;case this.DrawTypes.Polygon:this.drawEntity.polygon.hierarchy = new Cesium.PolygonHierarchy(this.positions.concat(this.positions[0]));this.drawEntity.polyline.positions = this.positions.concat(this.positions[0]);break;default:break;}this.deactive();}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);}startDraw() {switch (this.drawType) {case this.DrawTypes.Point:// 对于点,直接结束绘制就完事了this.drawEntity = this.generatePoint(this.positions[0]);// 记录最小构成几何体的坐标数量,point:1,polyline:2,rect:2,circle:2,polygon:3this.minPositionCount = 1;break;case this.DrawTypes.Polyline:this.generatePolyline();this.minPositionCount = 2;break;case this.DrawTypes.Rect:this.generateRect();this.minPositionCount = 2;break;case this.DrawTypes.Circle:this.generateCircle();this.minPositionCount = 2;break;case this.DrawTypes.Polygon:this.generatePolygon();this.minPositionCount = 3;default:break;}}// 绘制点generatePoint(position) {const cartographic = Cesium.Cartographic.fromCartesian(position);const point = this.viewer.entities.add({Type: this.DrawTypes.Point,position: position,point: {pixelSize: 14,color: Cesium.Color.RED,},});this.points.push(point);return point;}// 绘制线,position使用callbackPropertygeneratePolyline() {this.drawEntity = this.viewer.entities.add({Type: this.DrawTypes.Polyline,polyline: {positions: new Cesium.CallbackProperty((e) => {return this.curPositions;}, false),width: 2,material: new Cesium.PolylineDashMaterialProperty({color: Cesium.Color.YELLOW,})},});}// 绘制矩形,由polygon和polyline组成generateRect() {this.drawEntity = this.viewer.entities.add({Type: this.DrawTypes.Rect,polygon: {hierarchy: new Cesium.CallbackProperty((e) => {return new Cesium.PolygonHierarchy(this.getRectFourPoints());}, false),material: Cesium.Color.RED.withAlpha(0.6)},polyline: {positions: new Cesium.CallbackProperty((e) => {return this.getRectFourPoints();}, false),width: 1,material: new Cesium.PolylineDashMaterialProperty({color: Cesium.Color.YELLOW,}),depthFailMaterial: new Cesium.PolylineDashMaterialProperty({color: Cesium.Color.YELLOW,})},});}setClamp() {// 设置贴地if (this.drawMode === "clampToGround") {if(this.drawEntity.ellipse){this.drawEntity.ellipse.classificationType =Cesium.ClassificationType.BOTH;}if(this.drawEntity.polygon){this.drawEntity.polygon.classificationType =Cesium.ClassificationType.BOTH;this.drawEntity.polygon.perPositionHeight=false}if(this.drawEntity.polyline){this.drawEntity.polyline.classificationType =Cesium.ClassificationType.BOTH;this.drawEntity.polyline.clampToGround = true;}} else {if(this.drawEntity.ellipse){this.drawEntity.ellipse.height = this.positionTool.getPositionHeight(this.positions[0]);}if(this.drawEntity.polygon){this.drawEntity.polygon.classificationType = undefinedthis.drawEntity.polygon.perPositionHeight=true}if(this.drawEntity.polyline){this.drawEntity.polyline.classificationType = undefined;this.drawEntity.polyline.clampToGround = false;}}}// 获取矩形四个点getRectFourPoints() {let res = this.curPositions;if (this.curPositions.length > 1) {let p1 = this.curPositions[0];let p2 = this.curPositions[1];let c1 = Cesium.Cartographic.fromCartesian(p1);let c2 = Cesium.Cartographic.fromCartesian(p2);if (c1.height < 0) c1.height = 0;if (c2.height < 0) c2.height = 0;let lls = this.getRectanglePointsByTwoPoint(c1, c2);// 坐标数组转为指定格式let ars = [lls[0][0],lls[0][1],c1.height,lls[1][0],lls[1][1],c1.height,lls[2][0],lls[2][1],c1.height,lls[3][0],lls[3][1],c1.height,lls[0][0],lls[0][1],c1.height,];res = Cesium.Cartesian3.fromDegreesArrayHeights(ars);}return res;}// 获取矩形四个点getRectanglePointsByTwoPoint(c1, c2) {//转为经纬度let lngLat1 = [Cesium.Math.toDegrees(c1.longitude),Cesium.Math.toDegrees(c1.latitude),];let lngLat2 = [Cesium.Math.toDegrees(c2.longitude),Cesium.Math.toDegrees(c2.latitude),];let lngLat3 = [Cesium.Math.toDegrees(c1.longitude),Cesium.Math.toDegrees(c2.latitude),];let lngLat4 = [Cesium.Math.toDegrees(c2.longitude),Cesium.Math.toDegrees(c1.latitude),];return [lngLat1, lngLat3, lngLat2, lngLat4];}// 绘制多边形generatePolygon() {this.drawEntity = this.viewer.entities.add({Type: this.DrawTypes.Polygon,polygon: {// 多边形坐标有首位闭合的特点hierarchy: new Cesium.CallbackProperty((e) => {return new Cesium.PolygonHierarchy(this.curPositions.concat(this.curPositions[0]));}, false),material: Cesium.Color.RED.withAlpha(0.6)},polyline: {positions: new Cesium.CallbackProperty((e) => {return this.curPositions.concat(this.curPositions[0]);}, false),width: 1,material: new Cesium.PolylineDashMaterialProperty({color: Cesium.Color.YELLOW,}),depthFailMaterial: new Cesium.PolylineDashMaterialProperty({color: Cesium.Color.YELLOW,})},});}// 绘制圆generateCircle() {this.drawEntity = this.viewer.entities.add({position: this.positions[0],ellipse: {height: this.positionTool.getPositionHeight(this.positions[0]),semiMinorAxis: new Cesium.CallbackProperty((e) => {return this.getAxis();}, false),semiMajorAxis: new Cesium.CallbackProperty((e) => {return this.getAxis();}, false),material: Cesium.Color.RED.withAlpha(0.6),},});}//圆半径getAxis() {if (this.curPositions.length>1) {let p1 = this.curPositions[0];let p2 = this.curPositions[this.curPositions.length - 1];const axis = Cesium.Cartesian3.distance(p1, p2);return axis;}else{return 0}}// 结束绘制deactive() {// 对于圆,根据半径计算边缘点坐标并返回let points = [];if (this.drawType === this.DrawTypes.Circle) {const radius = this.getAxis();const positions = this.positionTool.cartesian3ToDegreesHeight(this.positions[0]);points = this.positionTool.generateCirclePoints(positions, radius);points = points.map((item) => {const height = this.positionTool.getPositionHeight(this.positions[0]);return Cesium.Cartesian3.fromDegrees(item[0], item[1], height);});}// 提交绘制结束事件this.DrawEndEvent.raiseEvent(this.drawEntity,this.positions,this.drawType,points);this.unRegisterEvents();this.destoryMarker();this.drawType = undefined;this.drawEntity = undefined;this.positions = [];this.curPositions = [];this.viewer._element.style.cursor = "pointer";this.viewer.enableCursorStyle = true;}//解除鼠标事件unRegisterEvents() {this.handler.removeInputAction(Cesium.ScreenSpaceEventType.RIGHT_CLICK);this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE);this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);}// 清除绘制的实体removeAllDrawEnts() {this.points &&this.points.forEach((point) => {this.viewer.entities.remove(point);});this.drawEntity && this.viewer.entities.remove(this.drawEntity);this.points = [];this.drawEntity = null;}removeListener(event) {this.DrawEndEvent &&this.DrawEndEvent.numberOfListeners &&this.DrawEndEvent.removeEventListener(event);}
}export default DrawTool;
DrawToolUse.js
import * as Cesium from 'cesium'
import * as dat from 'dat.gui'
import DrawTool from '../lib/DrawTool'const gui=new dat.GUI()
// Cesium Ion token
Cesium.Ion.defaultAccessToken='mytoken'
// viewer是整个三维场景的入口
const viewer=new Cesium.Viewer('cesiumContainer',{// 隐藏默认显示的控件// 时间轴控件timeline:false,// 动画控件animation:false,// 设置底图切换控件baseLayerPicker:false,// 复位按钮的控件homeButton:false,// 全屏按钮的控件fullscreenButton:false,// 导航功能的控件geocoder:false,// 隐藏二三维模式的切换控件sceneModePicker:false,scene3DOnly:true,// 隐藏默认的导航按钮navigationHelpButton:false,// 时间是否流动shouldAnimate:true,imageryProvider:new Cesium.GridImageryProvider({cells:1,glowWidth:0,color:Cesium.Color.WHITE.withAlpha(0.1),backgroundColor:Cesium.Color.GRAY})
})const drawTool=new DrawTool(viewer)gui.add({fn(){drawTool.active(drawTool.DrawTypes.Polyline)drawTool.DrawEndEvent.addEventListener((ent,positions)=>{console.log(positions);})}
},'fn').name('绘制线')gui.add({fn(){drawTool.active(drawTool.DrawTypes.Polygon)drawTool.DrawEndEvent.addEventListener((ent,positions)=>{console.log(positions);})}
},'fn').name('绘制多边形')