【收藏】Cesium billboard添加icon图片、label文字带背景图片、自定义广告牌(使用canvas绘制实现,附完整源码,vue2或vue3+vite都适用)

1.效果

       在项目开发过程中,有一个需求是有若干个需要展示的点,每个点icon不一样、对应的广告牌文字不一样、并且文字还需要有图片背景、每个文字背景也不同(抓狂)。这种需求只能编写canvas来绘制“icon+文字背景+文字”的image,并返回给billboard。

        实测100个点渲染,我这小破电脑花费140ms,能够满足项目需求;作为一个新手切图崽,所编写的代码还有很多改进的地方,有请各位大佬在评论区批评指正。


2.代码

       调用renderBillboard(options)方法,将返回一个billboard对象,返回值可以直接使用。也可以使用回调,renderBillboard(options,(result)=>{}),result包含billboard所需的image和pixelOffset。

       需要注意的是,vue3+vite加载图片资源使用的是new URL("地址", import.meta.url).href

而vue2加载图片资源使用的是require,在代码中均有,可以根据需要使用。

import * as Cesium from "cesium";
let canvas, context;
/**
 * @author: linmaoxin
 * @date: 2024/12/14
 * @description 渲染billboard,可选择 带背景的文字+图标icon、仅背景的文字 两种类型。1、返回Promise对象,异步获取billboard;2、也可以使用回调分别获取image和pixelOffset。
 * @param {*} options
 * @callback function
 * @returns Promise<object>
 * @example
 * let options = {
        type: 'text-icon',  // 默认 'text-icon'类型(图标+文字)、 'text' 类型(仅文字)
        paddingTo: 20, // 图标距离文字背景
        textPadding: 20, // 文字左右间距
        textBcgWidth: 200, // 文字背景宽度
        textBcgHeight: 50, // 文字背景高度
        iconWidth: 60, // 图标宽度
        iconHeight: 70, // 图标高度
        textBcgUrl: '../assets/label-bcg.png', // 文字背景图片地址 
        iconUrl: '../assets/positionIcon.png', // 图标地址
        textUrl:'', // 字体地址
        text: '测试文字测试文字', // 文字内容
        textColor: '#DCD085', // 字体颜色
        textFontSize: 16, // 字体大小
        textFontWeight: 800, // 字体粗细
    } 
 */
