Skip to content
Cesium绘制图形:圆形和水滴形

需求

需要实现如下图所示模块

水滴型:![[Pasted image 20241016093333.png]]

圆形:![[Pasted image 20241015163602.png]]

![[Pasted image 20241017144745.png]]

水滴里面radius是指AE,a是指BE,b是指BD

实现1:纯Canvas实现

圆形实际上可以通过Cesium自带的# CircleOutlineGeometry来实现,但是我这里想统一,因为水滴型是只能通过canvas绘制成img再加载进行,所以暂时统一通过canvas绘制的方法来实现

缺点

太粗暴直接加载而已,无法通过中心点和半径来改变这个园;仅仅只是为了加载出来

代码

圆形Canvas绘制代码(Rectangle就直接贴图实现)

javascript
const viewer = new Cesium.Viewer('cesiumContainer');

  

// 定义经纬度范围

const lonMin = 100.0;

const latMin = 20.0;

const lonMax = 110.0;

const latMax = 30.0;

  

const canvas = document.createElement('canvas');

canvas.width = 500;

canvas.height = 500;

const ctx = canvas.getContext('2d');

  

// 定义参数

const width = 500;

const radius = width / 2;

const lineWidth = 5; // 边框线的宽度

const strokeColor = '#3FCF8F'; // 轮廓线的颜色

  

// 清空画布

ctx.clearRect(0, 0, canvas.width, canvas.height);

  

// 开始绘制圆形轮廓

ctx.save();

ctx.beginPath();

ctx.arc(radius, radius, radius - lineWidth / 2, 0, 2 * Math.PI); // 圆形轮廓

ctx.lineWidth = lineWidth;

ctx.strokeStyle = strokeColor; // 设置边框颜色

ctx.stroke(); // 绘制边框

ctx.restore();

  

// 使用Cesium的贴地primitive加载Canvas

viewer.scene.primitives.add(

    new Cesium.GroundPrimitive({

        geometryInstances: new Cesium.GeometryInstance({

            // 和二维类似,本质上还是一个矩形

            geometry: new Cesium.RectangleGeometry({

                rectangle: Cesium.Rectangle.fromDegrees(

                    // 提供矩形的四至经纬度

                    lonMin,

                    latMin,

                    lonMax,

                    latMax

                ),

            }),

        }),

        appearance: new Cesium.Appearance({

            // 创建一个完全自定义的material

            material: new Cesium.Material({

                fabric: {

                    type: 'Image',

                    uniforms: {

                        // textureCanvas是我们创建的自定义Canvas

                        image: canvas,

                    },

                },

            }),

            flat: true,

        }),

    })

);

  

// 视角飞向贴地圆形区域

viewer.camera.flyTo({

    destination: Cesium.Rectangle.fromDegrees(lonMin, latMin, lonMax, latMax),

    duration: 3, // 飞行的时间(秒)

});

水滴形cavas绘制代码(Rectangle就直接贴图实现)

javascript
const viewer = new Cesium.Viewer('cesiumContainer');

  

// 定义经纬度范围

const lonMin = 100.0;

const latMin = 20.0;

const lonMax = 110.0;

const latMax = 30.0;

  

const canvas = document.createElement('canvas');

canvas.width = 500;

canvas.height = 500;

const ctx = canvas.getContext('2d');

  

// 定义参数

const width = 500;

const radius = width / 2;

const a = 100; // 椭圆的短轴半径

const b = 150; // 椭圆的长轴半径

const rotate = 0; // 旋转角度

const lineWidth = 5; // 轮廓线宽度

const strokeColor = '#3FCF8F'; // 轮廓线颜色

const fillColor = 'transparent'; // 填充颜色(透明)

const alpha = 1; // 透明度

const AB = radius - a; // 用于控制曲线的参数

  

// 清空画布

ctx.clearRect(0, 0, canvas.width, canvas.height);

  

// 缩小比例

ctx.scale(0.5, 1.0); // 将绘制的图形缩小一半

  

// 开始绘制水滴形轮廓

ctx.save();

ctx.beginPath();

ctx.globalAlpha = alpha;

ctx.fillStyle = fillColor || 'transparent'; // 设置填充颜色为透明

ctx.strokeStyle = strokeColor; // 设置边框颜色

ctx.lineWidth = lineWidth;

  

// 移动到水滴的起点

ctx.moveTo(radius, radius);

ctx.rotate((rotate * Math.PI) / 180);

// 绘制二次贝塞尔曲线(左边的弯曲部分)

ctx.quadraticCurveTo(radius + b, radius - AB / 2, radius + b, radius - AB);

