leaflet,canvas渲染目标,可加载大批量数据

基于Leaflet-CanvasMarker: 在Canvas上绘制Marker,而不是每个marker插件一个dom节点,极大地提高了渲染效率。主要代码参考自 https://github.com/eJuke/Leaflet.Canvas-Markers,不过此插件有些Bug,github国内不方便,作者也不维护了,所以在gitee上新建一个仓库进行维护。icon-default.png?t=N7T8https://gitee.com/panzhiyue/Leaflet-CanvasMarker 修改的canvas渲染marker

参数

collisionFlg

  • 类型:boolean
  • 默认值:false

配置是否启用碰撞检测,即重叠图标只显示一个

moveReset

  • 类型:boolean
  • 默认值:false

在move时是否刷新地图

zIndex

  • 类型:number
  • 默认值:null

Leaflet.Marker对象zIndex的默认值

opacity

  • 类型:number
  • 默认值:1

图层的不透明度,0(完全透明)-1(完全不透明)

Leaflet.Marker扩展参数

zIndex

  • 类型:number

显示顺序

Leaflet.Icon扩展参数

rotate

  • 类型:number

旋转角度:Math.PI/2

方法

  • addLayer(marker):向图层添加标记。
  • addLayers(markers):向图层添加标记。
  • removeLayer(marker, redraw):从图层中删除一个标记。redraw为true时删除后直接重绘,默认为true
  • redraw() : 重绘图层
  • addOnClickListener(eventHandler):为所有标记添加通用点击侦听器
  • addOnHoverListener(eventHandler):为所有标记添加悬停监听器
  • addOnMouseDownListener(eventHandler):为所有标记添加鼠标按下监听器
  • addOnMouseUpListener(eventHandler):为所有标记添加鼠标松开监听器

使用:
/**
 * 加载marker 点位
 *
 * */

export const useLoadMarker = () => {
    let MapCanvasLayer = null;

    // 添加10W个数据点
    let addLargeNumberMarker = () => {
        console.time("加载渲染canvas目标");
        // 创建图层
        MapCanvasLayer = L.canvasMarkerLayer({
            collisionFlg: false,
            moveReset: false,
            userDrawFunc: function (layer, marker, pointPos, size) {
                // console.log("lllllllllllll", layer);
                const ctx = layer._ctx;
                ctx.beginPath();
                ctx.arc(pointPos.x, pointPos.y, size[0] / 2, 0, 2 * Math.PI);
                ctx.fillStyle = "rgba(255,12,0,0.4)";
                ctx.fill();
                ctx.closePath();
            },
        }).addTo(window.MapLeaflet);

        let icon = L.icon({
            iconUrl: new URL("../assets/marker.jpg", import.meta.url).href,
            iconSize: [20, 20],
            iconAnchor: [10, 9],
        });

        // 定义Marker
        let markers = [];
        for (var i = 0; i < 80000; i++) {
            let marker = L.marker([39.26203 + Math.random() * 1.2, 122.58546 + Math.random() * 2], {
                icon: L.icon({
                    iconSize: [20, 20],
                     iconAnchor: [10, 9],
                }),
                zIndex: 2,
                riseOnHover: true,
            });
            marker.bindTooltip("我是浮动出来的标记" + i, {
                //添加提示文字
                permanent: false, //是永久打开还是悬停打开
                direction: "top", //方向
            }); //在图层打开
            markers.push(marker);
        }
        // 把marker添加到图层
        MapCanvasLayer.addLayers(markers);
        console.timeEnd("加载渲染canvas目标");
        //定义事件
        MapCanvasLayer.addOnClickListener(function (e, data) {
            console.log(data);
        });
        MapCanvasLayer.addOnHoverListener(function (e, data) {
            console.log(data[0].data);
        });
    };

    return {
        addLargeNumberMarker,
    };
};

修改版本v1.0,可以自定义画marker,使用canvas画marrker再添加到canvas上

import rbush from "rbush"; //https://www.5axxw.com/wiki/content/7wjc4t
/**
 * @typedef {Object} MarkerData marker的rubsh数据
 * @property {Number} MarkerData.minX  marker的经度
 * @property {Number} MarkerData.minY  marker的纬度
 * @property {Number} MarkerData.maxX  marker的经度
 * @property {Number} MarkerData.maxY  marker的纬度
 * @property {L.Marker} MarkerData.data  marker对象
 * @example
 * let latlng=marker.getLatlng();
 * let markerData={
 *      minX:latlng.lng,
 *      minY:latlng.lat,
 *      maxX:latlng.lng,
 *      maxY:latlng.lat,
 *      data:marker
 * }
 */

