主要功能
点击广告牌的时候,可以在固定的高度上拖动,没有使用entity是考虑效率问题,主要实现思路是在当前点的垂直方向创建一个平面,移动的时候是获取鼠标位置对应的射线,从而获取焦点,直接上代码
/**
* 广告牌,左键点击可以移动
*/
function generateDroneSvg(text: string): string {
return `
<svg xmlns='http://www.w3.org/2000/svg'
xmlns:xlink='http://www.w3.org/1999/xlink'
viewBox='0 0 42 35' width='42' height='35'>
<defs>
<filter id='a' width='150.8%' height='171.7%' x='-25.4%' y='-34.2%' filterUnits='objectBoundingBox'>
<feMorphology in='SourceAlpha' operator='dilate' radius='1' result='shadowSpreadOuter1'/>
<feOffset in='shadowSpreadOuter1' result='shadowOffsetOuter1'/>
<feGaussianBlur in='shadowOffsetOuter1' result='shadowBlurOuter1' stdDeviation='2'/>
<feComposite in='shadowBlurOuter1' in2='SourceAlpha' operator='out' result='shadowBlurOuter1'/>
<feColorMatrix in='shadowBlurOuter1' values='0 0 0 0 0.039215686 0 0 0 0 0.933333333 0 0 0 0 0.545098039 0 0 0 1 0'/>
</filter>
<path id='b' d='M16.832 3.248l14.132 21.197A1 1 0 0130.13 26H1.87a1 1 0 01-.833-1.555L15.168 3.248a1 1 0 011.664 0z'/>
</defs>
<g fill='none' fill-rule='evenodd'>
<g transform='matrix(1 0 0 -1 5 32)'>
<use fill='#000' filter='url(#a)' xlink:href='#b'/>
<path fill='#00D690' stroke='#FFF' stroke-linejoin='round' stroke-width='2' d='M15.955 3.871L30.13 25H1.87L15.955 3.871z'/>
</g>
<text fill='#FFF' font-size='14' font-weight='500'>
<tspan x='50%' y='50%' dy='.25em' text-anchor='middle'>${text}</tspan>
</text>
</g>
</svg>`;
}
// 2. SVG 转 Data URL 工具函数
function svgToDataUrl(svgStr: string): string {
// 使用 encodeURIComponent 编码,兼容性好且无需处理 base64 编码问题
const encoded = encodeURIComponent(svgStr).replace(/'/g, '%27').replace(/"/g, '%22');
return `data:image/svg+xml;charset=utf-8,${encoded}`;
}
interface XYZ {
x: number;
y: number;
z: number;
}
import {
Viewer,
Billboard,
BillboardCollection,
Cartesian3,
ScreenSpaceEventHandler,
ScreenSpaceEventType,
defined,
Cartesian2,
IntersectionTests,
Plane,
Cartographic,
Math as CesiumMath,
} from 'cesium';
export class DroneBillboard {
viewer: Viewer;
billboards: BillboardCollection;
listsMap: Map<string, Billboard> = new Map();
handler: ScreenSpaceEventHandler;
getPositionFn!: (id: string, e: XYZ) => void;
private isDragging: boolean = false;
private draggedBillboard: Billboard | null = null;
private dragPlane: Plane | undefined;
private dragId: string = '';
constructor(viewer: Viewer) {
this.viewer = viewer;
this.billboards = this.viewer.scene.primitives.add(new BillboardCollection());
this.handler = new ScreenSpaceEventHandler(this.viewer.scene.canvas);
this.bindEvents();
}
addFn(myId: string, text: string, x = 0, y = 0, z = 0) {
const imageUrl = svgToDataUrl(generateDroneSvg(text));
if (this.listsMap.has(myId)) {
const item = this.listsMap.get(myId);
if (item) {
item.position = Cartesian3.fromDegrees(x, y, z);
item.image = imageUrl;
}
} else {
const item = this.billboards.add({
position: Cartesian3.fromDegrees(x, y, z),
image: imageUrl,
});
this.listsMap.set(myId, item);
}
}
removeFn(myId: string) {
const item = this.listsMap.get(myId);
if (item) {
this.billboards.remove(item);
this.listsMap.delete(myId);
}
}
private bindEvents() {
const scene = this.viewer.scene;
const canvas = scene.canvas;
const ellipsoid = scene.globe.ellipsoid;
this.handler.setInputAction((movement: { position: Cartesian2 }) => {
const pickedObject = scene.pick(movement.position);
if (defined(pickedObject) && defined(pickedObject.primitive)) {
for (const [id, billboard] of this.listsMap) {
if (pickedObject.primitive === billboard && billboard.position) {
this.isDragging = true;
this.dragId = id;
this.draggedBillboard = billboard;
canvas.style.cursor = 'grabbing';
this.viewer.scene.screenSpaceCameraController.enableInputs = false;
// 2. 创建一次平面!
// 法线是当前点的地垂线方向
const normal = ellipsoid.geodeticSurfaceNormal(billboard.position, new Cartesian3());
this.dragPlane = Plane.fromPointNormal(billboard.position, normal);
break;
}
}
}
}, ScreenSpaceEventType.LEFT_DOWN);
this.handler.setInputAction((movement: { startPosition: Cartesian2; endPosition: Cartesian2 }) => {
if (!this.isDragging || !this.draggedBillboard) {
return;
}
// 1. 获取鼠标位置对应的射线
const ray = scene.camera.getPickRay(movement.endPosition);
if (defined(ray) && this.dragPlane) {
const intersection = IntersectionTests.rayPlane(ray, this.dragPlane);
if (defined(intersection)) {
this.draggedBillboard.position = intersection;
//回调
const cartographic = Cartographic.fromCartesian(intersection);
const { longitude, latitude, height } = cartographic;
this.getPositionFn(this.dragId, {
x: CesiumMath.toDegrees(longitude),
y: CesiumMath.toDegrees(latitude),
z: height,
});
}
}
}, ScreenSpaceEventType.MOUSE_MOVE);
this.handler.setInputAction(() => {
if (this.isDragging) {
//回调
// if ( this.draggedBillboard && this.draggedBillboard.position){
// const cartographic = Cartographic.fromCartesian( this.draggedBillboard.position);
// const { longitude, latitude, height } = cartographic;
// this.getPositionFn(this.dragId, {
// x: CesiumMath.toDegrees(longitude),
// y: CesiumMath.toDegrees(latitude),
// z: height
// });
// }
this.isDragging = false;
this.dragId = '';
this.draggedBillboard = null;
canvas.style.cursor = 'default';
this.viewer.scene.screenSpaceCameraController.enableInputs = true;
}
}, ScreenSpaceEventType.LEFT_UP);
}
getPosition(fn: (id: string, e: XYZ) => void) {
this.getPositionFn = fn;
}
destroy() {
if (this.viewer) {
if (this.handler) {
this.handler.destroy();
this.handler = null!;
}
if (this.billboards) {
this.viewer.scene.primitives.remove(this.billboards);
this.billboards = null!;
}
this.listsMap.clear();
this.getPositionFn = null!;
this.draggedBillboard = null;
this.dragPlane = undefined;
}
}
}
调用方法
import { DroneBillboard } from '/@/components/Cesium/DroneBillboardClass';
let droneBillboards: DroneBillboard | null = null;
const billboardFn = () => {
if (viewer) {
droneBillboards = new DroneBillboard(viewer);
for (let i = 0; i < polygon.length; i++) {
droneBillboards.addFn('billboard-' + i, (i + 1).toString(), polygon[i].lng, polygon[i].lat, 10);
}
droneBillboards.getPosition((id, p) => {
if (id) {
const index = id.split('-')[1];
if (index !== undefined) {
const item = polygon[Number(index)];
item.lng = p.x;
item.lat = p.y;
polylineFn();
// polygonFn();
}
}
});
}
};