// 绘制半椭圆(顶部圆滑部分)

ctx.ellipse(radius, radius - AB, b, a, 0, 0, Math.PI, true);

// 绘制二次贝塞尔曲线(右边的弯曲部分)

ctx.quadraticCurveTo(radius - b, radius - AB / 2, radius, radius);

  

// 绘制路径并应用样式

ctx.fill(); // 不会显示颜色,填充为空

ctx.stroke(); // 仅绘制边框

ctx.restore();

  

// 使用Cesium的贴地primitive加载Canvas

viewer.scene.primitives.add(

    new Cesium.GroundPrimitive({

        geometryInstances: new Cesium.GeometryInstance({

            // 和二维类似,本质上还是一个矩形

            geometry: new Cesium.RectangleGeometry({

                rectangle: Cesium.Rectangle.fromDegrees(

                    // 提供矩形的四至经纬度

                    lonMin,

                    latMin,

                    lonMax,

                    latMax

                ),

            }),

        }),

        appearance: new Cesium.Appearance({

            // 创建一个完全自定义的material

            material: new Cesium.Material({

                fabric: {

                    type: 'Image',

                    uniforms: {

                        // textureCanvas是我们创建的自定义Canvas

                        image: canvas,

                    },

                },

            }),

            flat: true,

        }),

    })

);

  

// 视角飞向贴地圆形区域

viewer.camera.flyTo({

    destination: Cesium.Rectangle.fromDegrees(lonMin, latMin, lonMax, latMax),

    duration: 3, // 飞行的时间(秒)

});

实现2:Canvas + CircleGeometry实现

圆形轮廓线贴地(传中心点+半径)

javascript
const viewer = new Cesium.Viewer('cesiumContainer');

// 定义中心点
const centerPoint = {
    longitude: 112.0285,
    latitude: 37.43843,
};

const radiusInMeters = 50; // 半径设置为50米

// 创建 canvas 并绘制圆形图案
const canvas = document.createElement('canvas');
canvas.width = 500;
canvas.height = 500;
const ctx = canvas.getContext('2d');

// 定义圆形参数
const width = 500;
const radius = width / 2;
const lineWidth = 5; // 边框线宽
const strokeColor = '#3FCF8F'; // 边框颜色

// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);

// 绘制圆形
ctx.save();
ctx.beginPath();
ctx.arc(radius, radius, radius - lineWidth / 2, 0, 2 * Math.PI); // 圆形轮廓
ctx.lineWidth = lineWidth;
ctx.strokeStyle = strokeColor; // 设置边框颜色
ctx.stroke(); // 绘制边框
ctx.restore();

// 使用 CircleGeometry 来绘制贴地的圆形
viewer.scene.primitives.add(
    new Cesium.GroundPrimitive({
        geometryInstances: new Cesium.GeometryInstance({
            geometry: new Cesium.CircleGeometry({
                center: Cesium.Cartesian3.fromDegrees(centerPoint.longitude, centerPoint.latitude),
                radius: radiusInMeters, // 半径设置为50米
            }),
        }),
        appearance: new Cesium.Appearance({
            material: new Cesium.Material({
                fabric: {
                    type: 'Image',
                    uniforms: {
                        image: canvas, // 自定义的canvas图形作为材质
                    },
                },
            }),
            flat: true,
        }),
    })
);

// 添加中心点标记
viewer.entities.add({
    position: Cesium.Cartesian3.fromDegrees(centerPoint.longitude, centerPoint.latitude),
    point: {
        pixelSize: 10, // 点的大小
        color: Cesium.Color.RED, // 点的颜色
        outlineColor: Cesium.Color.WHITE, // 外轮廓的颜色
        outlineWidth: 2, // 外轮廓的宽度
    },
});

// 飞向圆形区域
viewer.camera.flyTo({
    destination: Cesium.Cartesian3.fromDegrees(centerPoint.longitude, centerPoint.latitude, 500), // 设置飞行高度
});

为什么不直接用CircleOutlineGeometry来绘制

因为CircleOutlineGeometry不支持贴地,如果要做的话,要用turfJS来把圆半径外的每一个点算出来,再用线贴地的方式来实现(现阶段太麻烦,直接用这种先),先加个todo吧

实现3 完美贴合需求实现

借助AI的力量来实现了,给够描述参数即可实现

水滴型

![[Pasted image 20241016104214.png]]