/**
 * @typedef {Object} MarkerBoundsData marker的像素边界rubsh数据
 * @property {Number} MarkerBoundsData.minX  marker的左上角x轴像素坐标
 * @property {Number} MarkerBoundsData.minY  marker的左上角y轴像素坐标
 * @property {Number} MarkerBoundsData.maxX  marker的右下角x轴像素坐标
 * @property {Number} MarkerBoundsData.maxY  marker的右下角y轴像素坐标
 * @property {L.Marker} MarkerBoundsData.data  marker对象
 * @example
 * let options = marker.options.icon.options;
 * let minX, minY, maxX, maxY;
 * minX = pointPos.x - ((options.iconAnchor && options.iconAnchor[0]) || 0);
 * maxX = minX + options.iconSize[0];
 * minY = pointPos.y - ((options.iconAnchor && options.iconAnchor[1]) || 0);
 * maxY = minY + options.iconSize[1];
 *
 * let markerBounds = {
 *     minX,
 *     minY,
 *     maxX,
 *     maxY
 * };
 */

/**
 * 用于在画布而不是DOM上显示标记的leaflet插件。使用单页1.0.0及更高版本。
 * 已修改源码适配项目需求
 * 注意:开启碰撞检测 图标重叠合并 L.icon({ iconAnchor: [10, 9],}); 必须要配置iconAnchor属性
 */
