实现在cesium中基于鼠标动态绘制面功能,包括颜色模式,贴图模式,水面模式,贴地模式
1.基本架构设计
1.1封装 PolygonPrimitive 动态绘制多边形面操作类
class PolygonPrimitive {
private app: any;
private _handler: ScreenSpaceEventHandler;
private _handler2: ScreenSpaceEventHandler;
private _polygonConfigList: Map<any, any>;
private _polygonEntityList: Map<any, any>;
private _primitiveChecked: boolean;
constructor(app: any) {
this.app = app;
this._handler = new ScreenSpaceEventHandler(this.app.viewerCesium.scene.canvas);
this._handler2 = new ScreenSpaceEventHandler(this.app.viewerCesium.scene.canvas);
this._polygonConfigList = new Map();
this._polygonEntityList = new Map();
this._primitiveChecked = false;
}
/**
* 根据已知数据添加一个多边形面
* @param config 多边形面的配置项
*/
add(config: PolygonConfig) {}
/**
* 根据id删除对应的多边形面
* @param id
* @returns
*/
remove(id: string): Boolean {}
/**
* 改变多边形面的样式
* @param config 多边形面的配置项
* @returns
*/
changeStyle(config: PolygonConfig): Boolean {}
/**
* 创建材质
* @param config 多边形面的配置项
* @returns
*/
private createMaterial(config: PolygonConfig): Material {}
/**
* 绘制形状,用于内部临时画多边形面
* @param positionData 位置数据
* @param config 多边形面的配置项
* @returns
*/
private drawShape(positionData: Cartesian3[]) {}
/**
* 开启绘制多边形面
* @param successCallback 绘制多边形面完成后的回调,返回面的Cartesian3[]位置数据,此时调用add添加多边形面,并可进行业务逻辑处理
*/
startDrawing(successCallback: Function) {
// ...someCode...
this._handler.setInputAction((event: any) => {}, ScreenSpaceEventType.LEFT_CLICK);
this._handler.setInputAction((event: any) => {}, ScreenSpaceEventType.MOUSE_MOVE);
this._handler.setInputAction(() => {}, ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
this._handler.setInputAction(() => {}, ScreenSpaceEventType.RIGHT_CLICK);
}
/**
* 关闭绘制多边形面
*/
endDrawing() {
this._handler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
this._handler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
this._handler.removeInputAction(ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
this._handler.removeInputAction(ScreenSpaceEventType.RIGHT_CLICK);
}
/**
* 开启多边形面选中
* @param successCallback 选中多边形面的回调,返回这个面的config相关数据
*/
openSelection(successCallback: Function) {
this._handler2.setInputAction((event: any) => {}, ScreenSpaceEventType.MOUSE_MOVE);
this._handler2.setInputAction((event: any) => {}, ScreenSpaceEventType.LEFT_CLICK);
}
/**
* 关闭多边形面选中
*/
closeSelection() {
this._handler2.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
this._handler2.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
}
}
以上是一些基本的操作函数,具体的实现视业务而定
1.2 封装 PolygonConfig 多边形面的配置项
/**
* PolygonConfig 多边形面的配置项
*/
class PolygonConfig {
/** id区分多边形面 */
id: string;
/** 多边形面的位置数据 */
positions: Cartesian3[];
/** 多边形面的样式 */
material: MaterialColor | MaterialWater | MaterialImage;
/** 是否贴地 */
clampToGround: boolean;
constructor() {
this.id = '';
this.positions = [];
this.material = new MaterialColor();
this.clampToGround = false;
}
}
/**
* 颜色材质
*/
class MaterialColor {
materialType: string;
materialOptions: {
/** 颜色值 */
color: string;
};
constructor() {
this.materialType = 'Color';
this.materialOptions = {
color: '#fff'
};
}
}
/**
* 水面材质
*/
class MaterialWater {
materialType: string;
materialOptions: {
/** 水面法线贴图 */
normalMap: string;
/** 水面波纹数量 */
frequency: number;
/** 水面波纹流动速度 */
animationSpeed: number;
};
constructor() {
this.materialType = 'Water';
this.materialOptions = {
normalMap: '',
frequency: 1000.0,
animationSpeed: 0.05
};
}
}
/**
* 图片材质
*/
class MaterialImage {
materialType: string;
materialOptions: {
/** 图片地址 */
image: string;
/** X轴方向贴图重复次数 */
repeatX: number;
/** Y轴方向贴图重复次数 */
repeatY: number;
};
constructor() {
this.materialType = 'Image';
this.materialOptions = {
image: '',
repeatX: 1.0,
repeatY: 1.0
};
}
}
封装配置项的作用是为了方便使用者配置多边形面效果
2. 关键代码实现
2. 绘制多边形面交互相关事件
/**
* 开启绘制多边形面
* @param successCallback 绘制多边形面完成后的回调,返回面的Cartesian3[]位置数据,此时调用add添加多边形面,并可进行业务逻辑处理
*/
startDrawing(successCallback: Function) {
// 预防多次调用这个函数,多次绑定事件,所以先解绑事件
this.endDrawing();
// -----------文字提示--------------
textEntity = new Entity({
position: new Cartesian3(),
label: {
show: false,
text: '单击画多边形面',
font: '14px',
scale: 0.8,
showBackground: true,
disableDepthTestDistance: Number.POSITIVE_INFINITY,
pixelOffset: new Cartesian2(0.0, 30.0)
}
});
this.app.viewerCesium.entities.add(textEntity);
// -----------文字提示--------------
let activeShapePoints: Cartesian3[] = [];
let activeShape: Primitive | null;
let dynamicPositions: any;
// 鼠标左键点击事件
this._handler.setInputAction((event: any) => {
if (this._primitiveChecked) return;
const ray = this.app.viewerCesium.camera.getPickRay(event.position);
const earthPosition = this.app.viewerCesium.scene.globe.pick(ray, this.app.viewerCesium.scene);
if (defined(earthPosition)) {
if (activeShapePoints.length === 0) {
activeShapePoints.push(earthPosition);
// 使用CallbackProperty来实时画线,当activeShapePoints改变时,会自动计算位置实时绘制新的多边形面
dynamicPositions = new CallbackProperty(() => {
return new PolygonHierarchy(activeShapePoints);
}, false);
activeShape = this.drawShape(dynamicPositions);
}
if (activeShapePoints.length === 1) {
// @ts-ignore
textEntity.label.text = '左键双击结束,右键撤销';
}
activeShapePoints.push(earthPosition);
}
}, ScreenSpaceEventType.LEFT_CLICK);
// 鼠标移动事件(实现画多边形面,位置更新)
this._handler.setInputAction((event: any) => {
const ray = this.app.viewerCesium.camera.getPickRay(event.endPosition);
const earthPosition = this.app.viewerCesium.scene.globe.pick(ray, this.app.viewerCesium.scene);
//@ts-ignore
if (!textEntity.label.show._value) {
// @ts-ignore
textEntity.label.show = true;
}
textEntity.position = earthPosition;
if (activeShapePoints.length > 0) {
if (defined(earthPosition)) {
activeShapePoints.pop();
activeShapePoints.push(earthPosition);
}
}
}, ScreenSpaceEventType.MOUSE_MOVE);
// 鼠标左键双击结束绘制
this._handler.setInputAction(() => {
activeShapePoints.pop();
activeShapePoints.pop();
this.app.viewerCesium.entities.remove(activeShape);
// 坐标转换成经纬度高度
let geographyCoords = [];
for (let i = 0; i < activeShapePoints.length; i++) {
const cartographic = Cartographic.fromCartesian(activeShapePoints[i]);
const longitude = CesiumMath.toDegrees(cartographic.longitude);
const latitude = CesiumMath.toDegrees(cartographic.latitude);
const height = cartographic.height;
geographyCoords.push({
longitude,
latitude,
height
});
}
// 返回相关数据供业务端使用
successCallback &&
successCallback({
gc: geographyCoords,
cartesian3: activeShapePoints
});
activeShape = null;
activeShapePoints = [];
dynamicPositions = null;
// @ts-ignore
textEntity.label.text = '单击画多边形面';
}, ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
// 右键点击事件撤销上一步操作
this._handler.setInputAction(() => {
activeShapePoints.pop();
if (activeShapePoints.length === 1) {
// @ts-ignore
textEntity.label.text = '单击画多边形面';
}
}, ScreenSpaceEventType.RIGHT_CLICK);
}
/**
* 绘制形状,用于内部临时画多边形面
* @param positionData 位置数据
* @param config 多边形面的配置项
* @returns
*/
private drawShape(positionData: Cartesian3[]) {
const shape = this.app.viewerCesium.entities.add({
polygon: {
hierarchy: positionData,
perPositionHeight: true // 指定是否使用每个位置的高度(polygon的entity形式默认试贴地的,所以如果不配置成true,他会改变position位置数据,以达到贴地效果。
//为了保证位置数据是原始拾取到的数据,这里需要配置perPositionHeight: true。
}
});
return shape;
}
2.2 创建材质相关
/**
* 创建材质
* @param config 多边形面的配置项
* @returns
*/
private createMaterial(config: PolygonConfig): Material {
let material = null;
switch (config.material.materialType) {
case 'Color':
const materialColor = config.material as MaterialColor;
material = new Material({
fabric: {
type: 'Color',
uniforms: {
color: Color.fromCssColorString(materialColor.materialOptions.color)
}
}
});
break;
case 'Water':
const materialWater = config.material as MaterialWater;
const normalMap =
materialWater.materialOptions.normalMap ||
window.CcbimGisSDKLoaderConfig.staticHost +
'/ccbimGisSDK@' +
window.CcbimGisSDKLoaderConfig.version +
'/ccbimGis/bimAssets/assets/images/effect/waterNormals.jpg';
material = new Material({
fabric: {
type: 'Water',
uniforms: {
normalMap: normalMap,
frequency: materialWater.materialOptions.frequency,
animationSpeed: materialWater.materialOptions.animationSpeed
}
}
});
break;
case 'Image':
const materialImage = config.material as MaterialImage;
const image =
materialImage.materialOptions.image ||
window.CcbimGisSDKLoaderConfig.staticHost +
'/ccbimGisSDK@' +
window.CcbimGisSDKLoaderConfig.version +
'/ccbimGis/bimAssets/assets/images/pmlogo.jpg';
material = new Material({
fabric: {
type: 'Image',
uniforms: {
image: image,
repeat: new Cartesian2(materialImage.materialOptions.repeatX, materialImage.materialOptions.repeatY)
}
}
});
break;
default:
material = new Material({
fabric: {
type: 'Color',
uniforms: {
color: Color.fromCssColorString('#fff')
}
}
});
break;
}
return material;
}
cesium在Material中实现了水面效果,所以我们使用Primitive的material,而不是Entity的Property
2.3 添加polygon实体
/**
* 根据已知数据添加一个多边形面
* @param config 多边形面的配置项
*/
add(config: PolygonConfig) {
const configCopy = cloneDeep(config);
const positions = configCopy.positions;
const material = this.createMaterial(configCopy);
let polygonPrimitive;
if (configCopy.clampToGround) {
// 贴地
polygonPrimitive = new GroundPrimitive({
geometryInstances: new GeometryInstance({
id: 'polygonEntity_' + configCopy.id,
geometry: new PolygonGeometry({
polygonHierarchy: new PolygonHierarchy(positions)
})
}),
appearance: new EllipsoidSurfaceAppearance({
material: material
})
});
} else {
// 不贴地
polygonPrimitive = new Primitive({
geometryInstances: new GeometryInstance({
id: 'polygonEntity_' + configCopy.id,
geometry: new PolygonGeometry({
polygonHierarchy: new PolygonHierarchy(positions),
perPositionHeight: true // 使用每个位置的位置高度,而不是使用配置的高度来确定高度。如果不配置true,则会以配置的height高度来显示,即面与椭球体表面之间的距离显示,默认值0.0,会被地形遮挡看不见。
})
}),
appearance: new EllipsoidSurfaceAppearance({
material: material
})
});
}
this.app.viewerCesium.scene.primitives.add(polygonPrimitive);
this._polygonConfigList.set('polygonEntity_' + configCopy.id, configCopy);
this._polygonEntityList.set('polygonEntity_' + configCopy.id, polygonPrimitive);
}
3. 业务端调用
const polygonPrimitive = new Plugins.Polygon.PolygonPrimitive(gisApp);
// -------------鼠标动态绘制多边形面----------------
polygonPrimitive.startDrawing((data) => {
console.log(data);
let config = new Plugins.Polygon.PolygonConfig();
const id = ++polygonId;
polygonConfigMap.set(id, config);
config.id = id; // id必传且不能重复
config.positions = data.cartesian3;
polygonPrimitive.add(config);
});
// -------------鼠标动态绘制多边形面----------------
// polygonPrimitive.endDrawing();
// -------------改变成水面样式-----------------
let config = polygonConfigMap.get(polygonId);
let material = new Plugins.Polygon.MaterialWater();
config.material = material;
polygonPrimitive.changeStyle(config);
// -------------改变成水面样式-----------------
这里的设计,将Polygon模块放在Plugins下,并对外提供接口,由业务端组装使用功能。