javascript
// 假设 Cesium 和 CanvasGraphic 已经导入
const viewer = new Cesium.Viewer('cesiumContainer', {
    terrain: Cesium.Terrain.fromWorldTerrain(),
});
class CanvasGraphic {
    constructor(options = {}) {
        const { color = '#3FCF8F', width = 500, lineWidth = 1, shadowColor, shadowBlur = 24 } = options;
        this.color = color;
        this.width = width;
        this.height = width;
        this.lineWidth = lineWidth;
        this.shadowColor = shadowColor || color;
        this.shadowBlur = shadowBlur;

        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        this.canvas = canvas;
        this.ctx = ctx;
        ctx.canvas.height = width;
        ctx.canvas.width = width;
        // 将坐标移到画布中心,不管画什么图,都需要移到中间,使用图层叠加时不会偏移
        ctx.translate(width / 2, width / 2);
        ctx.moveTo(0, 0);
    }

    /**
     * 绘制圆形
     * @param {object} options 绘制圆形传入参数
     * @param {string} options.fillColor 填充颜色
     * @param {number} options.radius 半径,默认是画板的一半宽
     * @param {number} options.alpha 颜色的透明度
     */
    drawCircle(options = {}) {
        const { ctx, width, shadowColor, color } = this;
        const { radius = width / 2, alpha = 1, lineWidth = 2, fillColor = color } = options;
        ctx.globalAlpha = alpha;
        ctx.save();
        ctx.beginPath();
        // 只保留内部阴影部分)
        ctx.arc(0, 0, radius, 0, 2 * Math.PI);
        ctx.clip();
        // 边框+阴影
        ctx.beginPath();
        ctx.lineWidth = lineWidth;
        ctx.shadowColor = shadowColor || '#3FCF8F';
        ctx.shadowBlur = 0;
        ctx.arc(0, 0, radius + lineWidth / 2 + 1, 0, 2 * Math.PI);
        ctx.fillStyle = fillColor; // 设置填充颜色
        ctx.fill(); // 开始填充
        ctx.stroke();
        ctx.shadowBlur = 0;
        ctx.restore();
    }

    /**
     * 绘制水滴型图形
     * @param {object} options 绘制水滴型传入参数
     * @param {string} options.fillColor 填充颜色
     * @param {number} options.radius 半径,默认是画板的一半宽
     * @param {number} options.alpha 颜色的透明度
     * @param {number} options.a 椭圆的长轴,radius包含长轴
     * @param {number} options.b 椭圆的短轴 radius=>AE; a=>BE; b=>BD
     * @param {number} options.rotate 旋转角度,0是向上,逆时针旋转
     * @param {*} options
     */
    drawWaterDrop(options) {
        const { ctx, width, shadowColor, color } = this;
        const { fillColor, radius = width / 2, alpha = 1, a, b, rotate = 0, lineWidth = 2 } = options;
        ctx.fillStyle = fillColor || shadowColor || color;
        const AB = radius - a;
        ctx.save();
        // 开始新路径
        ctx.beginPath();
        ctx.globalAlpha = alpha;
        // 指定起点,建立子路径
        ctx.moveTo(0, 0);
        // 指定起点,建立子路径
        ctx.rotate((rotate * Math.PI) / 180);
        // 绘制二次贝塞尔曲线
        ctx.quadraticCurveTo(b, -AB / 2, b, -AB);
        // 绘制半椭圆
        ctx.ellipse(0, -AB, b, a, 0, 0, Math.PI, true);
        // 绘制二次贝塞尔曲线
        ctx.quadraticCurveTo(-b, -AB / 2, 0, 0);
        ctx.fill();
        ctx.strokeStyle = fillColor;
        ctx.lineWidth = lineWidth;
        ctx.stroke();
        ctx.restore();
    }

    // 得到图片的路径,返回的图片的base64编码
    getImage() {
        return this.canvas.toDataURL('image/png');
    }
}

// 定义中心点
const centerPoint = {
    longitude: 112.0285,
    latitude: 37.43843,
};

// 定义水滴图案的参数
const option = {
    id: '61974198357e00002d0039f5',
    longitude: 112.0285,
    latitude: 37.43843,
    isShow: true,
    rotate: 360,
    rounds: [
        {
            color: 'green',
            alpha: 0.4,
            radius: 30,
            a: 9,
            b: 7.5,
        },
        {
            color: 'yellow',
            radius: 20,
            alpha: 0.4,
            a: 6,
            b: 5,
        },
        {
            color: 'red',
            radius: 14,
            alpha: 0.4,
            a: 4,
            b: 3,
        },
    ],
};