export var CanvasMarkerLayer = (L.CanvasMarkerLayer = L.Layer.extend({
    options: {
        zIndex: null, //图层dom元素的堆叠顺序
        collisionFlg: false, //碰撞检测,使用canvas画marker时 不建议开启碰撞会影响性能
        moveReset: false, //在move时是否刷新地图
        opacity: 1, //图层透明度
        userDrawFunc: null, //改源码:自定义canvas画图标
    },
    //Add event listeners to initialized section.
    initialize: function (options) {
        L.setOptions(this, options);
        this._onClickListeners = [];
        this._onHoverListeners = [];
        this._onMouseDownListeners = [];
        this._onMouseUpListeners = [];

        /**
         * 所有marker的集合
         * @type {rbush<MarkerData>}
         */
        this._markers = new rbush();
        this._markers.dirty = 0; //单个插入/删除
        this._markers.total = 0; //总数

        /**
         * 在地图当前范围内的marker的集合
         * @type {rbush<MarkerData>}
         */
        this._containMarkers = new rbush();

        /**
         * 当前显示在地图上的marker的集合
         * @type {rbush<MarkerData>}
         */
        this._showMarkers = new rbush();

        /**
         * 当前显示在地图上的marker的范围集合
         * @type {rbush<MarkerBoundsData>}
         */
        this._showMarkerBounds = new rbush();
    },

    setOptions: function (options) {
        L.setOptions(this, options);

        return this.redraw();
    },

    /**
     * 重绘
     */
    redraw: function () {
        return this._redraw(true);
    },

    /**
     * 获取事件对象
     *
     * 表示给map添加的监听器
     * @return {Object} 监听器/函数键值对
     */
    getEvents: function () {
        var events = {
            viewreset: this._reset,
            zoom: this._onZoom,
            moveend: this._reset,
            click: this._executeListeners,
            mousemove: this._executeListeners,
            mousedown: this._executeListeners,
            mouseup: this._executeListeners,
        };
        if (this._zoomAnimated) {
            events.zoomanim = this._onAnimZoom;
        }
        if (this.options.moveReset) {
            events.move = this._reset;
        }
        return events;
    },

    /**
     * 添加标注
     * @param {L/Marker} layer 标注
     * @return {Object} this
     */
    addLayer: function (layer, redraw = true) {
        if (!(layer.options.pane == "markerPane" && layer.options.icon)) {
            console.error("Layer isn't a marker");
            return;
        }

        layer._map = this._map;
        var latlng = layer.getLatLng();

        L.Util.stamp(layer);

        this._markers.insert({
            minX: latlng.lng,
            minY: latlng.lat,
            maxX: latlng.lng,
            maxY: latlng.lat,
            data: layer,
        });

        this._markers.dirty++;
        this._markers.total++;

        var isDisplaying = this._map.getBounds().contains(latlng);
        if (redraw == true && isDisplaying) {
            this._redraw(true);
        }
        return this;
    },

    /**
     * 添加标注数组,在一次性添加许多标注时使用此函数会比循环调用marker函数效率更高
     * @param {Array.<L/Marker>} layers 标注数组
     * @return {Object} this
     */
    addLayers: function (layers, redraw = true) {
        console.time("canvas-layer渲染时间")
        layers.forEach((layer) => {
            this.addLayer(layer, false);
        });
        if (redraw) {
            this._redraw(true);
        }
        console.timeEnd("canvas-layer渲染时间");
        return this;
    },

    /**
     * 删除标注
     * @param {*} layer 标注
     * @param {boolean=true} redraw 是否重新绘制(默认为true),如果要批量删除可以设置为false,然后手动更新
     * @return {Object} this
     */
    removeLayer: function (layer, redraw = true) {
        var self = this;

        //If we are removed point
        // 改掩码
        if (layer && layer["minX"]) layer = layer.data;

        var latlng = layer.getLatLng();
        var isDisplaying = self._map.getBounds().contains(latlng);

        var markerData = {
            minX: latlng.lng,
            minY: latlng.lat,
            maxX: latlng.lng,
            maxY: latlng.lat,
            data: layer,
        };

        self._markers.remove(markerData, function (a, b) {
            return a.data._leaflet_id === b.data._leaflet_id;
        });

        self._markers.total--;
        self._markers.dirty++;

        if (isDisplaying === true && redraw === true) {
            self._redraw(true);
        }
        return this;
    },

    /**
     * 清除所有
     */
    clearLayers: function () {
        this._markers = new rbush();
        this._markers.dirty = 0; //单个插入/删除
        this._markers.total = 0; //总数
        this._containMarkers = new rbush();
        this._showMarkers = new rbush();
        this._showMarkerBounds = new rbush();

        this._redraw(true);
    },

    /**
     * 继承L.Layer必须实现的方法
     *
     * 图层Dom节点创建添加到地图容器
     */
    onAdd: function (map) {
        this._map = map;

        if (!this._container) this._initCanvas();

        if (this.options.pane) this.getPane().appendChild(this._container);
        else map._panes.overlayPane.appendChild(this._container);

        this._reset();
    },

    /**
     * 继承L.Layer必须实现的方法
     *
     * 图层Dom节点销毁
     */
    onRemove: function (map) {
        if (this.options.pane) this.getPane().removeChild(this._container);
        else map.getPanes().overlayPane.removeChild(this._container);
    },

    /**
     * 绘制图标
     * @param {L/Marker} marker 图标
     * @param {L/Point} pointPos 图标中心点在屏幕上的像素位置
     */
    _drawMarker: function (marker, pointPos) {
        var self = this;
        //创建图标缓存
        if (!this._imageLookup) this._imageLookup = {};

        //没有传入像素位置,则计算marker自身的位置
        if (!pointPos) {
            pointPos = self._map.latLngToContainerPoint(marker.getLatLng());
        }
        // 改源码:添加构造方法userDrawFunc--canvas图标
        // S 配置是否启用碰撞检测,即重叠图标只显示一个,使用canvas画marker时 不建议开启碰撞会影响性能
        let options = marker.options.icon.options;
        let minX, minY, maxX, maxY;
        minX = pointPos.x - ((options.iconAnchor && options.iconAnchor[0]) || 0);
        maxX = minX + options.iconSize[0];
        minY = pointPos.y - ((options.iconAnchor && options.iconAnchor[1]) || 0);
        maxY = minY + options.iconSize[1];

        let markerBounds = {
            minX,
            minY,
            maxX,
            maxY,
        };

        if (this.options.collisionFlg == true) {
            if (this._showMarkerBounds.collides(markerBounds)) {
                return;
            } else {
                this._showMarkerBounds.insert(markerBounds);
                let latlng = marker.getLatLng();
                this._showMarkers.insert({
                    minX,
                    minY,
                    maxX,
                    maxY,
                    lng: latlng.lng,
                    lat: latlng.lat,
                    data: marker,
                });
            }
        }
        // E 配置是否启用碰撞检测,即重叠图标只显示一个,使用canvas画marker时 不建议开启碰撞会影响性能
        // console.log("图标------", marker.options.icon.options);
        if (marker.options.icon.options.iconUrl && !!marker.options.icon.options.iconUrl) {
            // 使用icon的marker执行

            //图标图片地址
            var iconUrl = marker.options.icon.options.iconUrl;

            //已经有canvas_img对象,表示之前已经绘制过,直接使用,提高渲染效率
            if (marker.canvas_img) {
                self._drawImage(marker, pointPos);
            } else {
                //图标已经在缓存中
                if (self._imageLookup[iconUrl]) {
                    marker.canvas_img = self._imageLookup[iconUrl][0];

                    //图片还未加载,把marker添加到预加载列表中
                    if (self._imageLookup[iconUrl][1] === false) {
                        self._imageLookup[iconUrl][2].push([marker, pointPos]);
                    } else {
                        //图片已经加载,则直接绘制
                        self._drawImage(marker, pointPos);
                    }
                } else {
                    //新的图片
                    //创建图片对象
                    var i = new Image();
                    i.src = iconUrl;
                    marker.canvas_img = i;

                    //Image:图片,isLoaded:是否已经加载,[[marker,pointPos]]:预加载列表
                    self._imageLookup[iconUrl] = [i, false, [[marker, pointPos]]];

                    //图片加载完毕,循环预加列表,绘制图标
                    i.onload = function () {
                        self._imageLookup[iconUrl][1] = true;
                        self._imageLookup[iconUrl][2].forEach(function (e) {
                            self._drawImage(e[0], e[1]);
                        });
                    };
                }
            }
        } else if (this.options.userDrawFunc && typeof this.options.userDrawFunc === "function") {
            // 使用canvas-userDrawFunc的marker执行
            const size = marker.options.icon.options.iconSize;
            this.options.userDrawFunc(this, marker, pointPos, size);
        }
    },

    /**
     * 绘制图标
     * @param {L/Marker} marker 图标
     * @param {L/Point} pointPos 图标中心点在屏幕上的像素位置
     */
    _drawImage: function (marker, pointPos) {
        var options = marker.options.icon.options;
        this._ctx.save();
        this._ctx.globalAlpha = this.options.opacity;
        this._ctx.translate(pointPos.x, pointPos.y);
        this._ctx.rotate(options.rotate);

        this._ctx.drawImage(
            marker.canvas_img,
            -((options.iconAnchor && options.iconAnchor[0]) || 0),
            -((options.iconAnchor && options.iconAnchor[1]) || 0),
            options.iconSize[0],
            options.iconSize[1]
        );
        this._ctx.restore();
    },

    /**
     * 重置画布(大小,位置,内容)
     */
    _reset: function () {
        var topLeft = this._map.containerPointToLayerPoint([0, 0]);
        L.DomUtil.setPosition(this._container, topLeft);
        var size = this._map.getSize();
        this._container.width = size.x;
        this._container.height = size.y;
        this._update();
    },

    /**
     * 重绘画布
     * @param {boolean} clear 是否清空
     */
    _redraw: function (clear) {
        console.time("canvas一次重绘时间");
        this._showMarkerBounds = new rbush();
        this._showMarkers = new rbush();
        var self = this;
        //清空画布
        if (clear) this._ctx.clearRect(0, 0, this._container.width, this._container.height);
        if (!this._map || !this._markers) return;

        var tmp = [];

        //如果单个插入/删除的数量超过总数的10%,则重建查找以提高效率
        if (self._markers.dirty / self._markers.total >= 0.1) {
            self._markers.all().forEach(function (e) {
                tmp.push(e);
            });

            self._markers.clear();
            self._markers.load(tmp);
            self._markers.dirty = 0;
            tmp = [];
        }

        //地图地理坐标边界
        var mapBounds = self._map.getBounds();

        //适用于runsh的边界对象
        var mapBoxCoords = {
            minX: mapBounds.getWest(),
            minY: mapBounds.getSouth(),
            maxX: mapBounds.getEast(),
            maxY: mapBounds.getNorth(),
        };

        //查询范围内的图标
        self._markers.search(mapBoxCoords).forEach(function (e) {
            //图标屏幕坐标
            var pointPos = self._map.latLngToContainerPoint(e.data.getLatLng());
            var iconSize = e.data.options.icon.options.iconSize;
            var adj_x = iconSize[0] / 2;
            var adj_y = iconSize[1] / 2;

            var newCoords = {
                minX: pointPos.x - adj_x,
                minY: pointPos.y - adj_y,
                maxX: pointPos.x + adj_x,
                maxY: pointPos.y + adj_y,
                data: e.data,
                pointPos: pointPos,
            };

            tmp.push(newCoords);
        });
        // console.log("说有目标---", tmp);
        //需要做碰撞检测则降序排序,zIndex值大的优先绘制;不需要碰撞检测则升序排序,zIndex值的的后绘制
        tmp.sort((layer1, layer2) => {
            let zIndex1 = layer1.data.options.zIndex ? layer1.data.options.zIndex : 1;
            let zIndex2 = layer2.data.options.zIndex ? layer2.data.options.zIndex : 1;
            return (-zIndex1 + zIndex2) * (this.options.collisionFlg ? 1 : -1);
        }).forEach((layer) => {
            //图标屏幕坐标
            var pointPos = layer.pointPos;
            self._drawMarker(layer.data, pointPos);
        });
        //Clear rBush & Bulk Load for performance
        this._containMarkers.clear();
        this._containMarkers.load(tmp);
        if (this.options.collisionFlg != true) {
            this._showMarkers = this._containMarkers;
        }
        console.timeEnd("canvas一次重绘时间");
        return this;
    },

    /**
     * 初始化容器
     */
    _initCanvas: function () {
        this._container = L.DomUtil.create("canvas", "leaflet-canvas-icon-layer leaflet-layer");
        if (this.options.zIndex) {
            this._container.style.zIndex = this.options.zIndex;
        }

        var size = this._map.getSize();
        this._container.width = size.x;
        this._container.height = size.y;

        this._ctx = this._container.getContext("2d");

        var animated = this._map.options.zoomAnimation && L.Browser.any3d;
        L.DomUtil.addClass(this._container, "leaflet-zoom-" + (animated ? "animated" : "hide"));
    },

    /**
     * 添加click侦听器
     */
    addOnClickListener: function (listener) {
        this._onClickListeners.push(listener);
    },

    /**
     * 添加hover侦听器
     */
    addOnHoverListener: function (listener) {
        this._onHoverListeners.push(listener);
    },

    /**
     * 添加mousedown侦听器
     */
    addOnMouseDownListener: function (listener) {
        this._onMouseDownListeners.push(listener);
    },

    /**
     * 添加mouseup侦听器
     */
    addOnMouseUpListener: function (listener) {
        this._onMouseUpListeners.push(listener);
    },

    /**
     * 执行侦听器
     */
    _executeListeners: function (event) {
        if (!this._showMarkers) return;
        var me = this;
        var x = event.containerPoint.x;
        var y = event.containerPoint.y;

        if (me._openToolTip) {
            me._openToolTip.closeTooltip();
            delete me._openToolTip;
        }

        var ret = this._showMarkers.search({
            minX: x,
            minY: y,
            maxX: x,
            maxY: y,
        });

        if (ret && ret.length > 0) {
            me._map._container.style.cursor = "pointer";
            if (event.type === "click") {
                var hasPopup = ret[0].data.getPopup();
                if (hasPopup) ret[0].data.openPopup();

                me._onClickListeners.forEach(function (listener) {
                    listener(event, ret);
                });
            }
            if (event.type === "mousemove") {
                var hasTooltip = ret[0].data.getTooltip();
                if (hasTooltip) {
                    me._openToolTip = ret[0].data;
                    ret[0].data.openTooltip();
                }

                me._onHoverListeners.forEach(function (listener) {
                    listener(event, ret);
                });
            }
            if (event.type === "mousedown") {
                me._onMouseDownListeners.forEach(function (listener) {
                    listener(event, ret);
                });
            }

            if (event.type === "mouseup") {
                me._onMouseUpListeners.forEach(function (listener) {
                    listener(event, ret);
                });
            }
        } else {
            me._map._container.style.cursor = "";
        }
    },

    /**
     * 地图Zoomanim事件监听器函数
     * @param {Object} env {center:L.LatLng,zoom:number}格式的对象
     */
    _onAnimZoom(ev) {
        this._updateTransform(ev.center, ev.zoom);
    },

    /**
     * 地图修改zoom事件监听器函数
     */
    _onZoom: function () {
        this._updateTransform(this._map.getCenter(), this._map.getZoom());
    },

    /**
     * 修改dom原始的transform或position
     * @param {L/LatLng} center 中心点
     * @param {number} zoom 地图缩放级别
     */
    _updateTransform: function (center, zoom) {
        var scale = this._map.getZoomScale(zoom, this._zoom),
            position = L.DomUtil.getPosition(this._container),
            viewHalf = this._map.getSize().multiplyBy(0.5),
            currentCenterPoint = this._map.project(this._center, zoom),
            destCenterPoint = this._map.project(center, zoom),
            centerOffset = destCenterPoint.subtract(currentCenterPoint),
            topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);

        if (L.Browser.any3d) {
            L.DomUtil.setTransform(this._container, topLeftOffset, scale);
        } else {
            L.DomUtil.setPosition(this._container, topLeftOffset);
        }
    },

    /**
     * 更新渲染器容器的像素边界(用于以后的定位/大小/剪裁)子类负责触发“update”事件。
     */
    _update: function () {
        var p = 0,
            size = this._map.getSize(),
            min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();

        this._bounds = new L.Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());

        this._center = this._map.getCenter();
        this._zoom = this._map.getZoom();

        this._redraw();
    },
    /**
     * 设置图层透明度
     * @param {Number} opacity 图层透明度
     */
    setOpacity(opacity) {
        this.options.opacity = opacity;
        return this._redraw(true);
    },
}));