export function renderBillboard(options, callback) {
    let settings = {
        type: options.type || 'text-icon', // 默认 'text-icon'类型(图标+文字)、 'text' 类型(仅文字)
        paddingTo: options.paddingTo || 5, // 默认 图标距离文字背景
        textPadding: options.textPadding || 20, // 默认 文字左右间距

        textBcgWidth: options.textBcgWidth || 150, // 默认 文字背景宽度
        textBcgHeight: options.textBcgHeight || 40,  // 默认 文字背景高度

        iconWidth: options.iconWidth || 40,  // 默认 图标宽度
        iconHeight: options.iconHeight || 50, // 默认 图标高度

        textBcgUrl: options.textBcgUrl || console.error('请传入文字背景图片地址 textBcgUrl '), // 文字背景图片地址 
        iconUrl: options.iconUrl || console.error('请传入图标图片地址 iconUrl '), // 图标地址
        textUrl: options.textUrl, // 文字地址

        text: options.text || '', // 文字内容
        textColor: options.textColor || '#ffffff', // 默认 文字颜色
        textFontSize: options.textFontSize || 16, // 默认 文字大小
        textFontWeight: options.textFontWeight || 500, //默认 文字粗细
    }

    if (!canvas) {
        canvas = document.createElement('canvas');
    }
    if (!context) {
        context = canvas.getContext('2d', { willReadFrequently: true });
    }
    // 字体初始化
    context.font = `${Number(settings.textFontWeight)} ${Number(settings.textFontSize)}px Microsoft Yahei`;
    context.fillStyle = settings.textColor;
    context.textAlign = 'center';
    context.textBaseline = 'middle';
    context.canvas.willReadFrequently = true;
    // 根据字数计算宽度 并加上左右间距
    const textMetrics = context.measureText(settings.text);
    settings.textBcgWidth = textMetrics.width + settings.textPadding;
    // 画布大小
    if (settings.type === 'text-icon') {
        settings.textBcgWidth = settings.textBcgWidth > settings.iconWidth ? settings.textBcgWidth : settings.iconWidth;
        canvas.width = settings.textBcgWidth;
        canvas.height = settings.textBcgHeight + settings.paddingTo + settings.iconHeight;
    } else if (settings.type === 'text') {
        canvas.width = settings.textBcgWidth;
        canvas.height = settings.textBcgHeight;
    }
    return new Promise((result) => {
        // 加载图片资源
        if (settings.type === 'text-icon') {
            // 字体二次设置
            context.font = `${Number(settings.textFontWeight)} ${Number(settings.textFontSize)}px Microsoft Yahei`;
            context.fillStyle = settings.textColor;
            context.textAlign = 'center';
            context.textBaseline = 'middle';
            const image1 = new Image();
            const image2 = new Image();
            //image1.src = require(settings.iconUrl); //图标地址 vue2
            // image2.src = require(settings.textBcgUrl); //文字背景图片地址 vue2
            image1.src = new URL(settings.iconUrl, import.meta.url).href // vite
            image2.src = new URL(settings.textBcgUrl, import.meta.url).href // vite

            image1.onload = () => {
                context.drawImage(image1, 0, 0, canvas.width, canvas.height);
            }
            image2.onload = () => {
                context.drawImage(image2, 0, 0, canvas.width, canvas.height);
            }
            Promise.all([
                new Promise(resolve1 => { image1.onload = resolve1; }),
                new Promise(resolve2 => { image2.onload = resolve2; })
            ]).then(() => {
                // 清除画布
                context.clearRect(0, 0, canvas.width, canvas.height);
                context.beginPath();

                // 渲染
                context.drawImage(image2, 0, 0, settings.textBcgWidth, settings.textBcgHeight);
                context.drawImage(image1, (canvas.width / 2) - (settings.iconWidth / 2), (settings.textBcgHeight + settings.paddingTo), settings.iconWidth, settings.iconHeight);
                context.fillText(settings.text, settings.textBcgWidth / 2, settings.textBcgHeight / 2);

                //  判断callback是不是函数
                if (typeof callback === 'function') {
                    callback({ 'image': canvas.toDataURL(), 'pixelOffsetY': Math.round(-canvas.height / 2) })
                }
                result({
                    image: canvas.toDataURL(),
                    pixelOffset: new Cesium.Cartesian2(0, Math.round(-canvas.height / 2))
                })
                // console.log('canvas', canvas.height)

            });

        } else if (settings.type === 'text') {
            const image2 = new Image();
            //image2.src = require(settings.textBcgUrl); //文字背景图片地址 vue2
            image2.src = new URL(settings.textBcgUrl, import.meta.url).href // vite
            image2.onload = () => {
                // 清除画布
                context.clearRect(0, 0, canvas.width, canvas.height);
                context.beginPath();
                // 渲染
                context.drawImage(image2, 0, 0, canvas.width, canvas.height);
                context.fillText(settings.text, settings.textBcgWidth / 2, settings.textBcgHeight / 2);

                //  判断callback是不是函数
                if (typeof callback === 'function') {
                    callback({ 'image': canvas.toDataURL(), 'pixelOffsetY': Math.round(-canvas.height / 2), })
                }
                result({
                    image: canvas.toDataURL(),
                    pixelOffset: new Cesium.Cartesian2(0, Math.round(-canvas.height / 2))
                })
            }
        }
    })

}


3.结尾

(1)加载图片资源其实可以用 new Cesium.Resource.fetchImage(url).then(image => {}这个方法,这是Cesium的一个异步加载图片资源的方法,并不一定要使用 new Image。

(2)实现过程本文章不过多赘述,有不懂的地方可以评论区讨论、或者问问AI。


(PS:创造不易,如果文章对你有帮助,可以点个赞鼓励下博主哦!)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WebGIS皮卡茂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值