// 生成水滴图像的函数
function _getWaterDropImage(options) {
    const { rounds = [], maxRadius = 30, rotate = 0 } = options;
    const canvasGraphic = new CanvasGraphic({ width: 500, lineWidth: 2 });

    const unitRadiusWidth = canvasGraphic.width / (2 * maxRadius); // 单位半径对应的宽度
    if (rounds.length) {
        rounds.forEach((round) => {
            const { radius, color, alpha, a, b } = round;
            canvasGraphic.drawWaterDrop({
                radius: radius * unitRadiusWidth,
                fillColor: color,
                alpha,
                a: a * unitRadiusWidth,
                b: b * unitRadiusWidth,
                rotate,
            });
        });
    }

    return canvasGraphic.getImage(); // 返回 base64 图片
}

// 将水滴图形添加到 Cesium
function addWaterDropToCesium(viewer, imageBase64, centerPoint) {
    viewer.scene.primitives.add(
        new Cesium.GroundPrimitive({
            geometryInstances: new Cesium.GeometryInstance({
                geometry: new Cesium.CircleGeometry({
                    center: Cesium.Cartesian3.fromDegrees(centerPoint.longitude, centerPoint.latitude),
                    radius: 50, // 半径可根据需要调整
                }),
            }),
            appearance: new Cesium.Appearance({
                material: new Cesium.Material({
                    fabric: {
                        type: 'Image',
                        uniforms: {
                            image: imageBase64, // 使用生成的水滴图像
                        },
                    },
                }),
                flat: true,
            }),
        })
    );
}

// 获取水滴图像
const waterDropImage = _getWaterDropImage(option);

// 将水滴图像添加到 Cesium 场景
addWaterDropToCesium(viewer, waterDropImage, centerPoint);

// 添加中心点标记
viewer.entities.add({
    position: Cesium.Cartesian3.fromDegrees(centerPoint.longitude, centerPoint.latitude),
    point: {
        pixelSize: 10, // 点的大小
        color: Cesium.Color.RED, // 点的颜色
        outlineColor: Cesium.Color.WHITE, // 外轮廓的颜色
        outlineWidth: 2, // 外轮廓的宽度
        heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
    },
});

// 飞到中心点
viewer.camera.flyTo({
    destination: Cesium.Cartesian3.fromDegrees(centerPoint.longitude, centerPoint.latitude, 500),
});

圆型

![[Pasted image 20241016104312.png]]

javascript
// 假设 Cesium 和 CanvasGraphic 已经导入

const viewer = new Cesium.Viewer('cesiumContainer', {

    terrain: Cesium.Terrain.fromWorldTerrain(),

});

  

class CanvasGraphic {

    constructor(options = {}) {

        const { color = '#3FCF8F', width = 500, lineWidth = 1, shadowColor, shadowBlur = 24 } = options;

        this.color = color;

        this.width = width;

        this.height = width;

        this.lineWidth = lineWidth;

        this.shadowColor = shadowColor || color;

        this.shadowBlur = shadowBlur;

  

        const canvas = document.createElement('canvas');

        const ctx = canvas.getContext('2d');

        this.canvas = canvas;

        this.ctx = ctx;

        ctx.canvas.height = width;

        ctx.canvas.width = width;

        // 将坐标移到画布中心,不管画什么图,都需要移到中间,使用图层叠加时不会偏移

        ctx.translate(width / 2, width / 2);

        ctx.moveTo(0, 0);

    }

  

    /**

     * 绘制圆形

     * @param {object} options 绘制圆形传入参数

     * @param {string} options.fillColor 填充颜色

     * @param {number} options.radius 半径,默认是画板的一半宽

     * @param {number} options.alpha 颜色的透明度

     */

    drawCircle(options = {}) {

        const { ctx, width, shadowColor, color } = this;

        const { radius = width / 2, alpha = 1, lineWidth = 2, fillColor = color } = options;

        ctx.globalAlpha = alpha;

        ctx.save();

        ctx.beginPath();

        // 只保留内部阴影部分)

        ctx.arc(0, 0, radius, 0, 2 * Math.PI);

        ctx.clip();

        // 边框+阴影

        ctx.beginPath();

        ctx.lineWidth = lineWidth;

        ctx.shadowColor = shadowColor || '#3FCF8F';

        ctx.shadowBlur = 0;

        ctx.arc(0, 0, radius + lineWidth / 2 + 1, 0, 2 * Math.PI);

        ctx.fillStyle = fillColor; // 设置填充颜色

        ctx.fill(); // 开始填充

        ctx.stroke();

        ctx.shadowBlur = 0;

        ctx.restore();

    }

  