export var canvasMarkerLayer = (L.canvasMarkerLayer = function (options) {
    return new L.CanvasMarkerLayer(options);
});

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/726382.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

小学生杂志小学生杂志社小学生编辑部2024年第5期目录

教学研究 小学数学教学中易错题的纠正策略研究 黄喜军; 1-3 主题语境下小学英语作业多模态设计与实施策略研究 韩蓓; 4-6 小学美术教育中色彩教学的实施措施研究 顾雅洁; 7-9《小学生》投稿&#xff1a;cn7kantougao163.com 核心素养视域下小学英语单元整体教学…

Linux:配置本地yum源仓库

目录 一、挂载光盘到目录下 二、配置本地yum源仓库 一、挂载光盘到目录下 mount /dev/cdrom /mnt/ #把光盘挂载到/mnt目录下 挂载 设备 目录或文件夹 注&#xff1a;最好是空的 原来的数据将被隐藏一个挂载点同一时只能挂载一个设备。 mount /dev…

数据中心网络自动化不断发展

评估数据中心自动化的状况 随着数据中心变得越来越复杂&#xff0c;网络自动化对大多数企业来说愈发重要。因此&#xff0c;寻找一种更灵活、更高效的运营方式应该成为每个公司的首要任务。然而&#xff0c;即使是那些可能从自动化中受益的实体企业——例如通信服务提供商&…

Spring(核心概念:IoC/DI思想)

目录 一、引言 &#xff08;1&#xff09;如今的代码书写现状 1、业务层 2、数据层 3、假如当项目上线发布之后&#xff0c;想把数据层的实现换一下 二、核心概念 &#xff08;1&#xff09;IoC&#xff08; Inversion of Control ) 控制反转 &#xff08;2&#xff09;…

【开源】AigoTools —— 自动收录网站的导航站模板

在管理导航站点时&#xff0c;我们常常面临各种挑战&#xff1a;手动创建和更新站点信息费时费力&#xff0c;国际化需求&#xff0c;SEO 优化难以实施&#xff0c;以及图片存储方案不够灵活。针对这些问题&#xff0c;我们推出了 AigoTools&#xff0c;让导航站点管理变得更加…

【vue scrollTo 数据无限滚动 】

vue数据无限滚动 参考来源 Vue3 实现消息无限滚动的新思路 —— 林三心不学挖掘机 vue3代码 <template><div class"scroll-container" ref"scrollRef"><div v-for"(item, index) in list" :key"index" style"hei…

AD域离线破解新思路:Trustroasting和TimeRoasting

简介 近期Tom Tervoort发表了白皮书《TIMEROASTING, TRUSTROASTING AND COMPUTER SPRAYING》并在Github发布了名为Timeroast的工具包&#xff0c;其中介绍了几种新的攻击思路TimeRoasting、Trustroasting和计算机账户密码喷洒&#xff0c;本篇文章主要对TimeRoasting和Trustro…