    /**

     * 绘制水滴型图形

     * @param {object} options 绘制水滴型传入参数

     * @param {string} options.fillColor 填充颜色

     * @param {number} options.radius 半径,默认是画板的一半宽

     * @param {number} options.alpha 颜色的透明度

     * @param {number} options.a 椭圆的长轴,radius包含长轴

     * @param {number} options.b 椭圆的短轴 radius=>AE; a=>BE; b=>BD

     * @param {number} options.rotate 旋转角度,0是向上,逆时针旋转

     * @param {*} options

     */

    drawWaterDrop(options) {

        const { ctx, width, shadowColor, color } = this;

        const { fillColor, radius = width / 2, alpha = 1, a, b, rotate = 0, lineWidth = 2 } = options;

        ctx.fillStyle = fillColor || shadowColor || color;

        const AB = radius - a;

        ctx.save();

        // 开始新路径

        ctx.beginPath();

        ctx.globalAlpha = alpha;

        // 指定起点,建立子路径

        ctx.moveTo(0, 0);

        // 指定起点,建立子路径

        ctx.rotate((rotate * Math.PI) / 180);

        // 绘制二次贝塞尔曲线

        ctx.quadraticCurveTo(b, -AB / 2, b, -AB);

        // 绘制半椭圆

        ctx.ellipse(0, -AB, b, a, 0, 0, Math.PI, true);

        // 绘制二次贝塞尔曲线

        ctx.quadraticCurveTo(-b, -AB / 2, 0, 0);

        ctx.fill();

        ctx.strokeStyle = fillColor;

        ctx.lineWidth = lineWidth;

        ctx.stroke();

        ctx.restore();

    }

  

    // 得到图片的路径,返回的图片的base64编码

    getImage() {

        return this.canvas.toDataURL('image/png');

    }

}

  

// 定义中心点

const centerPoint = {

    longitude: 112.0285,

    latitude: 37.43843,

};

  

// 圆形图案参数

const circleOption = {

    id: '61974198357e00002d0039f5',

    longitude: 112.0285,

    latitude: 37.43843,

    rounds: [

        {

            color: 'green',

            alpha: 0.4,

            radius: 30, // 圆的半径

        },

        {

            color: 'yellow',

            radius: 20,

            alpha: 0.4,

        },

        {

            color: 'red',

            radius: 14,

            alpha: 0.4,

        },

    ],

};

  

// 生成圆形图像的函数

function _getCircleImage(options) {

    const { rounds = [], maxRadius = 30 } = options;

    const canvasGraphic = new CanvasGraphic({ width: 500, lineWidth: 2 }); // 创建 CanvasGraphic 实例

  

    // 单位半径对应的宽度

    const unitRadiusWidth = canvasGraphic.width / (2 * maxRadius);

  

    if (rounds.length) {

        rounds.forEach((round) => {

            const { radius, color, alpha } = round;

            // 绘制圆形

            canvasGraphic.drawCircle({

                radius: radius * unitRadiusWidth,

                fillColor: color,

                alpha,

            });

        });

    }

  

    return canvasGraphic.getImage(); // 返回 base64 图片

}

  

// 在 Cesium 场景中显示圆形图案

function addCircleToCesium(viewer, imageBase64, centerPoint) {

    viewer.scene.primitives.add(

        new Cesium.GroundPrimitive({

            geometryInstances: new Cesium.GeometryInstance({

                geometry: new Cesium.CircleGeometry({

                    center: Cesium.Cartesian3.fromDegrees(centerPoint.longitude, centerPoint.latitude),

                    radius: 30, // 设置半径为 50 米

                }),

            }),

            appearance: new Cesium.Appearance({

                material: new Cesium.Material({

                    fabric: {

                        type: 'Image',

                        uniforms: {

                            image: imageBase64, // 使用生成的圆形图案

                        },

                    },

                }),

                flat: true,

            }),

        })

    );

}

  

// 获取圆形图像

const circleImage = _getCircleImage(circleOption);

  

// 将圆形图像添加到 Cesium 场景中

addCircleToCesium(viewer, circleImage, centerPoint);

  

// 添加中心点标记

viewer.entities.add({

    position: Cesium.Cartesian3.fromDegrees(centerPoint.longitude, centerPoint.latitude),

    point: {

        pixelSize: 10, // 点的大小

        color: Cesium.Color.RED, // 点的颜色

        outlineColor: Cesium.Color.WHITE, // 外轮廓的颜色

        outlineWidth: 2, // 外轮廓的宽度

        heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,

    },

});

  

// 飞到中心点

viewer.camera.flyTo({

    destination: Cesium.Cartesian3.fromDegrees(centerPoint.longitude, centerPoint.latitude, 500),

});