Appium:Appium-Python-Client与Selenium版本不兼容导致的问题

一、问题描述 在执行python代码过程中&#xff0c;出现了以下错误&#xff1a; 错误一&#xff1a;No module named appium.webdriver.common.touch_action Traceback (most recent call last):File "d:\xxx\index.py", line 3, in <module> ModuleNotFound…

电动汽车电池是如何制造的

锂离子电池如何工作&#xff1f; 锂离子电池的工作原理是电化学反应&#xff0c;电子在两个电极之间转移&#xff0c;其中一个带负电&#xff0c;另一个带正电。电极浸入导电电解质中&#xff0c;促进带电离子在电极之间移动。 锂离子电池充电 锂离子电池具有插层化合物&…

使用Flink接受kafka中的数据并对数据进行ETL

做这个开发是因为&#xff1a;在实际开发操作中&#xff0c;你的kafka主题中会有大量的数据但是需求并不需要所有数据&#xff0c;所有我们要对数据进行清洗&#xff0c;把需要的数据保存在flink流中&#xff0c;为下流的开发做好数据保障&#xff01; 首先创建工具类 再写一…

ssh生成时注意事项

生成ssh ssh-keygen -t rsa -C "your_emailtemplate.com.cn"重新生成ssh后&#xff0c;拉代码时遇见 remote: remote: remote: remote: The project you were looking for could not be found or you dont have permission to view it. remote: remote: remote: f…

免费分享:1994-2020年中国各行业二氧化碳排放数据(附下载方法)

日前&#xff0c;国务院印发《2024—2025年节能降碳行动方案》针对重点领域进行部署&#xff0c;同时明确了制度标准、价格政策、资金支持、科技引领、市场化机制、全民行动等6项措施&#xff0c;为节能降碳提供支撑保障。1994-2020年中国各行业二氧化碳排放数据为评估环境政策…

RadioML 2016.10a 调制方式识别-IQ分量

文章目录 RadioML 2016.10a 调制方式识别-IQ分量一、IQ分量什么是 IQ 分量&#xff1f;为什么使用 IQ 分量&#xff1f;如何还原原始波形&#xff1f;如何进行傅里叶变换&#xff1f; 二、信号还原1、还原信号2、快速傅里叶变换3、频率域图 三、可视化1、时间域图2、 功率谱图 …

ecoAddRepeater -loc与-offLoadAtLoc的区别

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口 ecoAddRepeater -loc {x y} -cell BUF -net NET ecoAddRepeater -offLoadAtLoc {x y} -cell BUF -net NET 都是指定插buf/inv物理位置&#xff0c;区别在于前者用于插buf/inv…

Java多线程+线程池图文实例操作(源码自取)

目录 线程相关概念 并发 并行 继承Thread类 实现Runnable接口 实现Callable接口 使用ExecutorService 和线程池 多线程卖手机 非同步 同步机制卖手机 锁方法 锁代码块 ​编辑锁静态方法 锁静态代码块 线程常用方法 用户线程和守护线程 线程状态 线程池 自定…

Ubuntu/Linux系统安装JDK1.8(带jdk1.8资源和操作教程)

文章目录 前言一、JDK1.8下载二、上传三、安装四、配置环境变量五、查看总结 前言 &#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;Ubuntu/Linux jdk1.8安装包&#xff…

手机铃声下载2个必备技巧,定制化铃声,彰显个性魅力

手机铃声&#xff0c;就像是独特的信号灯&#xff0c;不仅仅是通知我们来电或信息的方式&#xff0c;更是展现个人品位和魅力的武器。手机铃声下载和定制&#xff0c;让你的手机从千万舰队中脱颖而出。在接下来的文章中&#xff0c;我们将详细探讨铃声下载技巧的具体操作步骤&a…

第二届人工智能、系统与网络安全国际学术会议 (AISNS 2024)

第二届人工智能、系统与网络安全国际学术会议 (AISNS 2024&#xff09; 2024 2nd International Conference on Artificial Intelligence, Systems and Network Security 一、重要信息 大会官网&#xff1a;www.aisns.org &#xff08;点击参会/投稿/了解会议详情&#xff09…

【Java】已解决java.sql.SQLTimeoutException异常

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决java.sql.SQLTimeoutException异常 在Java的数据库编程中&#xff0c;java.sql.SQLTimeoutException是一个重要的异常&#xff0c;它通常表示在数据库操作&#xff08;如查询…

Java Array示例说明

Java Array示例说明 数组是相同类型的元素的集合。例如&#xff0c;int数组包含整数元素&#xff0c;String数组包含String元素。Array的元素存储在内存中的相邻位置。Java中的数组基于零基索引系统&#xff0c;这意味着第一个元素位于索引0处。 数组如下所示&#xff1a; i…