水滴加标签

![[Pasted image 20241016114817.png]]

javascript
// 假设 Cesium 和 CanvasGraphic 已经导入

const viewer = new Cesium.Viewer('cesiumContainer', {

    terrain: Cesium.Terrain.fromWorldTerrain(),

});

class CanvasGraphic {

    constructor(options = {}) {

        const { color = '#3FCF8F', width = 500, lineWidth = 1, shadowColor, shadowBlur = 24 } = options;

        this.color = color;

        this.width = width;

        this.height = width;

        this.lineWidth = lineWidth;

        this.shadowColor = shadowColor || color;

        this.shadowBlur = shadowBlur;

  

        const canvas = document.createElement('canvas');

        const ctx = canvas.getContext('2d');

        this.canvas = canvas;

        this.ctx = ctx;

        ctx.canvas.height = width;

        ctx.canvas.width = width;

        // 将坐标移到画布中心,不管画什么图,都需要移到中间,使用图层叠加时不会偏移

        ctx.translate(width / 2, width / 2);

        ctx.moveTo(0, 0);

    }

  

    /**

     * 绘制圆形

     * @param {object} options 绘制圆形传入参数

     * @param {string} options.fillColor 填充颜色

     * @param {number} options.radius 半径,默认是画板的一半宽

     * @param {number} options.alpha 颜色的透明度

     */

    drawCircle(options = {}) {

        const { ctx, width, shadowColor, color } = this;

        const { radius = width / 2, alpha = 1, lineWidth = 2, fillColor = color, label = '' } = options;

        ctx.globalAlpha = alpha;

        ctx.save();

        ctx.beginPath();

        // 只保留内部阴影部分)

        ctx.arc(0, 0, radius, 0, 2 * Math.PI);

        ctx.clip();

        // 边框+阴影

        ctx.beginPath();

        ctx.lineWidth = lineWidth;

        ctx.shadowColor = shadowColor || '#3FCF8F';

        ctx.shadowBlur = 0;

        ctx.arc(0, 0, radius + lineWidth / 2 + 1, 0, 2 * Math.PI);

        ctx.fillStyle = fillColor; // 设置填充颜色

        ctx.fill(); // 开始填充

        ctx.stroke();

        ctx.shadowBlur = 0;

        ctx.restore();

  

        // 绘制标签

        if (label) {

            this.drawLabel(label, radius);

        }

    }

  

    /**

     * 绘制水滴型图形

     * @param {object} options 绘制水滴型传入参数

     * @param {string} options.fillColor 填充颜色

     * @param {number} options.radius 半径,默认是画板的一半宽

     * @param {number} options.alpha 颜色的透明度

     * @param {number} options.a 椭圆的长轴,radius包含长轴

     * @param {number} options.b 椭圆的短轴 radius=>AE; a=>BE; b=>BD

     * @param {number} options.rotate 旋转角度,0是向上,逆时针旋转

     * @param {*} options

     */

    drawWaterDrop(options) {

        const { ctx, width, shadowColor, color } = this;

        const { fillColor, radius = width / 2, alpha = 1, a, b, rotate = 0, lineWidth = 2, label = '' } = options;

        ctx.fillStyle = fillColor || shadowColor || color;

        const AB = radius - a;

        ctx.save();

        // 开始新路径

        ctx.beginPath();

        ctx.globalAlpha = alpha;

        // 指定起点,建立子路径

        ctx.moveTo(0, 0);

        // 指定起点,建立子路径

        ctx.rotate((rotate * Math.PI) / 180);

        // 绘制二次贝塞尔曲线

        ctx.quadraticCurveTo(b, -AB / 2, b, -AB);

        // 绘制半椭圆

        ctx.ellipse(0, -AB, b, a, 0, 0, Math.PI, true);

        // 绘制二次贝塞尔曲线

        ctx.quadraticCurveTo(-b, -AB / 2, 0, 0);

        ctx.fill();

        ctx.strokeStyle = fillColor;

        ctx.lineWidth = lineWidth;

        ctx.stroke();

        ctx.restore();

        // 绘制标签

        if (label) {

            this.drawLabel(label, radius);

        }

    }

  

    // 绘制标签方法

    drawLabel(label, radius) {

        const { ctx } = this;

        ctx.save();

        ctx.fillStyle = '#000'; // 标签颜色

        ctx.font = '16px Arial'; // 标签字体

        ctx.textAlign = 'center';

        ctx.textBaseline = 'middle';

  

        const x = 0; // 标签的X坐标

        const y = -(radius - 20); // 标签的Y坐标

  

        ctx.fillText(label, x, y); // 在指定位置绘制标签

        ctx.restore();

    }

  

    // 得到图片的路径,返回的图片的base64编码

    getImage() {

        return this.canvas.toDataURL('image/png');

    }

}

  

// 定义中心点

const centerPoint = {

    longitude: 112.0285,

    latitude: 37.43843,

};

  

// 定义水滴图案的参数

const option = {

    id: '61974198357e00002d0039f5',

    longitude: 112.0285,

    latitude: 37.43843,

    isShow: true,

    rotate: 360,

    rounds: [

        {

            color: 'green',

            alpha: 0.4,

            radius: 30,

            a: 9,

            b: 7.5,

            label: 'Green Circle', // 圆的标签

        },

        {

            color: 'yellow',

            radius: 20,

            alpha: 0.4,

            a: 6,

            b: 5,

            label: 'Yellow Circle',

        },

        {

            color: 'red',

            radius: 14,

            alpha: 0.4,

            a: 4,

            b: 3,

            label: 'Red Circle',

        },

    ],

};

  

// 生成水滴图像的函数

function _getWaterDropImage(options) {

    const { rounds = [], maxRadius = 30, rotate = 0 } = options;

    const canvasGraphic = new CanvasGraphic({ width: 500, lineWidth: 2 });

  

    const unitRadiusWidth = canvasGraphic.width / (2 * maxRadius); // 单位半径对应的宽度

    if (rounds.length) {

        rounds.forEach((round, index) => {

            const { radius, color, alpha, a, b, label } = round;

            canvasGraphic.drawWaterDrop({

                radius: radius * unitRadiusWidth,

                fillColor: color,

                alpha,

                a: a * unitRadiusWidth,

                b: b * unitRadiusWidth,

                rotate,

                label: label || `Label ${index + 1}`, // 默认标签

            });

        });

    }

  

    return canvasGraphic.getImage(); // 返回 base64 图片

}

  

// 将水滴图形添加到 Cesium

function addWaterDropToCesium(viewer, imageBase64, centerPoint) {

    viewer.scene.primitives.add(

        new Cesium.GroundPrimitive({

            geometryInstances: new Cesium.GeometryInstance({

                geometry: new Cesium.CircleGeometry({

                    center: Cesium.Cartesian3.fromDegrees(centerPoint.longitude, centerPoint.latitude),

                    radius: 50, // 半径可根据需要调整

                }),

            }),

            appearance: new Cesium.Appearance({

                material: new Cesium.Material({

                    fabric: {

                        type: 'Image',

                        uniforms: {

                            image: imageBase64, // 使用生成的水滴图像

                        },

                    },

                }),

                flat: true,

            }),

        })

    );

}

  

// 获取水滴图像

const waterDropImage = _getWaterDropImage(option);

  

// 将水滴图像添加到 Cesium 场景

addWaterDropToCesium(viewer, waterDropImage, centerPoint);

  

// 添加中心点标记

viewer.entities.add({

    position: Cesium.Cartesian3.fromDegrees(centerPoint.longitude, centerPoint.latitude),

    point: {

        pixelSize: 10, // 点的大小

        color: Cesium.Color.RED, // 点的颜色

        outlineColor: Cesium.Color.WHITE, // 外轮廓的颜色

        outlineWidth: 2, // 外轮廓的宽度

        heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,

    },

});

  

// 飞到中心点

viewer.camera.flyTo({

    destination: Cesium.Cartesian3.fromDegrees(centerPoint.longitude, centerPoint.latitude, 500),

});

圆形加标签

![[Pasted image 20241016115005.png]]

javascript
// 假设 Cesium 和 CanvasGraphic 已经导入

const viewer = new Cesium.Viewer('cesiumContainer', {

    terrain: Cesium.Terrain.fromWorldTerrain(),

});

  

class CanvasGraphic {

    constructor(options = {}) {

        const { color = '#3FCF8F', width = 500, lineWidth = 1, shadowColor, shadowBlur = 24 } = options;

        this.color = color;

        this.width = width;

        this.height = width;

        this.lineWidth = lineWidth;

        this.shadowColor = shadowColor || color;

        this.shadowBlur = shadowBlur;

  

        const canvas = document.createElement('canvas');

        const ctx = canvas.getContext('2d');

        this.canvas = canvas;

        this.ctx = ctx;

        ctx.canvas.height = width;

        ctx.canvas.width = width;

        ctx.translate(width / 2, width / 2); // 将坐标移到画布中心

    }

  

    // 绘制圆形

    drawCircle(options = {}) {

        const { ctx, width, shadowColor, color } = this;

        const { radius = width / 2, alpha = 1, lineWidth = 2, fillColor = color, label = '' } = options;

        ctx.globalAlpha = alpha;

        ctx.save();

        ctx.beginPath();

        ctx.arc(0, 0, radius, 0, 2 * Math.PI);

        ctx.clip();

        ctx.beginPath();

        ctx.lineWidth = lineWidth;

        ctx.shadowColor = shadowColor || '#3FCF8F';

        ctx.shadowBlur = 0;

        ctx.arc(0, 0, radius + lineWidth / 2 + 1, 0, 2 * Math.PI);

        ctx.fillStyle = fillColor;

        ctx.fill();

        ctx.stroke();

        ctx.restore();

  

        // 绘制标签

        if (label) {

            this.drawLabel(label, radius);

        }

    }

  

    // 绘制标签方法

    drawLabel(label, radius) {

        const { ctx } = this;

        ctx.save();

        ctx.fillStyle = '#000'; // 标签颜色

        ctx.font = '16px Arial'; // 标签字体

        ctx.textAlign = 'center';

        ctx.textBaseline = 'middle';

  

        const x = 0; // 标签的X坐标

        const y = -(radius - 20); // 标签的Y坐标,放在圆的上方

  

        ctx.fillText(label, x, y); // 在指定位置绘制标签

        ctx.restore();

    }

  

    getImage() {

        return this.canvas.toDataURL('image/png');

    }

}

// 定义中心点

const centerPoint = {

    longitude: 112.0285,

    latitude: 37.43843,

};

  

// 圆形图案参数

const circleOption = {

    id: '61974198357e00002d0039f5',

    longitude: 112.0285,

    latitude: 37.43843,

    rounds: [

        {

            color: 'green',

            alpha: 0.4,

            radius: 30, // 圆的半径

            label: 'Green Circle', // 圆的标签

        },

        {

            color: 'yellow',

            radius: 20,

            alpha: 0.4,

            label: 'Yellow Circle',

        },

        {

            color: 'red',

            radius: 14,

            alpha: 0.4,

            label: 'Red Circle',

        },

    ],

};

  

// 使用修改后的代码,绘制带有标签的圆形图像

function _getCircleImageWithLabels(options) {

    const { rounds = [], maxRadius = 30 } = options;

    const canvasGraphic = new CanvasGraphic({ width: 500, lineWidth: 2 });

  

    const unitRadiusWidth = canvasGraphic.width / (2 * maxRadius);

  

    if (rounds.length) {

        rounds.forEach((round, index) => {

            const { radius, color, alpha, label } = round;

            canvasGraphic.drawCircle({

                radius: radius * unitRadiusWidth,

                fillColor: color,

                alpha,

                label: label || `Label ${index + 1}`, // 默认标签

            });

        });

    }

  

    return canvasGraphic.getImage();

}

  

// 在 Cesium 场景中显示圆形图案

function addCircleToCesium(viewer, imageBase64, centerPoint) {

    viewer.scene.primitives.add(

        new Cesium.GroundPrimitive({

            geometryInstances: new Cesium.GeometryInstance({

                geometry: new Cesium.CircleGeometry({

                    center: Cesium.Cartesian3.fromDegrees(centerPoint.longitude, centerPoint.latitude),

                    radius: 30, // 设置半径为 50 米

                }),

            }),

            appearance: new Cesium.Appearance({

                material: new Cesium.Material({

                    fabric: {

                        type: 'Image',

                        uniforms: {

                            image: imageBase64, // 使用生成的圆形图案

                        },

                    },

                }),

                flat: true,

            }),

        })

    );

}

  

// 获取带标签的圆形图像

const circleImageWithLabels = _getCircleImageWithLabels(circleOption);

  

// 在 Cesium 场景中显示带有标签的圆形图像

addCircleToCesium(viewer, circleImageWithLabels, centerPoint);

  

// 添加中心点标记

viewer.entities.add({

    position: Cesium.Cartesian3.fromDegrees(centerPoint.longitude, centerPoint.latitude),

    point: {

        pixelSize: 10, // 点的大小

        color: Cesium.Color.RED, // 点的颜色

        outlineColor: Cesium.Color.WHITE, // 外轮廓的颜色

        outlineWidth: 2, // 外轮廓的宽度

        heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,

    },

});

  

// 飞到中心点

viewer.camera.flyTo({

    destination: Cesium.Cartesian3.fromDegrees(centerPoint.longitude, centerPoint.latitude, 500),

});

Updated at: