---
title: AI对话SDk
source: https://vjmap.com/guide/aisdk.html
---

---
search: false
---

# AI对话SDk

可以`AI`对话工具栏对话中点击`导出工程模板` [点击下载工程代码](https://vjmap.com/download/vjmap-app-vue3.zip) , 在`src\chat\sdk.ts` 查看全部代码知识库。

```ts
import vjmap, { Map }  from "vjmap";
import vjcommon from "vjcommon";
export class VjChatSDK {
    map: Map;
    context: Record<string, any>;
    constructor(map: Map, context: Record<string, any>) {
        this.map = map;
		this.context = context;
    }

        /**
     * 通过前缀从服务端获取用户自定义数据的键值
     * @param {string} prefix 键名前缀
     * @returns {Promise<string[]>} 返回获取到的键值数组
     * @example
      * // 从服务端通过前缀获取用户自定义数据的键值，前缀为test
      * let keys = await this.getCustomDataKeysByPrefixServer("test");
      * console.log(keys);
     */
    async getCustomDataKeysByPrefixServer(prefix)  {
            let svc = this.map.getService();
            let res = await svc.getCustomDataKeysByPrefix(prefix);
            return res?.keys || [];
        }
    

    /**
     * 创建点标记数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {string} options.text 要显示的文字，参数可选，默认为"index ${props.index}"
     * @param {number} options.minZoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxZoom 显示最大缩放级别，参数可选，默认为24
     * @param {boolean} options.isDrawLinePoint 绘制点时包括线上所有点, 参数可选，默认为false
     * @param {string} options.color 标记颜色，参数可选，默认为'#3FB1CE'
     * @param {number[]} options.offset 偏离XY像素值，如[0,0]，负数表示向左和向上
     * @param {number} options.rotation 标记旋转角度，顺时针旋转，默认0
     * @param {string} options.anchor 标记锚点位置，可选值：center、top、bottom、left、right、top-left、top-right、bottom-left、bottom-right，默认center
     * @param {boolean} options.draggable 是否可拖动，参数可选，默认为false
     * @param {string} options.animationType 动画类型：MAP_ANIMATION_NONE(无动画)、MAP_ANIMATION_BOUNCE(弹跳)、MAP_ANIMATION_DROP(坠落)
     * @param {string} options.rotationAlignment 旋转对齐：auto(自动)、map(相对地图)、viewport(相对视口)，默认auto
     * @param {string} options.pitchAlignment 倾斜对齐：auto(自动)、map(相对地图)、viewport(相对视口)，默认auto
     * @param {number} options.scale 缩放比例，默认1，对应高度41px宽度27px
     * @param {number} options.scaleMaxZoom 能缩放的最大级别(0-24)，小于此级别时自动缩放
     * @param {number} options.height 标高值，单位CAD图制图单位
     * @param {boolean} options.removeWhenNoInMapView 是否在视图范围内自动显隐
     * @param {number} options.removeWhenNoInMapViewPadding 视图范围内自动显隐的padding像素值，默认500
     * @param {number} options.clickTolerance 点击像素容差，默认继承地图的clickTolerance
     * @param {string} options.customImage 自定义图片地址/base64/svg内容
     * @param {number} options.customImageWidth 自定义图片宽度
     * @param {number} options.customImageHeight 自定义图片高度
     * @param {string} options.customHtml 自定义Html元素的JS代码
     * @param {string} options.popupHtml 信息窗口HTML内容
     * @param {[number,number]} options.popupOffset 信息窗口偏离XY像素值
     * @param {string} options.popupAnchor 信息窗口锚点位置，同anchor参数
     * @param {boolean} options.closeButton 是否显示关闭按钮
     * @param {boolean} options.closeOnClick 是否在点击地图时关闭弹出窗口
     * @param {boolean} options.closeOnMove 是否在地图移动后关闭信息窗口
     * @param {boolean} options.focusAfterOpen 是否尝试聚焦弹出窗口内第一个可聚焦元素
     * @param {string} options.className 添加到弹出容器的CSS类名
     * @param {string} options.maxWidth 弹出窗口最大宽度，CSS属性字符串，默认'240px'
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer19的点标记数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createMarkerDataLayer(geojson, {
      * layerName: 'layer19'
      * });
      * console.log(layer);
     */
    async createMarkerDataLayer(data, options)  {
            options = options || {};
            let type = "marker";
        
            if (options.height) {
              options.height = this.map.geoLengthToHeight(options.height);
            }
    
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                color: '#3FB1CE',
                closeButton: true,
                closeOnClick: true,
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createMarkerDataLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 移除一个功能按钮
     * @param {string} name 按钮名
     * @example
      * // 移除按钮名为"放大一级"的功能按钮
      * this.removeButtonItem("放大一级")
     */
    removeButtonItem(name, func)  {
             this.context.ui.removeButtonItem(name, func);   
             // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.addButtonItem(name, func);
            });
          }
    

    /**
     * 采集一个矩形坐标
     * @param {boolean} isSnap 是否启用捕捉功能, 默认false
     * @returns {Promise<number[] | undefined>} 返回包含四个数字的数组[x1,y1,x2,y2]，表示矩形的左下角和右上角坐标，如果取消操作则返回undefined
     * @example
      * // 采集一个矩形坐标
      * const rect = await this.actionDrawRectangle();
      * if (!rect) return;
      * // 矩形左下角坐标
      * const leftBottomX = rect[0];
      * const leftBottomY = rect[1];
      * // 矩形右上角坐标
      * const rightTopX = rect[2];
      * const rightTopY = rect[3];
      * console.log(leftBottomX, leftBottomY, rightTopX, rightTopY);
     */
    async actionDrawRectangle(isSnap)  {
            this.context.tools.logInfo("请在地图上绘制一个矩形")
            let snapObj = {};
            if (isSnap) {
                // 捕捉dwg图上的点
                vjcommon.getMapSnapPoints(this.map, snapObj);
            }
            let drawRect = await vjmap.Draw.actionDrawRectangle(this.map, isSnap ? {
                api: {
                    getSnapFeatures: snapObj
                }
            } : {});
            if (drawRect.cancel) {
                return; // 取消操作
            }
            let co = drawRect.features[0].geometry.coordinates[0];
            co = this.map.fromLngLat(co);
            return [co[0].x, co[0].y, co[2].x, co[1].y];
        }
    

    /**
     * 获取地图的当前缩放级别
     * @returns {number} 当前地图的缩放级别
     */
    getZoom() {  this.map.getZoom() }
    

    /**
     * 创建旋转的光环数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {string} options.text 要显示的文字，参数可选，默认为"index ${props.index}"
     * @param {number} options.minZoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxZoom 显示最大缩放级别，参数可选，默认为24
     * @param {boolean} options.isDrawLinePoint 绘制点时包括线上所有点, 参数可选，默认为false
     * @param {number} options.width 像素宽度，参数可选，默认为24
     * @param {number} options.markerHeight 像素高度，参数可选
     * @param {string} options.color1 颜色值一，参数可选
     * @param {string} options.color2 颜色值二，参数可选
     * @param {number} options.textFontSize 字体大小，参数可选
     * @param {string} options.textColor 字体颜色，参数可选
     * @param {boolean} options.draggable 是否可拖动，参数可选，默认为false
     * @param {string} options.animationType 动画类型，参数可选，无动画 MAP_ANIMATION_NONE，弹跳 MAP_ANIMATION_BOUNCE, 坠落 MAP_ANIMATION_DROP
     * @param {number} options.rotation 旋转角度，参数可选
     * @param {string} options.rotationAlignment 旋转对齐，参数可选, 自动 auto, 相对于地图 map, 相对于视口 viewport
     * @param {string} options.pitchAlignment 倾斜对齐，参数可选, 自动 auto, 相对于地图 map, 相对于视口 viewport
     * @param {number} options.height 坐标标高值，参数可选
     * @param {number} options.scaleMaxZoom 能缩放的最大级别，参数可选
     * @param {boolean} options.removeWhenNoInMapView 视图范围内自动显隐，参数可选
     * @param {number} options.removeWhenNoInMapViewPadding 视图范围内自动显隐Padding像素范围，参数可选，默认500
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer23的旋转的光环数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createRotatingMarkerLayer(geojson, {
      * layerName: 'layer23'
      * });
      * console.log(layer);
     */
    async createRotatingMarkerLayer(data, options)  {
            options = options || {};
            let type = "rotatingMarker";
        
    
            if (options.height) {
              options.height = this.map.geoLengthToHeight(options.height);
            }
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                text: "ID:${props.index}",
                width: 80,
                markerHeight: 80,
                color1: "#ff0000",
                color2: "#ffff00",
                textFontSize: 15,
                textColor: "#00ffff",
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createRotatingMarkerLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 获取地图元数据
     * @param {object | object[]} options 参数 可选。
     * @param {string} [options.mapid] 地图ID. 可选，为空时则获取当前打开的mapid
     * @param {string} [options.version] 地图版本(为空时采用当前打开的地图版本).
     * @returns {Promise<Object>} 返回地图元数据, 如果失败则返回undefined, 返回字段如下:
     * @example
      * // 获取地图sys_hello的元数据
      * let res = await this.metadata({
      * mapid: "sys_hello"
      * });
     */
    async metadata(options)  {
            let svc = this.map.getService();
            options = options || {};
            let res =  await svc.metadata(options.mapid, options.version);
            if (res) {
                res.bounds = vjmap.GeoBounds.fromString(res.bounds).toArray();
                res.dbBounds = vjmap.GeoBounds.fromString(res.dbBounds).toArray();
                res.drawBounds = vjmap.GeoBounds.fromString(res.drawBounds).toArray();
                res.initBounds = vjmap.GeoBounds.fromString(res.initBounds).toArray();
                if (res.clipbounds) {
                    res.clipbounds = vjmap.GeoBounds.fromString(res.clipbounds).toArray();
                } else {
                    res.clipbounds = null;
                }
                res.layouts = res.layouts.split(", ");
            }
            return res;
         }
    

    /**
     * 创建临时地图， 根据fileid创建一个临时的地图
     * @param {string} fileid 文件id
     * @param {object} options 其他参数(可选), 支持以下参数:
     * @param {boolean} [options.GeomRender] 是否以矢几何渲染打开，， false 内存直接打开，true 几何渲染打开， 默认false
     * @param {number} [options.expireTime] 临时图形不浏览情况下过期自动删除时间，单位分钟。默认5
     * @param {boolean} [options.isVisible] 是否可见，（可见的话，将可通过ListMaps获取，否则为隐藏图）。默认不可见
     * @param {boolean} options.lightTheme 是否使用浅色主题 可选，为空时则使用深色主题
     * @returns {string} 返回创建的图的图id
     * @example
      * //  根据文件id值"c0febd8e49c7"创建一个临时地图
      * let mapid = await this.createTempMap("c0febd8e49c7");
      * console.log(mapid);
     */
    async createTempMap(fileid, options)  {
            options = options || {};
            let svc = this.map.getService().clone();
            let mapid = vjmap.getTempMapId(options.expireTime || 5, options.isVisible);
            // js代码
            let res = await svc.updateMap({
                mapid: mapid,
                fileid: fileid,
                mapopenway: options.GeomRender === true ? vjmap.MapOpenWay.GeomRender : vjmap.MapOpenWay.Memory,
                style:  options.lightTheme ? vjmap.openMapLightStyle() : vjmap.openMapDarkStyle(),
            })
            return res.mapid;
        }
    

    /**
     * 创建线数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {string} options.lineColor 线条颜色，参数可选，默认为'#ff0000'
     * @param {number} options.lineWidth 线宽，参数可选，默认为1
     * @param {number} options.lineOpacity 线透明度，参数可选，取值范围0-1
     * @param {number} options.lineGapWidth 线间隙宽度，参数可选，默认为1，单位像素
     * @param {number} options.lineOffset 线的偏移量，参数可选。正值向右/内偏移，负值向左/外偏移
     * @param {number} options.lineBlur 线模糊度，参数可选，需小于线宽度
     * @param {[number,number]} options.lineDasharray 虚线样式，参数可选，格式如[1,1]表示实线虚线的组合
     * @param {[number,number]} options.lineTranslate 平移量，参数可选，格式如[x,y]，负数表示左和上，单位像素
     * @param {string} options.lineCap 线帽形状，参数可选，可选值：'butt'(尖头)、'round'(圆头)、'square'(平头)
     * @param {string} options.lineJoin 拐角形状，参数可选，可选值：'bevel'(平拐)、'round'(圆拐)、'miter'(棱拐)
     * @param {string} options.linePattern 线图案，参数可选，需与预加载的图片名称对应，图像宽度必须是2的幂次
     * @param {string} options.lineGradient 线渐变，参数可选
     * @param {boolean} options.isHoverPointer 是否启用鼠标悬浮事件，参数可选
     * @param {boolean} options.isHoverFeatureState 是否启用悬浮实体状态，参数可选
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer17的线数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createLineLayer(geojson, {
      * layerName: 'layer17'
      * });
      * console.log(layer);
     */
    async createLineLayer(data, options)  {
            options = options || {};
            let type = "line";
        
    
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                lineColor: '#ff0000',
                lineWidth: 1,
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createLineLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 测量面积
     * @example
      * // 测量面积
      * await this.measureArea();
     */
    measureArea()  {
                vjcommon.runMeasureCmd(this.map, "measureArea");
            }
    
    

    /**
     * 设置地图深色、浅色或高科技主题
     * @param {string} theme 主题，可选值："dark", "light", "high-tech"
     * @example
      * // 示例一
      * // 设置地图深色主题
      * await this.setMapTheme("dark");
      * // 示例二
      * // 设置地图浅色主题
      * await this.setMapTheme("light");
      * // 示例三
      * // 设置地图高科技主题
      * await this.setMapTheme("high-tech");
     */
    async setMapTheme(theme)  {
            if (!this.context.global.updateMapStyleObj) {
                this.context.global.updateMapStyleObj = vjcommon.createUpdateMapStyleObj(this.map);
            }
            const updateMapStyleObj = this.context.global.updateMapStyleObj;
            let style = {
                backcolor: theme === "light" ? 0xffffff : 0,
            };
            if (theme === "high-tech") {
                style.expression = "var color := gFilterCustomTheme(gInColorRed, gInColorGreen, gInColorBlue, 200, 200, 0.1);gOutColorRed[0] := gRed(color);gOutColorGreen[0] := gGreen(color);gOutColorBlue[0] := gBlue(color);";
            }
            const curStyle = {...updateMapStyleObj.getCurStyle()};
            await updateMapStyleObj.updateStyle(style)
            // 添加撤销记录
            this.context.ui.addUndoRecord(async () => {
                await updateMapStyleObj.updateStyle(curStyle)
            });
        }
    

    /**
     * 设置地图的最大、最小缩放级别
     * @param {object} options 包含maxZoom和minZoom的对象
     * @param {number} options.maxZoom 要设置的最大缩放级别(可为空)
     * @param {number} options.minZoom 要设置的最小缩放级别(可为空)
     * @example
      * this.setMaxMinZoom({maxZoom: 10}); // 设置最大缩放级别为10
     */
    setMaxMinZoom(options)  {
            options = options || {};
            if (options.maxZoom !== undefined) {
                let oldMaxZoom = this.map.getMaxZoom(); // 获取当前最大缩放级别
                this.map.setMaxZoom(options.maxZoom); // 设置新的最大缩放级别
                // 添加撤销记录
                this.context.ui.addUndoRecord(() => {
                    this.map.setMaxZoom(oldMaxZoom);
                });
            }
            if (options.minZoom !== undefined) {
                let oldMinZoom = this.map.getMinZoom(); // 获取当前最小缩放级别
                this.map.setMinZoom(options.minZoom); // 设置新的最小缩放级别
                // 添加撤销记录
                this.context.ui.addUndoRecord(() => {
                    this.map.setMinZoom(oldMinZoom);
                });
            }
        }
    

    /**
     * 从服务端获取用户自定义数据
     * @param {string | string[]} key 键名，如果是数组的话，可以查询.
     * @param {object} options 选项 retDataType为空时查询数据及属性,"value"只查询数据，"prop"只查询属性。contentType返回类型,为空时为json，为"image"时为图片.
     * @returns {Promise<object>} 返回获取到的用户自定义数据，格式如下 {data: any, prop: any, status: string}
     * @example
      * // 从服务端获取键名为testKey的用户自定义数据
      * let data = await this.getCustomDataServer("testKey");
      * console.log(data);
      * // 从服务端批量获取用户自定义数据，键名为testKey1，testKey2
      * let dataArray = await this.getCustomDataServer(["testKey1", "testKey2"]);
      * console.log(dataArray);
     */
    async getCustomDataServer(key, options)  {
            let svc = this.map.getService();
            return await svc.getCustomData(key, options);
        }
    

    /**
     * 设置地图是否显示线宽
     * @param {boolean} isShowLineWidth 是否显示线宽
     * @example
      * // 设置地图显示线宽
      * await this.setMapShowLineWidth(true);
      * // 设置地图不显示线宽
      * await this.setMapShowLineWidth(false);
     */
    async setMapShowLineWidth(isShowLineWidth)  {
            if (!this.context.global.updateMapStyleObj) {
                this.context.global.updateMapStyleObj = vjcommon.createUpdateMapStyleObj(this.map);
            }
            const updateMapStyleObj = this.context.global.updateMapStyleObj;
            const oldLineweight = updateMapStyleObj.getCurStyle()?.lineweight;
            await updateMapStyleObj.updateStyle({
                lineweight: isShowLineWidth ? 1 : 0
            })
            // 添加撤销记录
            this.context.ui.addUndoRecord(async () => {
                await updateMapStyleObj.updateStyle({
                    lineweight: oldLineweight
                })
            });
        }
    

    /**
     * 删除地图
     * @param {object} options 参数
     * @param {string} options.mapid 地图ID
     * @param {string} options.version 版本号，如删除所有版本，输入"*"号
     * @param {number} [options.retainVersionMaxCount] 删除所有版本时，保留的最新的版本总数。可选
     * @returns {Promise<boolean>} 返回删除结果true表示成功，false表示失败
     * @example
      * // 示例一
      * // 删除地图hello的所有版本
      * let res1 = await this.deleteMap({
      * mapid: "hello",
      * version: "*"
      * });
      * console.log(res1);
      * // 示例二
      * // 删除地图hello的v1版本
      * let res2 = await this.deleteMap({
      * mapid: "hello",
      * version: "v1"
      * });
      * console.log(res2);
     */
    async deleteMap(options)  {
           let svc = this.map.getService();
           let res = await svc.cmdDeleteMap(options.mapid, options.version, options.retainVersionMaxCount);
           return res && res.status;
       }
    

    /**
     * 创建呼吸的光圈点数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {string} options.text 要显示的文字，参数可选，默认为"index ${props.index}"
     * @param {number} options.minZoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxZoom 显示最大缩放级别，参数可选，默认为24
     * @param {boolean} options.isDrawLinePoint 绘制点时包括线上所有点, 参数可选，默认为false
     * @param {number} options.width 像素宽度，参数可选，默认为24
     * @param {number} options.markerHeight 像素高度，参数可选
     * @param {string} options.color1 颜色值一，参数可选
     * @param {string} options.color2 颜色值二，参数可选
     * @param {number} options.textFontSize 字体大小，参数可选
     * @param {string} options.textColor 字体颜色，参数可选
     * @param {boolean} options.draggable 是否可拖动，参数可选，默认为false
     * @param {string} options.animationType 动画类型，参数可选，无动画 MAP_ANIMATION_NONE，弹跳 MAP_ANIMATION_BOUNCE, 坠落 MAP_ANIMATION_DROP
     * @param {number} options.rotation 旋转角度，参数可选
     * @param {string} options.rotationAlignment 旋转对齐，参数可选, 自动 auto, 相对于地图 map, 相对于视口 viewport
     * @param {string} options.pitchAlignment 倾斜对齐，参数可选, 自动 auto, 相对于地图 map, 相对于视口 viewport
     * @param {number} options.height 坐标标高值，参数可选
     * @param {number} options.scaleMaxZoom 能缩放的最大级别，参数可选
     * @param {boolean} options.removeWhenNoInMapView 视图范围内自动显隐，参数可选
     * @param {number} options.removeWhenNoInMapViewPadding 视图范围内自动显隐Padding像素范围，参数可选，默认500
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer6的呼吸的光圈点数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createBreathingMarkerLayer(geojson, {
      * layerName: 'layer6',
      * text: '索引：${props.index}'
      * });
      * console.log(layer);
     */
    async createBreathingMarkerLayer(data, options)  {
            options = options || {};
            let type = "breathingMarker";
        
            if (options.height) {
              options.height = this.map.geoLengthToHeight(options.height);
            }
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                "width": 80,
                "markerHeight": 80,
                "color1": "#ff0000",
                "color2": "#ffff00",
                "textFontSize": 15,
                "textColor": "#00ffff",
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createBreathingMarkerLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 采集一个点坐标
     * @param {boolean} isSnap 是否启用捕捉功能, 默认false
     * @returns {Promise<[number,number] | undefined>} 返回采集的坐标[x,y]，如果取消操作则返回undefined
     * @example
      * const pt = await this.actionDrawPoint(true); // 启用捕捉功能采集一个点坐标
      * if (!pt) return;
      * // 点的坐标
      * const x = pt[0];
      * const y = pt[1];
      * console.log(x, y);
     */
    async actionDrawPoint(isSnap)  {
            this.context.tools.logInfo("请在地图上绘制一个点")
            let snapObj = {};
            if (isSnap) {
                // 捕捉dwg图上的点
                vjcommon.getMapSnapPoints(this.map, snapObj);
            }
            let drawPoint = await vjmap.Draw.actionDrawPoint(this.map, isSnap ? {
                api: {
                    getSnapFeatures: snapObj
                }
            } : {});
            if (drawPoint.cancel) {
                return; // 取消操作
            }
            let co = drawPoint.features[0].geometry.coordinates;
            co = this.map.fromLngLat(co);
            return [co.x, co.y];
        }
    

    /**
     * 根据图层名称和数据内容更新数据图层数据值
     * @param {string} layerName 图层名称，参数必填
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @example
      * // 更新图层名称为layer1的数据图层数据为随机生成的GeoJson数据
      * let layerName = 'layer1';
      * let geojson = this.randomGeojsonInMapBounds();
      * this.updateDataLayerSource(layerName, geojson);
     */
    updateDataLayerSource(layerName, data)  {
            let item = this.context.global.layers.find(layer => layer.layerName == layerName);
            if(item) {
                item.data = data;
                let oldData = {...item.data};
    
                let layerId = item.layerId;
                let sourceId = item.sourceId;
                if (item.layer && typeof(item.layer.getLayerId) == "function") {
                    layerId = item.layer.getLayerId();
                    let style = this.map.getStyle();
                    let layer = style.layers.find(layer => layer.id == layerId);
                    if (layer && layer.source) {
                        sourceId = layer.source;
                    }
                }
    
                this.map.setData(sourceId, this.map.toLngLat(data));
                // 添加撤销记录
                this.context.ui.addUndoRecord(() => {
                    this.updateDataLayerSource(layerName, oldData);
                });
            }
        }
    

    /**
     * 把geojson数据按给定的四参数进行转换
     * @param {Object} data 要转换的geojson数据，参数必填
     * @param {Object} transform 要转换的四参数，平移{dx: number, dy: number}，缩放{scale: number}，旋转弧度{rotate: number}，参数必填
     * @param {boolean} isLngLatCoord 传入的是否为经纬度坐标，默认false为CAD图几何坐标，true为地图经纬度坐标，参数可选
     * @returns {Object} 返回转换后的geojson数据
     * @example
      * // 把geojson数据按给定的四参数进行转换，平移[100, 200]，缩放  0.5，旋转0.5弧度
      * let newGeojson = this.geojsonTransform(geojson, {dx: 100, dy: 200, scale: 0.5, rotate: 0.5});
      * console.log(newGeojson);
     */
    geojsonTransform(data, transform, isLngLatCoord)  {
            return vjmap.transform.convert(data, (pt) => {
                let point = isLngLatCoord === true ? this.map.fromLngLat(vjmap.geoPoint(pt)) : vjmap.geoPoint(pt);
                let newPoint  = vjmap.coordTransfromByFourParamter(point, {
                    dx: transform.dx || 0,
                    dy: transform.dy || 0,
                    scale: transform.scale || 1,
                    rotate: transform.rotate || 0
                });
                return isLngLatCoord === true ? this.map.toLngLat(newPoint) : [newPoint.x, newPoint.y];
            });
        }
    

    /**
     * 采集三维多段线
     * @param {object} options 参数
     * @param {number} options.searchRadiusRatio 搜索文字范围半径倍数，默认2
     * @param {boolean} isSnap 是否启用捕捉绘制的点, 默认true
     * @param {boolean} isSnapMap 是否启用捕捉图上的点 默认false
     * @example
      * // 采集三维多段线
      * await this.pick3dPolylines();
     */
    async pick3dPolylines(options)  {
            // --采集三维线--采集时可自动搜索文字获取当前点的高程值
            // 采集的坐标转成 [x,z,y]格式，可参考此示例自动生成三维管道
            // https://vjmap.com/map3d/demo/#/demo/this.map/builtin/objects/03builtinobjtubepath
            options = options || {};
            let svc = this.map.getService();
            let mapAllTexts;
            let textMeanHeight = 0; // 图中文字平均高
            const getAllTexts = async ()=> {
                if (mapAllTexts) return mapAllTexts; // 如果获取过了
                // 实体类型ID和名称映射
                const { entTypeIdMap } = await svc.getConstData();
                const getTypeNameById = name => Object.keys(entTypeIdMap).find(e => entTypeIdMap[e] === name);
    
                let queryTextEntTypeId = getTypeNameById("AcDbText"); // 单行文字
                let queryMTextEntTypeId = getTypeNameById("AcDbMText"); // 多行文字
                let queryAttDefEntTypeId = getTypeNameById("AcDbAttributeDefinition"); // 属性定义文字
                let queryAttEntTypeId = getTypeNameById("AcDbAttribute"); // 属性文字
                let query = await svc.conditionQueryFeature({
                    condition: `name='${queryTextEntTypeId}' or name='${queryMTextEntTypeId}' or name='${queryAttDefEntTypeId}' or name='${queryAttEntTypeId}'`, // 只需要写sql语句where后面的条件内容,字段内容请参考文档"服务端条件查询和表达式查询"
                    fields: "objectid,envelop,text",
                    geom: true,
                    limit: 100000 //设置很大，相当于把所有的圆都查出来。不传的话，默认只能取100条
                })
                if (query.error) {
                    this.map.logInfo(query.error, "error");
                    return;
                } else {
                    mapAllTexts = []
                    textMeanHeight = 0
                    if (query.recordCount > 0) {
                        for(let i = 0; i < query.recordCount; i++) {
                            let bounds = this.map.getEnvelopBounds(query.result[i].envelop);
                            mapAllTexts.push({
                                position: bounds.center(),
                                text: query.result[i].text
                            });
                            textMeanHeight += bounds.height()
                        }
                        textMeanHeight = textMeanHeight / query.recordCount
                    }
                }
                return mapAllTexts
            }
        
            function isNumeric(value) {
                const regex = /^[0-9]*\.?[0-9]+$/;  // 匹配数字和一个小数点的模式
                return regex.test(value);
            }
            
            let snapObj = {};
            const collect3Dlines = async () => {
                // 给地图发送ESC键消息即可取消，模拟按ESC键，如果之前在采集，先取消之前的
                this.map.fire("keyup",{keyCode:27})
                this.map.logInfo("请在图上采集线，按回车键或右键菜单里确认结束")
                let elevations = []
                if (options.isSnapMap && !snapObj.features) {
                    vjcommon.getMapSnapPoints(this.map, snapObj);
                }
                let line = await vjmap.Draw.actionDrawLineSting(this.map, {
                    snap: options.isSnap === false ? false : true,
                    api: {
                        getSnapFeatures: snapObj
                    },
                    updatecoordinate: async (e) => {
                        if (!e.lnglat) return;
                        if (e.isClick) {
                            let curIdx = e.feature.coordinates.length - 1
                            const co = this.map.fromLngLat(e.feature.coordinates[curIdx]);
                            // 获取当前点旁边所有的文字
                            let allTexts = await getAllTexts()
                            let radius = textMeanHeight * (options.searchRadiusRatio || 2); // 以文字平均高度的2倍做为搜索距离 这个数值根据实际情况调节
                            // 先根据是否是数字，再根据距离过滤
                            let texts = allTexts.filter(item => isNumeric(item.text)).filter(item => vjmap.geoPoint(co).distanceTo(item.position) <= radius)
                            texts = texts.map(t => t.text); // 只获取内容
                            // 弹出输入框，并获取用户输入
                            let elevation = await this.context.ui.selectSuggestionValue(texts, {mustNumber: true, title: "请选择或输入高程值"})
                            if (elevation === undefined) {
                                // 取消采集
                                this.map.fire("keyup",{keyCode:27})
                                return;
                            }
                            elevations.push(elevation)
                        }
    
                    }, contextMenu: (e) => {
                        new vjmap.ContextMenu({
                            event: e.event.originalEvent,
                            theme: "dark", //light
                            width: "250px",
                            items: [
                                {
                                    label: '确认',
                                    onClick: () => {
                                        // 给地图发送Enter键消息即可取消，模拟按Enter键
                                        this.map.fire("keyup", { keyCode: 13 })
                                    }
                                },
                                {
                                    label: '取消',
                                    onClick: () => {
                                        // 给地图发送ESC键消息即可取消，模拟按ESC键
                                        this.map.fire("keyup", { keyCode: 27 })
                                    }
                                }, {
                                    label: '删除上一个点',
                                    onClick: () => {
                                        // 给地图发送退格键Backspace消息即可删除上一个点，模拟按Backspace键
                                        this.map.fire("keyup", { keyCode: 8 })
                                        elevations.pop(); // 删除最后一个元素
                                    }
                                }, {
                                    label: '结束',
                                    onClick: () => {
                                        // 给地图发送ESC键消息即可取消，模拟按ESC键
                                        this.map.fire("keyup", { keyCode: 27 })
                                    }
                                }
                            ]
                        });
    
                    }
                })
                if (line.cancel) {
                    return // 取消操作
                }
    
                let feature = line.features[0]
                feature.properties.elevations = elevations;
                feature.properties.typename = "3dpolyline"
                feature.properties.line_width = 12;
                feature.properties.color = vjmap.randomColor()
                this.map.getDrawLayer().add(feature) 
    
                if (options.isSnap !== false) {
                    // 把绘制的线加进捕捉
                    snapObj.features = snapObj.features || [];
                    snapObj.features.push({
                        type: "Feature",
                        geometry: {
                            type: "LineString",
                            coordinates: feature.geometry.coordinates
                        }
                    });
                }
            }
    
            this.addButtonItem("开始采集", async () => {
                await collect3Dlines()
            })
    
            this.addButtonItem("获取三维线数据", async () => {
                let data = this.map.getDrawLayer().getAll();
                data = this.map.fromLngLat(data);
                let features = data.features.filter(item => item.properties.typename === "3dpolyline") || [];
                let result = [];
                for(let i = 0; i < features.length; i++) {
                    let coords = features[i].geometry.coordinates;
                    let elevations = features[i].properties.elevations;
                    let data = coords.map((co, idx) => [co[0], co[1], elevations[idx]]);
                    result.push({
                        paths: data,
                        name: i
                    })
                }
                await this.showDataInfoDialog(result, false, "三维线数据")
            })
    
            this.addButtonItem("退出采集", async () => {
                // 取消采集
                this.map.fire("keyup",{keyCode:27})
                let data = this.map.getDrawLayer().getAll();
                data.features = data.features.filter(item => item.properties.typename !== "3dpolyline")
                this.map.getDrawLayer().set(data);
                this.removeButtonItem("开始采集")
                this.removeButtonItem("获取三维线数据")
                this.removeButtonItem("退出采集")
            })
    
        }
    

    /**
     * 根据内容生成二维码图片
     * @param {string} content 二维码内容
     * @param {object} options 选项
     * @param {number} options.size 二维码大小，默认256
     * @param {boolean} options.disableOpen 只返回地址，不打开图片，默认false
     * @returns {Promise<string>} 返回二维码图片的url
     * @example
      * // 根据"https://www.vjmap.com"生成二维码图片
      * let url = await this.qrcodeUrl("https://www.vjmap.com");
      * console.log(url);
     */
    async qrcodeUrl(content, options)  {
            options = options || {};
            let svc = this.map.getService();
            let res = await svc.qrcodeUrl(content, options.size);
            if (options.disableOpen) return res;
            window.open(res);
            return res;
        }
    

    /**
     * 删除一个点标记Marker
     * @param {string} id 点标记Marker的id
     * @example
      * this.deleteMarker("marker_1"); // 删除id为marker_1的点标记
     */
    deleteMarker(id)  {
           let markers = this.map.getMarkers();
           let marker = markers.find(m => m.id == id);
           if (marker) {
            let point = this.map.fromLngLat(marker.getLngLat());
            
            let oldOptions = {};
            oldOptions.color = marker._color;
            oldOptions.data = marker.data;
            oldOptions.draggable = marker.isDraggable();
            oldOptions.rotation = marker.getRotation();
    
            if (marker.getPopup()) {
                marker.getPopup().remove();
            }
            marker.remove()
    
           
             // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.addMarker(point, oldOptions);
            });
           }
          
        }
    

    /**
     * 将CAD图几何坐标转换为地图经纬度坐标
     * @returns {[number, number]} 返回包含两个数字的数组[lng,lat]，表示地图经纬度坐标
     * @example
      * // 将CAD图几何坐标[1000, 2000]转换为地图经纬度坐标
      * let point = this.geoPointToLngLat([1000, 2000]);
      * let lng = point[0];
      * let lat = point[1];
      * console.log(lng, lat);
     */
    geoPointToLngLat(pt)  {
            let lngLat = this.map.toLngLat(pt);
            let lng = lngLat[0];
            let lat = lngLat[1];
            return [lng, lat];
        }
    

    /**
     * 增加地图全屏控件
     * @param {string} options.container 全屏的容器，参数可选
     * @param {'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'} options.position 控件位置，参数可选, 默认'top-right'
     * @example
      * // 增加地图全屏控件,位置左上角
      * this.addMapFullscreenControl({ position: 'top-left' });
     */
    addMapFullscreenControl(options)  {
            options = options || {};
            this.context.global.controls = this.context.global.controls || {};
            if (this.context.global.controls.fullscreenControl) {
                this.map.removeControl(this.context.global.controls.fullscreenControl.control);
            }
            if (options.container) {
                options.container = document.querySelector(options.container);
            }
            let fullscreenControl = new vjmap.FullscreenControl(options);
            this.map.addControl(fullscreenControl, options.position);
            this.context.global.controls.fullscreenControl = {
                control: fullscreenControl,
                position: options.position,
                options: options
            };
            // 增加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.map.removeControl(fullscreenControl);
                delete this.context.global.controls.fullscreenControl;
            }); 
        }
    

    /**
     * 手动交互式绘制一个多边形
     * @param {object} options 配置参数, 参数可选
     * @param {boolean} options.isSnap 是否启用捕捉功能, 参数可选，默认false
     * @param {string} options.color 颜色，格式为#RRGGBB，参数可选
     * @param {boolean} options.isFill 是否填充，默认true，参数可选
     * @param {number} options.opacity 透明度，默认1，参数可选
     * @param {number} options.extrusionHeight 拉伸高度，有拉伸高度为拉伸对象，参数可选
     * @param {number} options.data 数据值，参数可选
     * @returns {Promise<Object | undefined>} 返回绘制的图元对象，如果取消操作则返回undefined，返回的对象包含以下属性：
     * @example
      * // 手动绘制一个多边形
      * const polygon = await this.drawPolygon({
      * isSnap: true,
      * color: "#FF0000",
      * isFill: true,
      * opacity: 0.8
      * });
      * if (!polygon) return;
      * console.log("多边形ID:", polygon.id);
      * console.log("多边形的点坐标:", polygon.points);
     */
    async drawPolygon(options)  {
            let points = await this.actionDrawPolygon(options.isSnap);
            if (!points) return;
            return this.createPolygon(points, options);
        }
    

    /**
     * 创建点符号聚合数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {boolean} options.isHoverPointer 是否启用鼠标悬浮事件，参数可选
     * @param {boolean} options.isHoverFeatureState 是否启用悬浮实体状态，参数可选
     * @param {string} options.iconImage 图标名称，用于绘制图像背景的图像名称
     * @param {number} options.iconRotate 图标旋转角度，顺时针旋转，单位为角度，默认0
     * @param {[number,number]} options.iconOffset 图标与锚点偏移距离，[x,y]格式，正值向右和向下
     * @param {string} options.iconAnchor 图标锚定位置，可选值: "center", "left", "right", "top", "bottom", "top-left", "top-right", "bottom-left", "bottom-right"
     * @param {string} options.iconRotationAlignment 图标旋转对齐方式，可选值: "map", "viewport", "auto"
     * @param {string} options.iconPitchAlignment 地图倾斜时图标方向，可选值: "map", "viewport", "auto"
     * @param {boolean} options.iconAllowOverlap 是否允许图标重叠，默认false
     * @param {number} options.iconOpacity 图标透明度，0-1之间，默认1
     * @param {boolean} options.iconIgnorePlacement 是否忽略碰撞检测，默认false
     * @param {boolean} options.iconOptional 图标重叠选项，默认false
     * @param {number} options.iconSize 图标缩放比例，默认1
     * @param {string} options.iconTextFit 图标适应文本方式，可选值: "none", "width", "height", "both"
     * @param {[number,number,number,number]} options.iconTextFitPadding 图标文本匹配padding，[top, right, bottom, left]格式
     * @param {number} options.iconPadding 图标边界填充大小，默认1像素
     * @param {boolean} options.iconKeepUpright 是否防止图标倒置，默认false
     * @param {string} options.iconColor SDF图标颜色，默认"#000000"
     * @param {string} options.iconHaloColor SDF图标光晕颜色，默认"rgba(0, 0, 0, 0)"
     * @param {number} options.iconHaloWidth 图标光晕宽度，默认0
     * @param {number} options.iconHaloBlur 图标光晕模糊度，默认0
     * @param {[number,number]} options.iconTranslate 图标平移距离，[x,y]格式
     * @param {string} options.iconTranslateAnchor 图标平移参考系，可选值: "map", "viewport"
     * @param {string|Expression} options.textField 文本标签内容
     * @param {string[]} options.textFont 文本字体，默认["Open Sans Regular","Arial Unicode MS Regular"]
     * @param {number} options.textSize 文本大小，默认16
     * @param {boolean} options.textAllowOverlap 是否允许文本重叠，默认false
     * @param {string} options.textColor 文本颜色，默认"#000000"
     * @param {number} options.textOpacity 文本透明度，0-1之间，默认1
     * @param {[number,number]} options.textOffset 文本偏移，[x,y]格式
     * @param {number} options.textRotate 文本旋转角度
     * @param {number} options.textPadding 文本内边距，默认2
     * @param {boolean} options.textKeepUpright 是否防止文本倒置，默认true
     * @param {number} options.textMaxWidth 文本最大宽度，默认10em
     * @param {number} options.textLineHeight 文本行高，默认1.2
     * @param {number} options.textLetterSpacing 文字间距，默认0
     * @param {string} options.textJustify 文本对齐方式，可选值: "auto", "left", "center", "right"
     * @param {string} options.textAnchor 文本锚点位置
     * @param {string} options.textRotationAlignment 文本旋转对齐方式
     * @param {string} options.textPitchAlignment 文本倾斜对齐方式
     * @param {number} options.textMaxAngle 相邻字符最大角度，默认45°
     * @param {string[]} options.textWritingMode 文本书写模式
     * @param {string} options.textTransform 文本转换方式，可选值: "none", "uppercase", "lowercase"
     * @param {number} options.textRadialOffset 文本径向偏移
     * @param {string[]} options.textVariableAnchor 文本可变锚点位置数组
     * @param {boolean} options.textIgnorePlacement 是否忽略文本位置
     * @param {boolean} options.textOptional 文本显示选项
     * @param {string} options.textHaloColor 文本光晕颜色
     * @param {number} options.textHaloWidth 文本光晕宽度
     * @param {number} options.textHaloBlur 文本光晕模糊度
     * @param {[number,number]} options.textTranslate 文本平移距离
     * @param {string} options.textTranslateAnchor 文本平移参考系
     * @param {string} options.symbolPlacement 符号放置位置，可选值: "point", "line", "line-center"
     * @param {number} options.symbolSpacing 符号间距，默认250
     * @param {boolean} options.symbolAvoidEdges 是否避免边缘碰撞
     * @param {string} options.symbolZOrder 符号绘制顺序，可选值: "auto", "viewport-y", "source"
     * @param {number} options.clusterMaxZoom 聚合最大缩放级别，超过此级别不再聚合，默认10
     * @param {number} options.clusterRadius 聚合的半径范围，单位像素，默认60
     * @param {Array<[number, string]>} options.outerColors 聚合外部圆的值和颜色分级设置，格式为[[值, 颜色], ...]，默认[[1000, 'rgba(253, 156, 115, 0.6)'], [100, 'rgba(241, 211, 87, 0.6)'], [0, 'rgba(181, 226, 140, 0.6)']]
     * @param {number} options.outerCircleRadius 聚合外部圆的半径，单位像素，默认20
     * @param {Array<[number, string]>} options.innerColors 聚合内部圆的值和颜色分级设置，格式为[[值, 颜色], ...]，默认[[1000, 'rgba(241, 128, 23, 0.6)'], [100, 'rgba(240, 194, 12, 0.6)'], [0, 'rgba(110, 204, 57, 0.6)']]
     * @param {number} options.innerCircleRadius 聚合内部圆的半径，单位像素，默认15
     * @param {number} options.clusterTextSize 聚合文本的字体大小，单位像素，默认12
     * @param {string} options.color 标记颜色，参数可选，默认为'#3FB1CE'
     * @param {number[]} options.offset 偏离XY像素值，如[0,0]，负数表示向左和向上
     * @param {number} options.rotation 标记旋转角度，顺时针旋转，默认0
     * @param {string} options.anchor 标记锚点位置，可选值：center、top、bottom、left、right、top-left、top-right、bottom-left、bottom-right，默认center
     * @param {boolean} options.draggable 是否可拖动，参数可选，默认为false
     * @param {string} options.animationType 动画类型：MAP_ANIMATION_NONE(无动画)、MAP_ANIMATION_BOUNCE(弹跳)、MAP_ANIMATION_DROP(坠落)
     * @param {string} options.rotationAlignment 旋转对齐：auto(自动)、map(相对地图)、viewport(相对视口)，默认auto
     * @param {string} options.pitchAlignment 倾斜对齐：auto(自动)、map(相对地图)、viewport(相对视口)，默认auto
     * @param {number} options.scale 缩放比例，默认1，对应高度41px宽度27px
     * @param {number} options.scaleMaxZoom 能缩放的最大级别(0-24)，小于此级别时自动缩放
     * @param {number} options.height 标高值，单位CAD图制图单位
     * @param {boolean} options.removeWhenNoInMapView 是否在视图范围内自动显隐
     * @param {number} options.removeWhenNoInMapViewPadding 视图范围内自动显隐的padding像素值，默认500
     * @param {number} options.clickTolerance 点击像素容差，默认继承地图的clickTolerance
     * @param {string} options.customImage 自定义图片地址/base64/svg内容
     * @param {number} options.customImageWidth 自定义图片宽度
     * @param {number} options.customImageHeight 自定义图片高度
     * @param {string} options.customHtml 自定义Html元素的JS代码
     * @param {string} options.popupHtml 信息窗口HTML内容
     * @param {[number,number]} options.popupOffset 信息窗口偏离XY像素值
     * @param {string} options.popupAnchor 信息窗口锚点位置，同anchor参数
     * @param {boolean} options.closeButton 是否显示关闭按钮
     * @param {boolean} options.closeOnClick 是否在点击地图时关闭弹出窗口
     * @param {boolean} options.closeOnMove 是否在地图移动后关闭信息窗口
     * @param {boolean} options.focusAfterOpen 是否尝试聚焦弹出窗口内第一个可聚焦元素
     * @param {string} options.className 添加到弹出容器的CSS类名
     * @param {string} options.maxWidth 弹出窗口最大宽度，CSS属性字符串，默认'240px'
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer25的点符号聚合数据图层， 图标名称为_markerIcon_
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createSymbolClusterDataLayer(geojson, {
      * layerName: 'layer25',
      * iconImage: '_markerIcon_'
      * });
      * console.log(layer);
     */
    async createSymbolClusterDataLayer(data, options)  {
            options = options || {};
            let type = "symbolCluster";
        
            if (options.height) {
              options.height = this.map.geoLengthToHeight(options.height);
            }
    
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                clusterMaxZoom: 10,
                clusterRadius: 60,
                outerColors: [[1000, 'rgba(253, 156, 115, 0.6)'], [100, 'rgba(241, 211, 87, 0.6)'], [0, 'rgba(181, 226, 140, 0.6)']],
                outerCircleRadius: 20,
                innerColors: [[1000, 'rgba(241, 128, 23, 0.6)'], [100, 'rgba(240, 194, 12, 0.6)'], [0, 'rgba(110, 204, 57, 0.6)']],
                innerCircleRadius: 15,
                clusterTextSize: 12,
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createSymbolClusterDataLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 获取地图的中心坐标
     * @returns {[number, number]} 当前地图中心的坐标，格式为[x,y]
     */
    getCenter()  {
            let center = this.map.fromLngLat(this.map.getCenter());
            return [center.x, center.y];
        }
    
    

    /**
     * 自动提取图中的表格
     * @returns {Promise<GeoJSON>} 返回提取出的表格的GeoJSON数据， 如果失败则返回undefined
     * @example
      * //示例一
      * // 自动提取图中的表格
      * let geojson = await this.extractTale();
      * if (!geojson) {
      * console.log("提取表格失败");
      * } else {
      * console.log("提取表格成功, geojson数据为:", geojson);
      * }
      * //示例二
      * // 手动选择范围自动提取图中的表格
      * let res2 = await this.extractTale({
      * isSelectBounds: true
      * });
      * console.log(res2);
     */
    async extractTale(options)  {
           options = options || {};
           let svc = this.map.getService();
           options = {
            mapid: options.mapid || svc.currentMapParam().mapid,
            bounds: "",
            condition: "",
            debugInfo: [],
            digit: 4,
            tol: 0,
            tableEdgeMinPoint: 12,
            tableTextMinCount: 4,
            cellMaxArea: 90,
            cellEmptyRatio: 90,
            unLinkLineRatio: 20,
            tableMaxCellCount: 100000,
            noHvLineSegCount: 0,
            seachTableMost: false,
            version:  svc.currentMapParam()?.version,
            layer: svc.currentMapParam()?.layer,
            // 如果直接内存打开的话，用表达式查询，如果几何渲染打开，用空间查询
            geom: svc.currentMapParam()?.mapopenway != vjmap.MapOpenWay.Memory,
            ...options,
           };
           if (options.isSelectBounds) {
                // 如果是手动，先在图中选择一个区域
                if (this.map.logInfo) this.map.logInfo("请先选择表格所在的矩形区域，选择区域的范围需要覆盖表格内容", "success", 10000);
                 // 先在地图上绘制一个矩形,做为裁剪区域
                let snapObj = {}; // 设置的捕捉的实体
                vjcommon.getMapSnapPoints(this.map, snapObj);
                let res = await vjmap.Draw.actionDrawRectangle(this.map, {
                    api: {
                        getSnapFeatures: snapObj //要捕捉的数据项在后面，通过属性features赋值
                    },
                    contextMenu: (e) => {
                        // 给地图发送Enter键消息即可取消，模拟按Enter键
                        this.map.fire("keyup", {keyCode:13})
                    }
                });
                if (res.cancel) {
                    return // 取消操作
                }
                // 获取所绘制的坐标
                let rectCoord = this.map.fromLngLat(res.features[0].geometry.coordinates[0]);
                // 取左下角和右下角两个点
                let  bounds = [rectCoord[0].x, rectCoord[0].y, rectCoord[2].x, rectCoord[2].y];
                options.bounds = bounds.join(',');
           }
           if (Array.isArray(options.bounds)) {
            options.bounds = JSON.stringify(options.bounds);
           }
           if (options.showSettingForm !== false) {
            options = await this.showDataModifyDlg({
                data: options,
                title: "自动提取表格设置",
                showForm: true
            })
            if (!options) return;
           }
           let result = await svc.cmdExtractTale(options);
           if (result && result.error) {
                if (this.map.logInfo) this.map.logInfo(result.error, "error", 10000);
                return;
           }
           let tables = result.tables || [];
           let geoDatas = {
                "type": "FeatureCollection",
                "features": []
            }
            for(let t = 0; t < tables.length; t++) {
                let rect = vjmap.GeoBounds.fromString(tables[t].rect);
                let pts = rect.toPointArray();
                pts.push(pts[0]); // 闭合
                geoDatas.features.push(
                    {
                        "id": t,
                        "type": "Feature",
                        "properties": {
                            color: vjmap.randomColor(),
                            index: t,
                            data: tables[t],
                            tableDatas: tables[t].datas
                        },
                        "geometry": {
                            "coordinates": [pts],
                            "type": "Polygon"
                        }
                    })
            }
           return geoDatas;
       }
    

    /**
     * 显示消息内容
     * @param {string} message 消息内容，必填
     * @param {string} type 消息类型，可选值为警告("warning")、错误("error")、信息("info")、成功("success")，参数可选， 默认错误
     * @example
      * // 示例一
      * // 显示错误消息内容"出错了"
      * this.showMessage("出错了", "error");
      * // 示例二
      * // 显示成功消息内容"已完成"
      * this.showMessage("已完成", "success");
     */
    async showMessage(message, type)  {
            return await this.context.ui.showMessage(message, type || "error");
        }
    

    /**
     * 从服务端删除用户自定义数据
     * @param {string | string[]} key 键名，如果是数组的话，可以查询
     * @param {boolean} isPrefix 是否删除所有前缀为key的所有键值，默认false
     * @returns {Promise<boolean>} 返回删除是否成功, true表示成功，false表示失败
     * @example
      * // 从服务端删除键名为drawData的用户自定义数据
      * await this.deleteCustomDataServer("drawData");
     */
    async deleteCustomDataServer(key, isPrefix = false)  {
            let svc = this.map.getService();
            let res = await svc.deleteCustomData(key, isPrefix);
            return res?.status === "ok";
        }
    

    /**
     * 创建栅格瓦片图层
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {number} options.rasterOpacity 图片的不透明度，参数可选，取值范围为 0 ~ 1，默认值为 1
     * @param {number} options.rasterHueRotate 在色轮上旋转色相的角度，参数可选，默认值为 0，单位：角度
     * @param {number} options.rasterBrightnessMin 图片的最小亮度，参数可选，取值范围为 0 ~ 1，默认值为 0
     * @param {number} options.rasterBrightnessMax 图片的最大亮度，参数可选，取值范围为 0 ~ 1，默认值为 1
     * @param {number} options.rasterSaturation 图片的饱和度，参数可选，取值范围为 -1 ~ 1，默认值为 0
     * @param {number} options.rasterContrast 图片的对比度，参数可选，取值范围为 -1 ~ 1，默认值为 0
     * @param {number} options.rasterFadeDuration 切换瓦片时的渐隐时间，参数可选，默认值为 300，单位：毫秒
     * @param {string} options.rasterResampling 采样方式，参数可选，可选值为 linear、nearest，默认值为 linear
     * @param {string} options.tileMapId 瓦片的mapid，参数可选
     * @param {string} options.tileMapVersion 瓦片的版本，参数可选
     * @param {string} options.tileMapLayer 瓦片的样式图层名称，参数可选
     * @param {string|Array<string>} options.tiles 瓦片地址，支持变量 {x},{y},{z},{serviceUrl},{accessToken}
     * @param {number} options.tileSize 瓦片大小，参数可选，默认值为 512
     * @param {number[]} options.bounds 瓦片范围，参数可选，格式为[minx, miny, maxx, maxy]，默认为全图范围
     * @param {string} options.scheme 瓦片y方向，参数可选，可选值为 xyz(Y从顶部开始)、tms(Y从底部开始)，默认为 xyz
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 创建栅格瓦片图层, 瓦片的mapid为sys_hello
      * let layer = await this.createRasterLayer({
      * tileMapId: "sys_hello",
      * });
      * console.log(layer);
     */
    async createRasterLayer(options)  {
            options = options || {};
            let type = "raster";
    
            let randomId = vjmap.RandomID();
            let sourceId = `source_${type}_${randomId}`;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                sourceId,
                layerId,
                ...options,
                type: type
            }
    
            if (options.tileMapId && !options.tiles) {
                let layer = options.tileMapLayer;
                if (!layer) {
                    let style = await this.map.getService().createStyle({
                        backcolor: this.map.getService().currentMapParam().darkMode ? 0x000000 : 0xFFFFFF
                    }, options.tileMapId, options.tileMapVersion);
                    layer = style.stylename
                }
                let tileUrl = this.map.getService().rasterTileUrl({
                    mapid: options.tileMapId,
                    version: options.tileMapVersion,
                    layer: layer,
                })
                options.tiles = tileUrl;
            }
            if (options.bounds) {
                const p1 = this.map.toLngLat([options.bounds[0], options.bounds[1]]);
                const p2 = this.map.toLngLat([options.bounds[2], options.bounds[3]]);
                options.bounds = [p1[0], p1[1], p2[0], p2[1]];
            }
            let sourceData = {
                type: "raster",
                tiles: Array.isArray(options.tiles) ? options.tiles : [options.tiles],
                tileSize: options.tileSize || 256
            }
            if (options.bounds) {
                sourceData.bounds = options.bounds;
            }
            if (options.scheme) {
                sourceData.scheme = options.scheme;
            }
            this.map.addRasterSource(sourceId, sourceData);
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data: sourceData, options, layer, visible: true, create: this.createRasterLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 通过数据内容显示数据面板
     * @param {any} data 数据内容，必填
     * @param {object} options 数据面板配置，可选
     * @param {string} options.title 数据面板标题，可选
     * @param {number} options.width 数据面板宽度，可选
     * @param {number} options.height 数据面板高度，可选
     * @param {number} options.left 数据面板左边距离，可选
     * @param {number} options.top 数据面板上边距离，可选
     * @param {string} options.position 数据面板位置, 值有'top-right'、'top-left'、'bottom-right'、'bottom-left'，可选，默认"top-left"
     * @param {boolean} options.showForm 是否以表单形式显示，可选，默认false
     * @example
      * // 通过数据内容显示数据面板，数据为{"a":1,"b":2}，数据面板标题为"数据查看"，数据面板宽度为300，数据面板高度为200，数据面板左边距离为50，数据面板上边距离为100，数据面板位置为左上角，数据面板以表单形式显示
      * this.showDataPanel({a:1,b:2}, {title: "数据查看", width: 300, height: 200, left: 50, top: 100, position: "top-left", showForm: true})
     */
    showDataPanel(data, options)  {
            options = options || {};
            options.title = options.title || "数据查看";
            this.context.ui.setDataPanelContent(data, options);   
         }
    

    /**
     * 设置地图的中心坐标
     * @param {[number, number]} point 中心点坐标，格式为[x,y]
     * @example
      * this.setCenter([100, 20]); // 设置中心坐标为x为100,y为20
     */
    setCenter(point)  {
            let oldCenter = this.map.fromLngLat(this.map.getCenter()) ; // 获取当前中心坐标
            this.map.setCenter(this.map.toLngLat(point)); // 设置新的中心坐标
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.map.setCenter(this.map.toLngLat(oldCenter));
            });
        }
    

    /**
     * 清空地图数据
     * @param {object} options 选项
     * @param {boolean} [options.clearMarkers] 是否清空所有点标注
     * @param {boolean} [options.clearDraw] 是否清空所有绘制图层
     * @param {boolean} [options.clearOtherLayerData] 清空地图除底图外所有数据
     * @param {boolean} [options.clearAllData] 清空地图所有数据
     * @example
      * // 示例一
      * // 清空地图所有点标注数据
      * this.clearMapData({clearMarkers: true});
      * // 示例二
      * // 清空地图所有绘制图层数据
      * this.clearMapData({clearDraw: true});
      * // 示例三
      * // 清空地图除底图外所有数据
      * this.clearMapData({clearOtherLayerData: true});
      * // 示例四
      * // 清空地图所有数据
      * this.clearMapData({clearAllData: true});
     */
    clearMapData(options)  {
            options = options || {};
            if (options.clearMarkers === true) {
                this.map.removeMarkers();
                this.map.removePopups();
            }
            if (options.clearDraw === true) {
                let drawLayer = this.map.getDrawLayer();
                drawLayer.deleteAll();
            }
            if (options.clearOtherLayerData === true) {
                this.map.removeMarkers();
                this.map.removePopups();
                // 把所有绘图图层删除了
                // @ts-ignore
                const controls = this.map._controls;
                for(let k = controls.length - 1; k >= 0; k--) {
                    if (controls[k] instanceof vjmap.Draw.Tool) {
                        this.map.removeControl(controls[k])
                    }
                }
                let sources = this.map.getStyle().sources;
                
                for(let item in sources) {
                    if (item != "highlight-source" && sources[item].type == "geojson")
                    {
                        // 只是把类型为geojson的移除了。同时点击高亮的图层不要删除了
                        this.map.removeSourceEx(item);
                    }
                }
            } 
            if (options.clearAllData === true) {
                if (this.map.hasVectorLayer()) {
                    this.map.disableVectorLayerHoverHighlight()
                } else {
                    this.map.disableLayerClickHighlight();
                }
                // 把所有绘图图层删除了
                // @ts-ignore
                const controls = this.map._controls;
                for(let k = controls.length - 1; k >= 0; k--) {
                    if (controls[k] instanceof vjmap.Draw.Tool) {
                        this.map.removeControl(controls[k])
                    }
                }
                let sources = this.map.getStyle().sources;
                for(let item in sources) {
                    // @ts-ignore
                   this.map.removeSourceEx(item);
                }
                this.map.removeMarkers();
                this.map.removePopups();
            }
        }
    

    /**
     * 移动图层
     * @param {string} layerName 图层名称，参数必填
     * @param {object} options 其他参数，参数可选
     * @param {string} options.beforeLayerName 移动到此图层之前，参数可选
     * @param {string} options.topMost 移动到最顶层，参数可选
     * @param {string} options.bottomMost 移动到底层，参数可选
     * @example
      * // 示例一
      * // 移动图层名称为layer1的数据图层到最顶层
      * let layerName = 'layer1';
      * this.moveLayer(layerName, {
      * topMost: true
      * });
      * // 示例二
      * // 移动图层名称为layer1的数据图层到最底层
      * this.moveLayer(layerName, {
      * bottomMost: true
      * });
      * // 示例三
      * // 移动图层名称为layer1的数据图层到图层名称为layer2的数据图层之前
      * this.moveLayer(layerName, {
      * beforeLayerName: 'layer2'
      * });
     */
    moveLayer(layerName, options)  {
            let item = this.context.global.layers.find(layer => layer.layerName == layerName);
            if(item) {
                item.visible = true;
                let layerId = item.layerId;
                if (item.layer && typeof(item.layer.getLayerId) == "function") {
                    layerId = item.layer.getLayerId();
                }
                let layers = this.map.getStyle().layers;
                let oldBeforeLayerId;
                let index = layers.findIndex(item => item.id == layerId);
                if (index < layers.length - 1) {
                    oldBeforeLayerId = layers[index + 1].id;
                }
                
                if (options.topMost) {
                    this.map.moveLayer(layerId);
                } else if (options.bottomMost) {
                    this.map.moveLayer(layerId, layers[0].id);
                } else if (options.beforeLayerName) {
                    let itemBefore = this.context.global.layers.find(layer => layer.layerName == options.beforeLayerName);
                    let beforeLayerId;
                    if (itemBefore) {
                        beforeLayerId = itemBefore.layerId;
                        if (itemBefore.layer && typeof(itemBefore.layer.getLayerId) == "function") {
                            beforeLayerId = itemBefore.layer.getLayerId();
                        }
                    }
                    this.map.moveLayer(beforeLayerId);
                }
    
    
                // 添加撤销记录
                this.context.ui.addUndoRecord(() => {
                    if (oldBeforeLayerId) {
                        this.map.moveLayer(layerId, oldBeforeLayerId);
                    }
                });
            }
        }
    

    /**
     * 手动交互式绘制一条线
     * @param {object} options 配置参数, 参数可选
     * @param {boolean} options.isSnap 是否启用捕捉功能, 参数可选，默认false
     * @param {string} options.color 颜色，格式为#RRGGBB，参数可选
     * @param {number} options.width 线宽，默认1，参数可选
     * @param {number} options.data 数据值，参数可选
     * @returns {Promise<Object | undefined>} 返回绘制的图元对象，如果取消操作则返回undefined，返回的对象包含以下属性：
     * @example
      * // 手动绘制一条线
      * const line = await this.drawLineString({
      * isSnap: true,
      * color: "#FF0000",
      * width: 2
      * });
      * if (!line) return;
      * console.log("线ID:", line.id);
      * console.log("线的点坐标:", line.points);
     */
    async drawLineString(options)  {
            let points = await this.actionDrawLineString(options.isSnap);
            if (!points) return;
            return this.createLineString(points, options);
        }
    

    /**
     * 获取地图的最大、最小缩放级别
     * @returns {number} 当前地图的最大缩放级别
     */
    getMaxZoom() {  this.map.getMaxZoom() }
    

    /**
     * 移除地图全屏控件
     * @example
      * // 移除地图全屏控件
      * this.removeMapFullscreenControl();
     */
    removeMapFullscreenControl()  {
            this.context.global.controls = this.context.global.controls || {};
            let options;
            if (this.context.global.controls.fullscreenControl) {
                options = {...this.context.global.controls.fullscreenControl.options}
                this.map.removeControl(this.context.global.controls.fullscreenControl.control);
                delete this.context.global.controls.fullscreenControl;
            }
            // 增加撤销记录
            this.context.ui.addUndoRecord(() => {
                if (options) {
                    this.addMapFullscreenControl(options);
                }
            }); 
        }
    
    

    /**
     * 弹出消息确认框
     * @param {string} message 对话框内容，参数可选
     * @param {string} title 对话框标题，参数可选
     * @param {string} type 对话框类型，可选值为"warning"、"error"、"info"、"success"，参数可选
     * @returns {boolean} 返回用户点击的按钮， 点击"确定"返回true，点击"取消"返回false
     * @example
      * // 弹出消息确认框, 消息为"是否删除"
      * let res = await this.messageBoxConfirm("是否删除", "提示", "warning");
      * if (res) {
      * console.log("点击了确定")
      * } else {
      * console.log("点击了取消")
      * }
     */
    async messageBoxConfirm(message, title, type)  {
            return await this.context.ui.confirm(message || "是否确认?", title || "提示", type || "warning");
        }
    

    /**
     * 获取地图的旋转角度
     * @returns {number} 当前地图的旋转角度
     */
    getBearing() {  this.map.getBearing() }
    

    /**
     * 获取当前地图服务版本号和运行环境信息
     * @example
      * // 获取当前地图服务版本号和运行环境信息, 获取返回数据弹出数据信息框，标题为地图服务版本信息，以表单格式显示
      * let res = await this.getMapServerInfo();
      * if (res) {
      * await this.showDataInfoDialog(res, true, "地图服务版本信息");
      * }
     */
    async getMapServerInfo()  {
            let svc = this.map.getService();
            const _url = svc.baseUrl() + "version";
            let res = await svc._get(_url, {});
            if (res && "data" in res) {
                let data =  res["data"];
                return {
                    version: data.version,
                    os: data.os,
                    arch: data.arch
                };
            }
         }
    
    

    /**
     * 设置当前地图栅格底图样式
     * @param {object} options 其他参数，参数可选
     * @param {number} options.rasterOpacity 图片的不透明度，参数可选，取值范围为 0 ~ 1，默认值为 1
     * @param {number} options.rasterHueRotate 在色轮上旋转色相的角度，参数可选，默认值为 0，单位：角度
     * @param {number} options.rasterBrightnessMin 图片的最小亮度，参数可选，取值范围为 0 ~ 1，默认值为 0
     * @param {number} options.rasterBrightnessMax 图片的最大亮度，参数可选，取值范围为 0 ~ 1，默认值为 1
     * @param {number} options.rasterSaturation 图片的饱和度，参数可选，取值范围为 -1 ~ 1，默认值为 0
     * @param {number} options.rasterContrast 图片的对比度，参数可选，取值范围为 -1 ~ 1，默认值为 0
     * @param {boolean} options.showSettingForm 是否弹出设置对话框. 默认true
     * @example
      * // 设置当前地图栅格底图样式
      * this.setMapRasterLayerStyle();
     */
    async setMapRasterLayerStyle(options)  {
            options = options || {};
            let showSettingForm = options.showSettingForm === undefined ? true : options.showSettingForm;
            let oldStyle = {...this.map.getLayerStyle(this.map.getService().rasterLayerId())?.paint};
            // 将样式转换为驼峰命名
            let formattedOldStyle = {};
            for (let key in oldStyle) {
                let camelCaseKey = key.replace(/-([a-z])/g, (match, p1) => p1.toUpperCase());
                formattedOldStyle[camelCaseKey] = oldStyle[key];
            }
            oldStyle = formattedOldStyle;
            delete options.showSettingForm;
            options = {
                rasterOpacity: oldStyle.rasterOpacity || 1,
                rasterHueRotate: oldStyle.rasterHueRotate || 0,
                rasterBrightnessMin: oldStyle.rasterBrightnessMin || 0,
                rasterBrightnessMax: oldStyle.rasterBrightnessMax || 1,
                rasterSaturation: oldStyle.rasterSaturation || 0,
                rasterContrast: oldStyle.rasterContrast || 0,
                ...options,
            }
            let svc = this.map.getService();
            let layerId = svc.rasterLayerId();
            const setRasterStyle = (data) => {
                this.map.setRasterOpacity(layerId, data.rasterOpacity || 1);
                this.map.setRasterHueRotate(layerId, data.rasterHueRotate || 0);
                this.map.setRasterBrightnessMin(layerId, data.rasterBrightnessMin || 0);
                this.map.setRasterBrightnessMax(layerId, data.rasterBrightnessMax || 1);
                this.map.setRasterSaturation(layerId, data.rasterSaturation || 0);
                this.map.setRasterContrast(layerId, data.rasterContrast || 0);
            }
            if (showSettingForm) {
                let dataOptions = {
                    rasterOpacity: {
                        type:"slider",
                        field:"rasterOpacity",
                        title:"图片的不透明度",
                        value:options.rasterOpacity,
                        props:{
                            min: 0,
                            max: 1,
                            step: 0.01
                        }
                    },
                    rasterHueRotate: {
                        type:"slider",
                        field:"rasterHueRotate",
                        title:"在色轮上旋转色相的角度",
                        value: options.rasterHueRotate,
                        props:{
                            min: 0,
                            max: 360
                        }
                    },
                    rasterBrightnessMin: {
                        type:"slider",
                        field:"rasterBrightnessMin",
                        title:"图片的最小亮度",
                        value: options.rasterBrightnessMin,
                        props:{
                            min: 0,
                            max: 1,
                            step: 0.01
                        }
                    },
                    rasterBrightnessMax: {
                        type:"slider",
                        field:"rasterBrightnessMax",
                        title:"图片的最大亮度",
                        value: options.rasterBrightnessMax,
                        props:{
                            min: 0,
                            max: 1,
                            step: 0.01
                        }
                    },
                    rasterSaturation: {
                        type:"slider",
                        field:"rasterSaturation",
                        title:"图片的饱和度",
                        value: options.rasterSaturation,
                        props:{
                            min: -1,
                            max: 1,
                            step: 0.01
                        }
                    },
                    rasterContrast: {
                        type:"slider",
                        field:"rasterContrast",
                        title:"图片的对比度",
                        value: options.rasterContrast,
                        props:{
                            min: -1,
                            max: 1,
                            step: 0.01
                        }
                    },
                }
                options = await this.showDataModifyDlg({
                    data: dataOptions,
                    title: "栅格底图样式设置",
                    showForm: true,
                    applyCallback: (data) => {
                        setRasterStyle(data);
                    }
                })
                if (!options) {
                    // 如果用户点击取消，则恢复原来的样式
                    setRasterStyle(oldStyle);
                    return
                }
            }
            setRasterStyle(options);
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                setRasterStyle(oldStyle);
            });
        }
    

    /**
     * 设置地图的缩放级别
     * @param {number} zoom 要设置的缩放级别
     * @example
      * this.setZoom(10); // 设置缩放级别为10
      * // 将级别放大一级
      * let zoom = this.getZoom(); // 获取原来的级别
      * zoom = zoom + 1; // 放大一级
      * this.setZoom(zoom);
     */
    setZoom(zoom)  {
            let oldZoom = this.map.getZoom(); // 获取当前缩放级别
            this.map.setZoom(zoom) // 设置新的缩放级别
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.map.setZoom(oldZoom);
            });
        }
    

    /**
     * 测量坐标
     * @example
      * // 测量坐标
      * await this.measureCoordinate();
     */
    measureCoordinate()  {
                vjcommon.runMeasureCmd(this.map, "measureCoordinate");
            }
    
    

    /**
     * 根据数据创建等值线
     * @param {Object} options 配置参数
     * @param {number[]} options.breaks 等值线分级
     * @param {string} [options.model] 插值模型，可选值为 'exponential', 'gaussian', 'spherical'， 默认'exponential',
     * @param {number} [options.sigma2] 方差参数，默认0, 一般设置为 0 ，其他数值设了可能会出空白图
     * @param {number} [options.alpha] 方差函数的先验值，默认100,此参数可能控制钻孔扩散范围,越小范围越大,少量点效果明显，但点多了且分布均匀以后改变该数字即基本无效果了
     * @param {string[]} [options.colors] 等值面分级区间颜色
     * @param {number} [options.width] 生成等值线宽度参数，默认500
     * @param {number} [options.contoursSize] 等值面分级区间数，默认20,值越大，等值线越密
     * @param {[number, number, number, number]} [options.extent] 要绘制的范围，默认根据数据范围自动生成
     * @param {number} [options.maxHeight] 最大值要拉伸的高度，默认100
     * @param {[number, number]} [options.clipBounds] 裁剪范围，默认不裁剪
     * @param {boolean} [options.drawSymbols] 是否绘制原始点，默认false
     * @param {boolean} [options.drawPolyline] 是否绘制等值线，默认true
     * @param {boolean} [options.drawPolygon] 是否绘制等值面，默认false
     * @param {boolean} [options.drawFillExtrusion] 是否绘制拉伸等值面，默认false
     * @returns {Object} 返回对象，包含创建数据源Id数组和图层Id数组 {sourceIds: string[], layerIds: string[]}
     * @example
      * // 在地图的实际范围内随机生成50个点，每个点随机赋值一个10到150的随机值，根据这些点绘制等值线
      * let points = this.randomPointInMapBounds(50);
      * let data = points.map(p => ({x: p[0], y: p[1], value: Math.floor(Math.random() * 90) + 10}));
      * let contour = await this.vectorContour({data});
      * console.log(contour);
     */
    async vectorContour(options)  {
            options = options || {};
            options.data = options.data || [];
            let dataset = {
                "type" : "FeatureCollection",
                "features" : []
            };
            let dataMinValue = 0;
            let dataMaxValue = 0;
            options.data = options.data.map(item => {
                return Array.isArray(item) ? {
                    x: item[0],
                    y: item[1],
                    value: item[2]
                } : item;
            });
            let minx, miny, maxx, maxy;
            for (let i = 0; i < options.data.length; i++) {
                if (i == 0) {
                    dataMinValue = options.data[i].value;
                    dataMaxValue = options.data[i].value;
                    minx = options.data[i].x;
                    miny = options.data[i].y;
                    maxx = options.data[i].x;
                    maxy = options.data[i].y;
                } else {
                    if (options.data[i].value < dataMinValue) dataMinValue = options.data[i].value;
                    if (options.data[i].value > dataMaxValue) dataMaxValue = options.data[i].value;
                    if (options.data[i].x < minx) minx = options.data[i].x;
                    if (options.data[i].y < miny) miny = options.data[i].y;
                    if (options.data[i].x > maxx) maxx = options.data[i].x;
                    if (options.data[i].y > maxy) maxy = options.data[i].y;
                }
                let feature={
                    "type" : "Feature",
                    "properties" : {
                        "value" : options.data[i].value
                    },
                    "geometry" : {
                        "type" : "Point",
                        "coordinates" : this.map.toLngLat([options.data[i].x,options.data[i].y])
                    }
                };
                dataset.features.push(feature);
            }
            let extent = options.extent || vjmap.GeoBounds.fromArray([minx, miny, maxx, maxy]).scale(1.2).toArray();
            let contoursSize = options.contoursSize || 20; // 【值越大，等值线越密】等值面分级区间数，这里设置为20，可以自行设置
            let model = options.model || 'exponential'; // 当前模型方法名
            let alpha = options.alpha || 100; // 当前alpha值
            let colors = options.colors || ["#006837", "#1a9850", "#66bd63", "#a6d96a", "#d9ef8b", "#ffffbf","#fee08b",
                "#fdae61", "#f46d43", "#d73027", "#a50026"];
            let propField = "value";
            let maxHeight = options.maxHeight || this.map.pixelToHeight(100, this.map.getZoom()) ; // 设置最大值要拉伸的高度
            let contours = [];
            let isClipContour = false;
            if (options.clipBounds) {
                isClipContour = true;
                options.clipBounds = vjmap.GeoBounds.fromArray(options.clipBounds);
            }
    
            for(let i = 0; i < contoursSize; i++) {
                contours.push(dataMinValue + (dataMaxValue - dataMinValue) * i /  (contoursSize - 1));
            }
            if (dataMinValue >= 10 && dataMaxValue > 100) {
                // 等值线的值按10取整
                for(let i = 0; i < contours.length; i++) {
                    contours[i] = (parseInt(contours[i]/10) * 10);
                }
            }
    
            let interpolateInput = [], interpolateOutput = [];
            for(let i = 0; i < colors.length; i++) {
                interpolateInput.push(i / (colors.length - 1)); // 插值输入值，这里输入0-1之间的比例
                interpolateOutput.push(colors[i]) // 插值输出值，这里输入0-1之间的比例对应的颜色值
            }
    
            // 启动webworker计算函数
            let createContourWorker = vjmap.WorkerProxy(vjmap.vectorContour);
            let { grid, contour, variogram } =  await createContourWorker(this.map.fromLngLat(dataset), propField, contours, {
                model: model || 'exponential', // 'exponential','gaussian','spherical'，三选一，默认exponential
                sigma2: options.sigma2 || 0, // sigma2是σ²，对应高斯过程的方差参数，也就是这组数据z的距离，方差参数σ²的似然性反映了高斯过程中的误差，并应手动设置。一般设置为 0 ，其他数值设了可能会出空白图
                alpha: alpha, // [如果绘制不出来，修改此值，可以把此值改小] Alpha α对应方差函数的先验值，此参数可能控制钻孔扩散范围,越小范围越大,少量点效果明显，但点多了且分布均匀以后改变该数字即基本无效果了，默认设置为100
                extent: extent, // 如果要根据数据范围自动生成此范围，则无需传此参数
                width: options.width || 500 // 生成等值线宽度参数。像素长度。默认200。宽度值越大，绘制越精确，但也会导致速度变慢，内存占用越多
            }, []);
            contour = this.map.toLngLat(contour);
           
            // 根据比例插值颜色
            const mapProgressToValues = value => vjmap.interpolate(
                interpolateInput,
                interpolateOutput,
                { ease: vjmap.linear }
            )(value)
    
            // 把原数据的颜色也设置下，绘制marker需要
            dataset.features.forEach(f => f.properties.color = mapProgressToValues((f.properties.value - dataMinValue) / (dataMaxValue - dataMinValue)))
    
            let h = maxHeight; // 设置最大值要拉伸的高度
            for(let i = 0; i < contour.features.length; i++) {
                let prop = contour.features[i].properties;
                let r = (prop.value - dataMinValue) / (dataMaxValue - dataMinValue);
                prop.color = mapProgressToValues(r); // 插值出颜色值
                prop.height = h * r; // 插值出要拉伸的高度值
            }
    
            contour.features.forEach(f => {
                f.properties.formatValue = Math.round(f.properties.value) // 多加一个用于格式化的注记的值
            })
    
            
            let markers = null;
            let markerLayerId = "marker_contour_" + vjmap.RandomID();
            const addMarkers = ()=> {
                if (markers) return;
                markers = dataset.features.map(f => {
                    if (isClipContour && !clipBounds.contains(this.map.fromLngLat(f.geometry.coordinates))) {
                        // 如果是裁剪等值线，则超出范围的marker不进行绘制
                        return null;
                    }
                    // 再随机生成不同样式的
                    let _marker = new vjmap.DiffusedApertureMarker({
                        lngLat: f.geometry.coordinates,
                        text: f.properties.value.toFixed(0)
                    }, {
                        // 可以给不同的属性，如宽度，颜色，字体
                        width: 10,
                        colors: [f.properties.color, vjmap.randomColor()],
                        textFontSize: 14,
                        textColor: f.properties.color
                    }).createMarker();
                    _marker.layerId = markerLayerId
                    _marker.addTo(this.map)
                    return _marker
                })
            }
            const removeMarkers = ()=> {
                if (!markers) return;
                for(let i = markers.length - 1; i >= 0; i--) {
                    if (markers[i]) markers[i].remove();
                }
                markers = null;
            }
    
            let symbols = null;
            const addSymbols = async ()=> {
                if (symbols) return;
                let contourData = contour;
                if (isClipContour) contourData = clipContours(contour, clipBounds);
    
                // 增加一个文字背景图片，背景色和地图背景色一样
                await this.map.addImageData("imgMaskText", `<svg viewBox="0 0 1024 1024" width="1024" height="1024" xmlns="http://www.w3.org/2000/svg">
                            <path d="M0 0L0 1024 1024 1024 1024 0z"  fill="#022B4F"></path>
                        </svg>`, 30, 15);
                symbols = new vjmap.Symbol({
                    data: contourData,
                    iconImage: "imgMaskText",
                    symbolSpacing: 250, // 符号空格,默认250像素
                    textField:  [
                        'format',
                        ["to-string", ['round', ['get', 'value']]], // 把值取整显示，如果要小数点用这个 ['number-format',['get', 'value'], { 'min-fraction-digits': 1, 'max-fraction-digits': 1 }],
                        {}
                    ],
                    textFont: ['Arial Unicode MS Regular'],
                    textSize: 14,
                    textColor: '#00ffff',
                    symbolPlacement: "line",
                    textAnchor: 'center',
                    textRotationAlignment: "this.map",
                    iconAllowOverlap: false,
                    textAllowOverlap: false
                });
                symbols.addTo(this.map);
            }
    
            const removeSymbols = ()=> {
                if (!symbols) return;
                symbols.remove();
                symbols = null;
            }
    
            let polyline = null;
            const addPolyline = ()=> {
                if (polyline) return;
                let contourData = contour;
                if (isClipContour) contourData = clipContours(contour, clipBounds);
                polyline = new vjmap.Polyline({
                    data: contourData,
                    lineColor: ['case', ['to-boolean', ['feature-state', 'hover']], '#00ffff', ['get', 'color']],
                    isHoverPointer: true,
                    isHoverFeatureState: true
                });
                polyline.addTo(this.map);
                polyline.clickPopup(f => `<h3>值: ${f.properties.value.toFixed(2)}</h3>Color: ${f.properties.color}`, { anchor: 'bottom' });
    
            }
            const removePolyline = ()=> {
                if (!polyline) return;
                polyline.remove();
                polyline = null;
                removeSymbols();
            }
            const drawPolyline = async () => {
                addPolyline();
                removeSymbols();
                await addSymbols();
            }
    
            let polygon = null;
            const addPolygon = ()=> {
                if (polygon) return;
                let contourData = contour;
                if (isClipContour) contourData = clipContoursPolygon(contour, clipBounds);
                polygon = new vjmap.Polygon({
                    data: contourData,
                    fillColor: ['case', ['to-boolean', ['feature-state', 'hover']], '#00ffff', ['get', 'color']],
                    fillOpacity: 0.9,
                    isHoverPointer: true,
                    isHoverFeatureState: true
                });
                polygon.addTo(this.map);
                polygon.clickPopup(f => `<h3>值: ${f.properties.value.toFixed(2)}</h3>Color: ${f.properties.color}`, { anchor: 'bottom' });
            }
            const removePolygon = ()=> {
                if (!polygon) return;
                polygon.remove();
                polygon = null;
                removeSymbols();
            }
    
            const drawPolygon = async () => {
                addPolygon();
                removeSymbols();
                await addSymbols();
            }
    
            let fillExtrusions = null;
            const addFillExtrusion = ()=> {
                if (fillExtrusions) return;
                fillExtrusions = new vjmap.FillExtrusion({
                    data: contour,
                    fillExtrusionColor: ['case', ['to-boolean', ['feature-state', 'hover']], '#00ffff', ['get', 'color']],
                    fillExtrusionOpacity: 0.9,
                    fillExtrusionHeight: ['get', 'height'],
                    fillExtrusionBase:0,
                    isHoverPointer: true,
                    isHoverFeatureState: true
                });
                fillExtrusions.addTo(this.map);
                fillExtrusions.clickPopup(f => `<h3>值: ${f.properties.value.toFixed(2)}</h3>Color: ${f.properties.color}`, { anchor: 'bottom' });
            }
            const removeFillExtrusion = ()=> {
                if (!fillExtrusions) return;
                fillExtrusions.remove();
                fillExtrusions = null;
                removeSymbols();
            }
    
            const drawFillExtrusion = async ()=> {
                addFillExtrusion();
                removeSymbols();
                await addSymbols();
            }
    
            let clipPolyline = null;
            const addClipPolyline = () => {
                if (clipPolyline) return;
                let points = clipBounds.toPointArray();
                points.push(points[0]);
                clipPolyline = new vjmap.Polyline({
                    data: this.map.toLngLat(points),
                    lineWidth: 2,
                    lineColor: 'red'
                });
                clipPolyline.addTo(this.map);
            }
            const removeClipPolyline = ()=> {
                if (!clipPolyline) return;
                clipPolyline.remove();
                clipPolyline = null;
            }
    
            
            // 要裁剪的多段线数组和要裁剪的范围
            const clipContours = (polylineData, clipBounds) => {
                let data = {
                    type: 'FeatureCollection',
                    features: []
                }
                for(let k = 0; k < polylineData.features.length; k++) {
                    // 遍历每一个子多边形
                    for(let n = 0; n < polylineData.features[k].geometry.coordinates.length; n++) {
                        let coordinates = this.map.fromLngLat(polylineData.features[k].geometry.coordinates[n]);
                        // 一段一段的与范围去裁剪
                        let clipCoordinates = [];
                        let clipSubCoordinates = [];
                        for(let j = 1; j < coordinates.length; j++) {
                            let p1 = vjmap.geoPoint(coordinates[j - 1]);
                            let p2 = vjmap.geoPoint(coordinates[j]);
                            let res = vjmap.clipSegment(p1, p2, clipBounds);
                            if (res === false) {
                                // 在范围外，之前的线段弄个单独一条线段，后面的重新做为一条新的线段
                                if (clipSubCoordinates.length > 0) {
                                    clipCoordinates.push(this.map.toLngLat(clipSubCoordinates));
                                    clipSubCoordinates = [];
                                }
                                continue;
                            }
                            let pt1 = [res[0].x, res[0].y];
                            let pt2 = [res[1].x, res[1].y];
                            clipSubCoordinates.push(pt1, pt2);
                        }
                        if (clipSubCoordinates.length > 0) {
                            clipCoordinates.push(this.map.toLngLat(clipSubCoordinates));
                        }
                        if (clipCoordinates.length > 0) {
                            for(let i = 0; i < clipCoordinates.length; i++) {
                                let feature = vjmap.cloneDeep(polylineData.features[k]);
                                feature.geometry.type = "LineString" ;// 由多边形改成多段线
                                feature.geometry.coordinates = clipCoordinates[i];
                                data.features.push(feature);
                            }
                        }
                    }
                }
                return data
            }
    
            // 要裁剪的多边形和要裁剪的范围
            const clipContoursPolygon = (polygonData, clipBounds) => {
                let data = {
                    type: 'FeatureCollection',
                    features: []
                }
                for (let k = 0; k < polygonData.features.length; k++) {
                    // 遍历每一个子多边形
                    for (let n = 0; n < polygonData.features[k].geometry.coordinates.length; n++) {
                        let feature = vjmap.cloneDeep(polygonData.features[k]);
                        feature.geometry.coordinates = []
                        let coordinates = this.map.fromLngLat(polygonData.features[k].geometry.coordinates[n]);
                        let clipCoordinates = vjmap.clipPolygon(coordinates, clipBounds)
                        if (clipCoordinates.length > 2) {
                            clipCoordinates.push(clipCoordinates[0]); //闭合
                            feature.geometry.coordinates.push(this.map.toLngLat(clipCoordinates));
                            feature.properties.area = vjmap.calcPolygonArea(clipCoordinates)
                            data.features.push(feature)
                        }
                    }
                }
                // 按面积大小排下序。有时候裁剪后会导致一些面积大的覆盖之前面积小的，导致小的看不见
                data.features = data.features.sort((a, b) => b.properties.area - a.properties.area)
                return data
            }
    
            if (options.drawSymbols) {
                addMarkers();
            }
            if (options.drawPolyline !== false) {
                await drawPolyline();
            }
            if (options.drawPolygon) {
                await drawPolygon();
            }
            if (options.drawFillExtrusion) {
                await drawFillExtrusion();
            }
            if (isClipContour) {
                addClipPolyline();
            }
    
            let sourceIds = [];
            let layerIds = [];
            if (markers) {
                sourceIds.push(markerLayerId);
                layerIds.push(markerLayerId);
            }
            if (symbols) {
                sourceIds.push(symbols.sourceId);
                layerIds.push(symbols.layerId);
            }
            if (polyline) {
                sourceIds.push(polyline.sourceId);
                layerIds.push(polyline.layerId);
            }
            if (polygon) {
                sourceIds.push(polygon.sourceId);
                layerIds.push(polygon.layerId);
            }
            if (fillExtrusions) {
                sourceIds.push(fillExtrusions.sourceId);
                layerIds.push(fillExtrusions.layerId);
            }
            if (clipPolyline) {
                sourceIds.push(clipPolyline.sourceId);
                layerIds.push(clipPolyline.layerId);
            }
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
               for(let i = 0; i < sourceIds.length; i++) {
                if (sourceIds[i].indexOf("marker_contour_")  == 0) {
                    // 如果是marker, 需要单独处理下
                    let markers = this.map.getMarkers();
                    // 倒序删除
                    for(let j = markers.length - 1; j >= 0; j--) {
                        if (markers[j].layerId == sourceIds[i]) {
                            markers[j].remove();
                        }
                    }
                } else {
                    this.map.removeSourceEx(sourceIds[i]);
                }
               }
            });
    
            return { sourceIds, layerIds }
        }
    

    /**
     * 获取WMS瓦片URL
     * @param {object} options WMS配置选项
     * @param {string} [options.mapid] 地图ID
     * @param {string} [options.version] 地图版本(为空时采用当前打开的地图版本)
     * @param {string} [options.layer] 图层样式名称(如果为空时，则用默认样式)
     * @param {string} [options.epsg] cad图epsg代号
     * @param {"beijing54"|"xian80"|"cgs2000"} [options.crs] cad图的坐标系,beijing54(北京54坐标)、xian80(西安80坐标、cgs2000(CGCS2000坐标)
     * @param {string} [options.webMapTiles] 叠加地图为互联网地图时，数据瓦片地址, 可为空
     * @param {boolean} [options.overMapImage] 叠加地图为互联网地图时，是否为影像地图
     * @param {boolean} [options.isSetRotateZero] 公共点叠加时是否允许旋转
     * @param {boolean} [options.baseMapWgs84] 底图是否为互联网地图wgs84坐标
     * @returns {Promise<string[]>} 返回wms瓦片URL数组
     * @example
      * // 叠加的地图mapid为sys_hello, 获取WMS瓦片URL, 根据此瓦片地址, 创建栅格瓦片图层
      * let wmsTileUrl = await this.getWmsTileUrl({
      * mapid: "sys_hello"
      * });
      * let layer = await this.createRasterLayer({
      * tiles: wmsTileUrl,
      * });
      * console.log(layer);
     */
    async getWmsTileUrl(options)  {
            options = options || {}
            let svc = this.map.getService();
            // 判断底图是否为互联网地图
            let isWebMap = this.map.getMapProjection() instanceof vjmap.LnglatProjection;
            let baseMapType = "CAD"
            if (isWebMap) {
                if (options.baseMapWgs84 === undefined) {
                    // 自动获取下
                    baseMapType = "GCJ02";
                    let sources = this.map.getStyle().sources;
                    for (let name in sources) {
                        let source = sources[name];
                        if (source.type === 'raster') {
                            let tiles = source.tiles;
                            if (tiles && tiles.length > 0) {
                                let tile = tiles[0];
                                if (tile.includes('tianditu') || tile.includes('openstreetmap')) {
                                    baseMapType = "WGS84";
                                    break;
                                }
                            }
                        }
                    }
                } else {
                    baseMapType = options.baseMapWgs84 === true ? "WGS84" : "GCJ02";
                }
               
            }
            let mapParam  = svc.currentMapParam() || {}
            let baseMapParam = {
                // 底图类型 
                baseMapType: baseMapType,
                // 地图ID
                mapid: mapParam.mapid,
                // 地图版本
                version: mapParam.version,
                // 地图范围
                mapbounds: JSON.stringify(this.map.getGeoBounds(1.0).toArray())
            }
    
            let overlayType = options.overlayType;
            if (overlayType !== "param") {
                if (options.epsg !== undefined) {
                    overlayType = "auto";
                } else {
                    let epsg = await this.getCadEpsgCode(options);
                    if (epsg) {
                        options.epsg = epsg;
                        overlayType = "auto";
                    }
                }
            }
           
            if (overlayType === undefined && options.coordinates !== undefined) {
                overlayType = "param";
            }
    
            let overMapType = options.overMapType || "CAD";
            
            if (options.webMapTiles === undefined) {
                let isImageUrl =  options.overMapImage === true; // 是否为影像地图
                if (overMapType === "WGS84") {
                    options.webMapTiles = vjcommon.tiandituProviderTiles(isImageUrl);
                } else if (overMapType === "GCJ02") {
                    options.webMapTiles = vjcommon.gaodeProviderTiles(isImageUrl);
                }
            }
            if (overlayType === "auto" && !options.epsg) {
                this.showMessage("请设置CAD图的epsg参数"); 
                return;
            }
            let overMapParam = {
                // 地图ID(为空时采用当前打开的mapid)
                mapid: options.mapid,
                // 地图版本(为空时采用当前打开的地图版本)
                version: options.version ,
                // 图层样式名称
                layer: options.layer,
                // cad图的坐标系
                crs: options.epsg,
                // 叠加类型
                baseMapType: overMapType,
                // 底图为互联网地图时，数据瓦片地址
                webMapTiles: Array.isArray(options.webMapTiles) ? options.webMapTiles : [options.webMapTiles]  ,
                isSetRotateZero: options.isSetRotateZero, // 公共点叠加时是否允许旋转
                coordinates: options.coordinates // 公共点坐标 x1,y1底图坐标, x2,y2对应点坐标
            };
           
            return await vjcommon.getWmsTileUrl(svc, baseMapParam, overlayType || "direct", {maps: overMapParam})
        }
    

    /**
     * 导出为新的CAD图形
     */
    async exportCAD()  {
            this.map.logInfo("正在导出dwg并打开，请稍候...")
            const res = await vjcommon.exportDwg(this.map, this.map.getDrawLayer());
            if (res.error) {
                this.map.logInfo(res.error, "error");
                return;
            }
            const isCloud = this.map.getService().serverUrl.indexOf("vjmap.com") >= 0;
            const host = isCloud ? "https://vjmap.com/app/cloud" : (window.location.origin + "/_cloud");
            const url = host + `/#/this.map/${res.mapid}?mapopenway=Memory&version=${res.version}`;
            window.open(url);
          }
    
    

    /**
     * 创建路径动画数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {boolean} options.isHoverPointer 是否启用鼠标悬浮事件，参数可选
     * @param {boolean} options.isHoverFeatureState 是否启用悬浮实体状态，参数可选
     * @param {string} options.lineColor 线条颜色，默认#FF9900
     * @param {number} options.lineWidth 线宽，默认10
     * @param {number} options.lineOpacity 线透明度，取值0-1
     * @param {number} options.lineGapWidth 线间隙宽度，在线的实际路径之外绘制描边时的内部间隙宽度
     * @param {number} options.lineOffset 线的偏移量，正值向右偏移，负值向左偏移
     * @param {number} options.lineBlur 线模糊度，需小于线宽度
     * @param {string} options.strokeStyle 线型，'solid'实线或'dashed'虚线
     * @param {[number,number]} options.lineDasharray 虚线样式，格式如[1,1]表示实线虚线的组合
     * @param {[number,number]} options.lineTranslate 平移量，格式[x,y]，负数分别表示左和上
     * @param {string} options.lineCap 线帽形状，'butt'尖头、'round'圆头、'square'平头
     * @param {string} options.lineJoin 拐角形状，'bevel'平拐、'round'圆拐、'miter'棱拐
     * @param {string} options.strokeImage 线图案，需与预加载的图片名对应
     * @param {string} options.lineGradient 线渐变
     * @param {boolean} options.showBorder 是否显示线边框，默认true
     * @param {string} options.borderColor 边框颜色，默认#f00
     * @param {number} options.borderWidth 边框宽度
     * @param {number} options.borderOpacity 边框透明度，取值0-1
     * @param {number} options.borderGapWidth 边框间隙宽度
     * @param {number} options.borderOffset 边框偏移量
     * @param {number} options.borderBlur 边框模糊度
     * @param {string} options.borderGradient 边框渐变
     * @param {boolean} options.showDir 是否显示箭头，默认true
     * @param {number} options.dirSize 箭头大小
     * @param {number} options.dirSpacing 箭头间距
     * @param {number} options.dirImageWidth 箭头图像宽度
     * @param {number} options.dirImageHeight 箭头图像高度
     * @param {string} options.dirImageColor 箭头图像颜色
     * @param {string} options.dirIconColor 箭头图标颜色
     * @param {string} options.dirLayout 箭头布局
     * @param {string} options.cursor 鼠标光标形状
     * @param {string} options.iconImage 图标名称，用于绘制图像背景的图像名称
     * @param {number} options.iconRotate 图标旋转角度，顺时针旋转，单位为角度，默认0
     * @param {[number,number]} options.iconOffset 图标与锚点偏移距离，[x,y]格式，正值向右和向下
     * @param {string} options.iconAnchor 图标锚定位置，可选值: "center", "left", "right", "top", "bottom", "top-left", "top-right", "bottom-left", "bottom-right"
     * @param {string} options.iconRotationAlignment 图标旋转对齐方式，可选值: "map", "viewport", "auto"
     * @param {string} options.iconPitchAlignment 地图倾斜时图标方向，可选值: "map", "viewport", "auto"
     * @param {boolean} options.iconAllowOverlap 是否允许图标重叠，默认false
     * @param {number} options.iconOpacity 图标透明度，0-1之间，默认1
     * @param {boolean} options.iconIgnorePlacement 是否忽略碰撞检测，默认false
     * @param {boolean} options.iconOptional 图标重叠选项，默认false
     * @param {number} options.iconSize 图标缩放比例，默认1
     * @param {string} options.iconTextFit 图标适应文本方式，可选值: "none", "width", "height", "both"
     * @param {[number,number,number,number]} options.iconTextFitPadding 图标文本匹配padding，[top, right, bottom, left]格式
     * @param {number} options.iconPadding 图标边界填充大小，默认1像素
     * @param {boolean} options.iconKeepUpright 是否防止图标倒置，默认false
     * @param {string} options.iconColor SDF图标颜色，默认"#000000"
     * @param {string} options.iconHaloColor SDF图标光晕颜色，默认"rgba(0, 0, 0, 0)"
     * @param {number} options.iconHaloWidth 图标光晕宽度，默认0
     * @param {number} options.iconHaloBlur 图标光晕模糊度，默认0
     * @param {[number,number]} options.iconTranslate 图标平移距离，[x,y]格式
     * @param {string} options.iconTranslateAnchor 图标平移参考系，可选值: "map", "viewport"
     * @param {string|Expression} options.textField 文本标签内容
     * @param {string[]} options.textFont 文本字体，默认["Open Sans Regular","Arial Unicode MS Regular"]
     * @param {number} options.textSize 文本大小，默认16
     * @param {boolean} options.textAllowOverlap 是否允许文本重叠，默认false
     * @param {string} options.textColor 文本颜色，默认"#000000"
     * @param {number} options.textOpacity 文本透明度，0-1之间，默认1
     * @param {[number,number]} options.textOffset 文本偏移，[x,y]格式
     * @param {number} options.textRotate 文本旋转角度
     * @param {number} options.textPadding 文本内边距，默认2
     * @param {boolean} options.textKeepUpright 是否防止文本倒置，默认true
     * @param {number} options.textMaxWidth 文本最大宽度，默认10em
     * @param {number} options.textLineHeight 文本行高，默认1.2
     * @param {number} options.textLetterSpacing 文字间距，默认0
     * @param {string} options.textJustify 文本对齐方式，可选值: "auto", "left", "center", "right"
     * @param {string} options.textAnchor 文本锚点位置
     * @param {string} options.textRotationAlignment 文本旋转对齐方式
     * @param {string} options.textPitchAlignment 文本倾斜对齐方式
     * @param {number} options.textMaxAngle 相邻字符最大角度，默认45°
     * @param {string[]} options.textWritingMode 文本书写模式
     * @param {string} options.textTransform 文本转换方式，可选值: "none", "uppercase", "lowercase"
     * @param {number} options.textRadialOffset 文本径向偏移
     * @param {string[]} options.textVariableAnchor 文本可变锚点位置数组
     * @param {boolean} options.textIgnorePlacement 是否忽略文本位置
     * @param {boolean} options.textOptional 文本显示选项
     * @param {string} options.textHaloColor 文本光晕颜色
     * @param {number} options.textHaloWidth 文本光晕宽度
     * @param {number} options.textHaloBlur 文本光晕模糊度
     * @param {[number,number]} options.textTranslate 文本平移距离
     * @param {string} options.textTranslateAnchor 文本平移参考系
     * @param {string} options.symbolPlacement 符号放置位置，可选值: "point", "line", "line-center"
     * @param {number} options.symbolSpacing 符号间距，默认250
     * @param {boolean} options.symbolAvoidEdges 是否避免边缘碰撞
     * @param {string} options.symbolZOrder 符号绘制顺序，可选值: "auto", "viewport-y", "source"
     * @param {number} options.fps 动画帧率，每秒运行多少帧，默认10，把线段分成了100段，每一秒跑多少段，取值范围1-100
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer21的路径动画数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createPathAnimateLayer(geojson, {
      * layerName: 'layer21',
      * iconImage: 'icon1'
      * });
      * console.log(layer);
     */
    async createPathAnimateLayer(data, options)  {
            options = options || {};
            let type = "pathAnimate";
        
    
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                lineWidth: 10,
                showDir: true,
                showBorder: true,
                borderColor: "#f00",
                lineColor: '#FF9900',
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createPathAnimateLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 创建旋转的文本框数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {string} options.text 要显示的文字，参数可选，默认为"index ${props.index}"
     * @param {number} options.minZoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxZoom 显示最大缩放级别，参数可选，默认为24
     * @param {boolean} options.isDrawLinePoint 绘制点时包括线上所有点, 参数可选，默认为false
     * @param {number} options.width 像素宽度，参数可选，默认为24
     * @param {number} options.markerHeight 像素高度，参数可选
     * @param {string} options.color1 颜色值一，参数可选
     * @param {string} options.color2 颜色值二，参数可选
     * @param {number} options.textFontSize 字体大小，参数可选
     * @param {string} options.textColor 字体颜色，参数可选
     * @param {boolean} options.draggable 是否可拖动，参数可选，默认为false
     * @param {string} options.animationType 动画类型，参数可选，无动画 MAP_ANIMATION_NONE，弹跳 MAP_ANIMATION_BOUNCE, 坠落 MAP_ANIMATION_DROP
     * @param {number} options.rotation 旋转角度，参数可选
     * @param {string} options.rotationAlignment 旋转对齐，参数可选, 自动 auto, 相对于地图 map, 相对于视口 viewport
     * @param {string} options.pitchAlignment 倾斜对齐，参数可选, 自动 auto, 相对于地图 map, 相对于视口 viewport
     * @param {number} options.height 坐标标高值，参数可选
     * @param {number} options.scaleMaxZoom 能缩放的最大级别，参数可选
     * @param {boolean} options.removeWhenNoInMapView 视图范围内自动显隐，参数可选
     * @param {number} options.removeWhenNoInMapViewPadding 视图范围内自动显隐Padding像素范围，参数可选，默认500
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer28的旋转的文本框数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createTextBorderMarkerLayer(geojson, {
      * layerName: 'layer28'
      * });
      * console.log(layer);
     */
    async createTextBorderMarkerLayer(data, options)  {
            options = options || {};
            let type = "textBorderMarker";
        
    
            if (options.height) {
              options.height = this.map.geoLengthToHeight(options.height);
            }
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                text: "ID:${props.index}",
                width: 40,
                markerHeight: 20,
                color1: "#ff0000",
                color2: "#ffff00",
                textFontSize: 15,
                textColor: "#00ffff",
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createTextBorderMarkerLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 创建符号路径动画数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {boolean} options.isHoverPointer 是否启用鼠标悬浮事件，参数可选
     * @param {boolean} options.isHoverFeatureState 是否启用悬浮实体状态，参数可选
     * @param {string} options.iconImage 图标名称，用于绘制图像背景的图像名称
     * @param {number} options.iconRotate 图标旋转角度，顺时针旋转，单位为角度，默认0
     * @param {[number,number]} options.iconOffset 图标与锚点偏移距离，[x,y]格式，正值向右和向下
     * @param {string} options.iconAnchor 图标锚定位置，可选值: "center", "left", "right", "top", "bottom", "top-left", "top-right", "bottom-left", "bottom-right"
     * @param {string} options.iconRotationAlignment 图标旋转对齐方式，可选值: "map", "viewport", "auto"
     * @param {string} options.iconPitchAlignment 地图倾斜时图标方向，可选值: "map", "viewport", "auto"
     * @param {boolean} options.iconAllowOverlap 是否允许图标重叠，默认false
     * @param {number} options.iconOpacity 图标透明度，0-1之间，默认1
     * @param {boolean} options.iconIgnorePlacement 是否忽略碰撞检测，默认false
     * @param {boolean} options.iconOptional 图标重叠选项，默认false
     * @param {number} options.iconSize 图标缩放比例，默认1
     * @param {string} options.iconTextFit 图标适应文本方式，可选值: "none", "width", "height", "both"
     * @param {[number,number,number,number]} options.iconTextFitPadding 图标文本匹配padding，[top, right, bottom, left]格式
     * @param {number} options.iconPadding 图标边界填充大小，默认1像素
     * @param {boolean} options.iconKeepUpright 是否防止图标倒置，默认false
     * @param {string} options.iconColor SDF图标颜色，默认"#000000"
     * @param {string} options.iconHaloColor SDF图标光晕颜色，默认"rgba(0, 0, 0, 0)"
     * @param {number} options.iconHaloWidth 图标光晕宽度，默认0
     * @param {number} options.iconHaloBlur 图标光晕模糊度，默认0
     * @param {[number,number]} options.iconTranslate 图标平移距离，[x,y]格式
     * @param {string} options.iconTranslateAnchor 图标平移参考系，可选值: "map", "viewport"
     * @param {string|Expression} options.textField 文本标签内容
     * @param {string[]} options.textFont 文本字体，默认["Open Sans Regular","Arial Unicode MS Regular"]
     * @param {number} options.textSize 文本大小，默认16
     * @param {boolean} options.textAllowOverlap 是否允许文本重叠，默认false
     * @param {string} options.textColor 文本颜色，默认"#000000"
     * @param {number} options.textOpacity 文本透明度，0-1之间，默认1
     * @param {[number,number]} options.textOffset 文本偏移，[x,y]格式
     * @param {number} options.textRotate 文本旋转角度
     * @param {number} options.textPadding 文本内边距，默认2
     * @param {boolean} options.textKeepUpright 是否防止文本倒置，默认true
     * @param {number} options.textMaxWidth 文本最大宽度，默认10em
     * @param {number} options.textLineHeight 文本行高，默认1.2
     * @param {number} options.textLetterSpacing 文字间距，默认0
     * @param {string} options.textJustify 文本对齐方式，可选值: "auto", "left", "center", "right"
     * @param {string} options.textAnchor 文本锚点位置
     * @param {string} options.textRotationAlignment 文本旋转对齐方式
     * @param {string} options.textPitchAlignment 文本倾斜对齐方式
     * @param {number} options.textMaxAngle 相邻字符最大角度，默认45°
     * @param {string[]} options.textWritingMode 文本书写模式
     * @param {string} options.textTransform 文本转换方式，可选值: "none", "uppercase", "lowercase"
     * @param {number} options.textRadialOffset 文本径向偏移
     * @param {string[]} options.textVariableAnchor 文本可变锚点位置数组
     * @param {boolean} options.textIgnorePlacement 是否忽略文本位置
     * @param {boolean} options.textOptional 文本显示选项
     * @param {string} options.textHaloColor 文本光晕颜色
     * @param {number} options.textHaloWidth 文本光晕宽度
     * @param {number} options.textHaloBlur 文本光晕模糊度
     * @param {[number,number]} options.textTranslate 文本平移距离
     * @param {string} options.textTranslateAnchor 文本平移参考系
     * @param {string} options.symbolPlacement 符号放置位置，可选值: "point", "line", "line-center"
     * @param {number} options.symbolSpacing 符号间距，默认250
     * @param {boolean} options.symbolAvoidEdges 是否避免边缘碰撞
     * @param {string} options.symbolZOrder 符号绘制顺序，可选值: "auto", "viewport-y", "source"
     * @param {string} options.lineColor 线条颜色，参数可选，默认为'#ff0000'
     * @param {number} options.lineWidth 线宽，参数可选，默认为1
     * @param {number} options.lineOpacity 线透明度，参数可选，取值范围0-1
     * @param {number} options.lineGapWidth 线间隙宽度，参数可选，默认为1，单位像素
     * @param {number} options.lineOffset 线的偏移量，参数可选。正值向右/内偏移，负值向左/外偏移
     * @param {number} options.lineBlur 线模糊度，参数可选，需小于线宽度
     * @param {[number,number]} options.lineDasharray 虚线样式，参数可选，格式如[1,1]表示实线虚线的组合
     * @param {[number,number]} options.lineTranslate 平移量，参数可选，格式如[x,y]，负数表示左和上，单位像素
     * @param {string} options.lineCap 线帽形状，参数可选，可选值：'butt'(尖头)、'round'(圆头)、'square'(平头)
     * @param {string} options.lineJoin 拐角形状，参数可选，可选值：'bevel'(平拐)、'round'(圆拐)、'miter'(棱拐)
     * @param {string} options.linePattern 线图案，参数可选，需与预加载的图片名称对应，图像宽度必须是2的幂次
     * @param {string} options.lineGradient 线渐变，参数可选
     * @param {boolean} options.drawPath 是否绘制路径线，可选值: true, false
     * @param {number} options.anmiDuration 动画持续时长，单位为秒，默认5。时长为0表示没有动画
     * @param {number} options.anmiRepeat 动画持续次数，默认0。次数为0表示无限循环
     * @param {string} options.anmiRepeatType 动画重复类型，可选值: "reverse"(来回往返), "loop"(循环), "mirror"(镜像)
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer26的符号路径动画数据图层, 图标名称为_markerIcon_
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createSymbolPathAnimateLayer(geojson, {
      * layerName: 'layer26',
      * iconImage: '_markerIcon_'
      * });
      * console.log(layer);
     */
    async createSymbolPathAnimateLayer(data, options)  {
            options = options || {};
            let type = "symbolPathAnimate";
        
    
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                drawPath: true,
                anmiDuration: 5,
                lineColor: '#75E7EF',
                lineOpacity: 0.4,
                lineWidth: 20,
                iconAllowOverlap: true,
                textAllowOverlap: true,
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createSymbolPathAnimateLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 查询全部块实体
     * @param {string | string[]} blockName 要查询的块名称或块名称数组，参数可选，默认为空，为空时查询全部块
     * @returns {Object} 返回标准的GeoJson格式的FeatureCollection对象, 包含features属性, 每个feature包含geometry和properties属性,properties属性中包含如下字段:
     * @example
      * // 查询全部块实体
      * let geoJson = await this.queryMapAllBlock();
      * // 获取结果
      * console.log(geoJson)
     */
    async queryMapAllBlock(blockName)  {
            let svc = this.map.getService();
            let query = await svc.exprQueryFeature({
                // 先表达式查询所有的块实体，因为如何用条件查询的话，块已经转为了离散的空间数据，不能整体查了，所以先通过表达式查询出块信息
                /// 以下是查所有块。如果要查询某个具体的块名可以这样写  expr: `gOutReturn := if((gInFeatureType == 'AcDbBlockReference' and gInAttrs4 == '您要查询的块名称'), 1, 0);`,
                expr: `gOutReturn := if((gInFeatureType == 'AcDbBlockReference'), 1, 0);`,
                fields: "objectid,blockname,layername,positon,rotate,scaleX,scaleY,scaleZ,bounds", // 为空的话，表达所有字段
                geom: false, // 内存模式
                useCache: true,
                limit: 1000000 // 数量大些，相当于查找所有的
            })
            let blockNameSet = new Set();
            if (Array.isArray(blockName)) {
                blockName.forEach(name => {
                    blockNameSet.add(name)
                })
            } else if (typeof blockName === "string") {
                blockNameSet.add(blockName)
            }
            let geoJson = {
                type: "FeatureCollection",
                features: []
            }
            if (query.error) {
                this.map.logInfo(query.error, "error")
                return geoJson
            }
            for(let i = 0; i < query.result.length; i++) {
                if (blockNameSet.size > 0 && !blockNameSet.has(query.result[i].blockname)) {
                    continue
                }
                query.result[i].bounds = vjmap.GeoBounds.fromString(query.result[i].bounds).toArray();
                let feature = {
                    type: "Feature",
                    geometry: {
                        type: "Point",
                        coordinates: vjmap.GeoPoint.fromString(query.result[i].positon).toArray()
                    },
                    properties: query.result[i]
                }
                geoJson.features.push(feature)
            }
            return geoJson
        }
    

    /**
     * 获取地图范围
     * @param {number} 范围类型(0 地图绘制范围,1 地图实际范围, 2 地图整个范围)，默认0, 参数可选
     * @returns {[number, number, number, number]} 返回包含四个数字的数组[x1,y1,x2,y2]，表示地图范围的左下角和右上角坐标
     * @example
      * // 获取地图的范围
      * let rect = this.getMapGeoBounds();
      * let minx = rect[0];
      * let miny = rect[1];
      * let maxx = rect[2];
      * let maxy = rect[3];
      * let width = maxx - minx; // 地图范围宽度
      * let height = maxy - miny; // 地图范围高度
      * console.log(minx, miny, maxx, maxy, width, height);
      * // 获取地图实际范围
      * let rect2 = this.getMapGeoBounds(1);
      * console.log(rect2);
     */
    getMapGeoBounds(boundsType)  {
            boundsType = boundsType || 0;
            let geoBounds;
            if (boundsType == 1) {
                // CAD图实际范围(整个地图范围的0.4倍) this.map.getGeoBounds(0.4) // 正方形
                geoBounds = this.map.getGeoBounds(0.4) 
            } else if (boundsType == 2) {
                // 整个地图范围 this.map.getGeoBounds(1.0) // 正方形
                geoBounds = this.map.getGeoBounds(1)
            } else {
                // 地图绘制范围
                geoBounds = this.map.getService().currentMapParam().drawBounds || this.map.getGeoBounds(0.4) 
            }
            return geoBounds.toArray()
        }
    

    /**
     * 创建文本数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {string} options.text 要显示的文字，参数可选，默认为"index ${props.index}"
     * @param {Object} options.style 文本样式设置，参数可选
     * @param {number} options.minZoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxZoom 显示最大缩放级别，参数可选，默认为24
     * @param {boolean} options.isDrawLinePoint 绘制点时包括线上所有点, 参数可选，默认为false
     * @param {string} options.color 标记颜色，参数可选，默认为'#3FB1CE'
     * @param {number[]} options.offset 偏离XY像素值，如[0,0]，负数表示向左和向上
     * @param {number} options.rotation 标记旋转角度，顺时针旋转，默认0
     * @param {string} options.anchor 标记锚点位置，可选值：center、top、bottom、left、right、top-left、top-right、bottom-left、bottom-right，默认center
     * @param {boolean} options.draggable 是否可拖动，参数可选，默认为false
     * @param {string} options.animationType 动画类型：MAP_ANIMATION_NONE(无动画)、MAP_ANIMATION_BOUNCE(弹跳)、MAP_ANIMATION_DROP(坠落)
     * @param {string} options.rotationAlignment 旋转对齐：auto(自动)、map(相对地图)、viewport(相对视口)，默认auto
     * @param {string} options.pitchAlignment 倾斜对齐：auto(自动)、map(相对地图)、viewport(相对视口)，默认auto
     * @param {number} options.scale 缩放比例，默认1，对应高度41px宽度27px
     * @param {number} options.scaleMaxZoom 能缩放的最大级别(0-24)，小于此级别时自动缩放
     * @param {number} options.height 标高值，单位CAD图制图单位
     * @param {boolean} options.removeWhenNoInMapView 是否在视图范围内自动显隐
     * @param {number} options.removeWhenNoInMapViewPadding 视图范围内自动显隐的padding像素值，默认500
     * @param {number} options.clickTolerance 点击像素容差，默认继承地图的clickTolerance
     * @param {string} options.customImage 自定义图片地址/base64/svg内容
     * @param {number} options.customImageWidth 自定义图片宽度
     * @param {number} options.customImageHeight 自定义图片高度
     * @param {string} options.customHtml 自定义Html元素的JS代码
     * @param {string} options.popupHtml 信息窗口HTML内容
     * @param {[number,number]} options.popupOffset 信息窗口偏离XY像素值
     * @param {string} options.popupAnchor 信息窗口锚点位置，同anchor参数
     * @param {boolean} options.closeButton 是否显示关闭按钮
     * @param {boolean} options.closeOnClick 是否在点击地图时关闭弹出窗口
     * @param {boolean} options.closeOnMove 是否在地图移动后关闭信息窗口
     * @param {boolean} options.focusAfterOpen 是否尝试聚焦弹出窗口内第一个可聚焦元素
     * @param {string} options.className 添加到弹出容器的CSS类名
     * @param {string} options.maxWidth 弹出窗口最大宽度，CSS属性字符串，默认'240px'
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer27的文本数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createTextDataLayer(geojson, {
      * layerName: 'layer27'
      * });
      * console.log(layer);
     */
    async createTextDataLayer(data, options)  {
            options = options || {};
            let type = "text";
        
            if (options.height) {
              options.height = this.map.geoLengthToHeight(options.height);
            }
    
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                text: "ID:${props.index}",
                style: {
                    'cursor': 'pointer',
                    'opacity': 1,
                    'padding': '12px',
                    'border-radius': '4px',
                    'background-color': '#0ff',
                    'border-width': 0,
                    'box-shadow': '0px 2px 6px 0px rgba(97,113,166,0.2)',
                    'text-align': 'center',
                    'font-size': '14px',
                    'color': '#F33'
                },
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createTextDataLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 创建曲线数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {string} options.lineColor 线条颜色，参数可选，默认为'#ff0000'
     * @param {number} options.lineWidth 线宽，参数可选，默认为1
     * @param {number} options.lineOpacity 线透明度，参数可选，取值范围0-1
     * @param {number} options.lineGapWidth 线间隙宽度，参数可选，默认为1，单位像素
     * @param {number} options.lineOffset 线的偏移量，参数可选。正值向右/内偏移，负值向左/外偏移
     * @param {number} options.lineBlur 线模糊度，参数可选，需小于线宽度
     * @param {[number,number]} options.lineDasharray 虚线样式，参数可选，格式如[1,1]表示实线虚线的组合
     * @param {[number,number]} options.lineTranslate 平移量，参数可选，格式如[x,y]，负数表示左和上，单位像素
     * @param {string} options.lineCap 线帽形状，参数可选，可选值：'butt'(尖头)、'round'(圆头)、'square'(平头)
     * @param {string} options.lineJoin 拐角形状，参数可选，可选值：'bevel'(平拐)、'round'(圆拐)、'miter'(棱拐)
     * @param {string} options.linePattern 线图案，参数可选，需与预加载的图片名称对应，图像宽度必须是2的幂次
     * @param {string} options.lineGradient 线渐变，参数可选
     * @param {boolean} options.isHoverPointer 是否启用鼠标悬浮事件，参数可选
     * @param {boolean} options.isHoverFeatureState 是否启用悬浮实体状态，参数可选
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer8的曲线数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createCurveLayer(geojson, {
      * layerName: 'layer8'
      * });
      * console.log(layer);
     */
    async createCurveLayer(data, options)  {
            options = options || {};
            let type = "curve";
        
    
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                lineColor: '#00ffff',
                lineWidth: 3,
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createCurveLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 测量角度
     * @example
      * // 测量角度
      * await this.measureAngle();
     */
    measureAngle()  {
                vjcommon.runMeasureCmd(this.map, "measureAngle");
            }
    
    

    /**
     * 对地图重命名
     * @param {string} oldMapID 旧地图ID
     * @param {string} newMapID 新地图ID
     * @returns {Promise<boolean>} 返回重命名结果true表示成功，false表示失败
     * @example
      * // 将地图ID从old_hello重命名为new_hello
      * let ok = await this.renameMap("old_hello", "new_hello");
      * console.log(ok);
     */
    async renameMap(oldMapID, newMapID)  {
           let svc = this.map.getService();
           let res = await svc.cmdRenameMap(oldMapID, newMapID);
           return res && res.status;
       }
    

    /**
     * 叠加互联网地图
     * @param {object} options 叠加配置选项
     * @param {string} [options.epsg] cad图epsg代号
     * @param {"beijing54"|"xian80"|"cgs2000"|""} [options.crs] cad图的坐标系,beijing54(北京54坐标)、xian80(西安80坐标、cgs2000(CGCS2000坐标) 或 空
     * @param {number} [options.webmaptype] 叠加的互联网地图类型，1:高德地图，2:高德影像，3:天地图，4:天地图影像
     * @returns {Promise<string>} 返回叠加的图层名称
     * @example
      * // 示例一
      * // 叠加互联网地图,cad图的坐标系为2000坐标
      * let layerName = await this.overlayWebMap({
      * crs: "cgs2000"
      * });
      * console.log("layerName", layerName);
      * // 示例二
      * // 叠加天地图影像地图,cad图的坐标系为西安80坐标
      * let layerName2 = await this.overlayWebMap({
      * crs: "xian80",
      * webmaptype: 4
      * });
      * console.log("layerName", layerName2);
      * // 示例三
      * // 叠加互联网地图
      * let layerName3 = await this.overlayWebMap();
      * console.log("layerName", layerName3);
     */
    async overlayWebMap(options)  {
            options = {...options}
            // 判断底图是否为互联网地图
            let isWebMap = this.map.getMapProjection() instanceof vjmap.LnglatProjection;
            if (isWebMap) {
                this.showMessage("当前地图已经是互联网地图，无法叠加");
                return;
            }
            let epsg = options.epsg;
            if (!epsg) {
                epsg = await this.getCadEpsgCode(options);
            }
            let webmaptype = options.webmaptype ?? 1;
            let fp;
            if (!epsg) {
               // 用四参数叠加, 左边是默认地图，右边是互联网地图
               fp = await this.context.ui.getFourParamter({}, {webmaptype: webmaptype});
               if (!fp) return;
                if (fp.webmaptype == 1 || fp.webmaptype == 2) {
                    // 高德地图
                    options.overMapType = "GCJ02";
                    if (fp.webmaptype == 2) {
                        // 影像
                        options.overMapImage = true;
                    }
                } else if (fp.webmaptype == 3 || fp.webmaptype == 4) {
                    // 天地图
                    options.overMapType = "WGS84";
                    if (fp.webmaptype == 4) {
                        // 影像
                        options.overMapImage = true;
                    }
                }
                options.isSetRotateZero = !fp.allowsRotation;
                options.coordinates = [];
                for(let n = 0; n < fp.leftMarkers.length; n++) {
                    let leftMarker = fp.leftMarkers[n];
                    let rightMarker = fp.rightMarkers[n];
                    options.coordinates.push({
                        x1: rightMarker.x,
                        y1: rightMarker.y,
                        x2: leftMarker.x,
                        y2: leftMarker.y
                    });
                }
                options.overlayType = "param";
            } else {
                options.overlayType = "auto";
                if (webmaptype == 1 || webmaptype == 2) {
                    // 高德地图
                    options.overMapType = "GCJ02";
                    if (webmaptype == 2) {
                        // 影像
                        options.overMapImage = true;
                    }
                } else if (webmaptype == 3 || webmaptype == 4) {
                    // 天地图
                    options.overMapType = "WGS84";
                    if (webmaptype == 4) {
                        // 影像
                        options.overMapImage = true;
                    }
                }
            }
            let wmsTileUrl;
            if (options.overlayType == "param") {
                // 四参数叠加
                let b = this.map.getMapExtent();
                if (options.overMapType == "WGS84") {
                    // 天地图
                    let webMapTiles = vjcommon.tiandituProviderTiles(options.overMapImage);   
                    const tiles = webMapTiles.map(t => vjcommon.getTileShards(t)) || [];
                    wmsTileUrl = this.map.getService().webMapUrl({
                        tileCrs: "wgs84",
                        tileUrl: tiles?.map(t => t.tileUrl) ?? '',
                        tileShards: (tiles.length >= 0) ? tiles[0].tileShards : '',
                        tileToken: "",
                        tileSize: 256,
                        tileRetina: 1,
                        tileMaxZoom: 18,
                        tileFlipY: false,
                        // @ts-ignore
                        mapbounds: JSON.stringify([b.min.x,b.min.y,b.max.x,b.max.y]),
                        srs:  "EPSG:3857",
                        fourParameterBefore: fp.fourParamter
                    })
                } else {
                    // 高德地图
                    let webMapTiles = vjcommon.gaodeProviderTiles(options.overMapImage);
                    const tiles = webMapTiles.map(t => vjcommon.getTileShards(t)) || [];
                    wmsTileUrl = this.map.getService().webMapUrl({
                        tileCrs: "gcj02",
                        tileUrl: tiles?.map(t => t.tileUrl) ?? '',
                        tileShards: (tiles.length >= 0) ? tiles[0].tileShards : '',
                        tileToken: "",
                        tileSize: 256,
                        tileRetina: 1,
                        // @ts-ignore
                        tileMaxZoom: 18,
                        tileFlipY: false,
                        mapbounds: JSON.stringify([b.min.x,b.min.y,b.max.x,b.max.y]),
                        srs:  "EPSG:3857",
                        fourParameterBefore: fp.fourParamter
                    })
                }
            } else {
                // 自动叠加
                wmsTileUrl = await this.getWmsTileUrl({
                    epsg: epsg,
                    overMapType: options.overMapType,
                    overMapImage: options.overMapImage,
                    isSetRotateZero: options.isSetRotateZero,
                    coordinates: options.coordinates,
                    overlayType: options.overlayType
                });
            }
            
            if (!wmsTileUrl) return;
            // 创建栅格瓦片图层
            let layer = await this.createRasterLayer({
                tiles: wmsTileUrl,
            });
            // 把图层移动到最下面
            this.moveLayer(layer.layerName, {
                bottomMost: true
            });
            return layer
         }
    

    /**
     * 设置CAD图实体对象的隐藏或显示
     * @param {Array<string>} objectids 实体id数组
     * @param {boolean} visible 是否可见, true表示可见，false表示不可见
     */
    async setCadEntityVisible(objectids, visible)  {
          if (!this.context.global.updateMapStyleObj) {
            this.context.global.updateMapStyleObj = vjcommon.createUpdateMapStyleObj(this.map);
          }
          const updateMapStyleObj = this.context.global.updateMapStyleObj;
          if (visible) {
            await updateMapStyleObj.removeHideObjectIds(new Set(objectids));
          } else {
            await updateMapStyleObj.addHideObjectIds(new Set(objectids));
          }
          // 添加撤销记录
          this.context.ui.addUndoRecord(async () => {
              if (!visible) {
                await updateMapStyleObj.removeHideObjectIds(new Set(objectids));
              } else {
                await updateMapStyleObj.addHideObjectIds(new Set(objectids));
              }
          });
        }
    

    /**
     * 切换图纸工作区
     * @param {string} workspaceName 工作区名称, 可选，为空时则选择工作区
     * @example
      * // 切换图纸工作区
      * this.switchWorkspace();
     */
    async switchWorkspace(workspaceName)  {
            // 先获取默认的图层
            let svc = this.map.getService();
            let oldWorkspaceName = svc.getCurWorkspaceName();
            if (!workspaceName) {
                let workspaces = await svc.getWorkspaces() || [];
                let workspaceNames = workspaces.map(wp => wp.name + (wp.alias ? "[" + wp.alias + "]" : ""));
                let oldWorkspace = workspaces.find(wp => wp.name === oldWorkspaceName);
                if (oldWorkspace) {
                    oldWorkspaceName = oldWorkspace.name + (oldWorkspace.alias ? "[" + oldWorkspace.alias + "]" : "");
                }
                workspaceNames.unshift("[默认工作区]");
                workspaceName = await this.selectOptionsValue(workspaceNames, {
                    isMultiSelect: false,
                    title: "选择工作区",
                    initValue: oldWorkspaceName || "[默认工作区]"
                });
                if (!workspaceName) {
                    return;
                }
                if (workspaceName) {
                    // 取前面的名称
                    workspaceName = workspaceName.split("[")[0];
                }
            }
            
            svc.switchWorkspace(workspaceName);
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                svc.switchWorkspace(oldWorkspaceName);
            });
        }
    

    /**
     * 对坐标数据进行坐标转换
     * @param {string} srs 源坐标系名称，如 EPSG:4326
     * @param {string} crs 目标坐标系名称，如 EPSG:3857
     * @param {[number, number] | [number, number][]} points 要转换的坐标
     * @param {string | string[]} [fourParameter] 四参数(x偏移,y偏移,缩放，旋转弧度)，可选，对坐标最后进行修正
     * @param {boolean} [isInverseFourParamter] 是否是上面四参数坐标的反算
     * @returns {Promise<[number, number] | [number, number][]>} 返回转换后的坐标
     * @example
      * // 对坐标数据进行坐标转换，将坐标[120.0, 30.0]从EPSG:4326转换到EPSG:3857
      * let transformedPoints = await this.coordTransform("EPSG:4326", "EPSG:3857", [120.0, 30.0]);
      * console.log(transformedPoints);
     */
    async coordTransform(srs, crs, points, fourParameter, isInverseFourParamter)  {
           let svc = this.map.getService();
           let isOnePoint = false;
           if (Array.isArray(points)) {
            if (points.length > 0 && !Array.isArray(points[0])) {
                isOnePoint = true;
                points = vjmap.geoPoint(points);
            } else {
                points = points.map((point) => vjmap.geoPoint(point));
            }
           }
           let result = await svc.cmdTransform(srs, crs, points, fourParameter, isInverseFourParamter);
           if (result && result.error) {
               if (this.map.logInfo) this.map.logInfo(result.error, "error", 10000);
               return;
           }
           if (isOnePoint) {
            return result[0];
           }
           return result;
       }
    

    /**
     * 获取绘制的全部图元
     * @returns {<GeoJSON | undefined>} 返回选中的GeoJSON对象
     * @example
      * // 获取绘制的全部图元的图元ID
      * const geojson = this.getDrawAll();
      * if (!geojson) return;
      * let ids = [];
      * for(let i = 0; i < geojson.features.length; i++) {
      * let feature = geojson.features[i];
      * ids.push(feature.id);
      * }
      * console.log(ids);
     */
    getDrawAll()  {
            let draw = this.map.getDrawLayer();
            return this.map.fromLngLat(draw.getAll());
        }
    
    

    /**
     * 在地图范围内随机生成GeoJson数据
     * @param {Record<string, any>} options 参数，参数可选
     * @param {number} boundsType 范围类型(0 地图绘制范围,1 地图实际范围, 2 地图整个范围)，默认0, 参数可选
     * @param {string} options.type 类型，值为点 'point'，线 'line'，面 'polygon', 所有 'all'，默认'all'
     * @param {number} options.pointCount 最多点数，默认10
     * @param {number} options.lineCount 最多线数，默认10
     * @param {number} options.polygonCount 最多面数，默认10
     * @param {number} options.linePointCount 每条线上最多点数，默认3
     * @param {number} options.polygonPointCount 每个面最多点数，默认3
     * @returns {Object} 返回GeoJson数据，包含features属性, 每个feature包含geometry和properties属性,properties属性中包含index字段
     * @example
      * // 在地图范围内随机生成GeoJson数据
      * let geojson = this.randomGeojsonInMapBounds();
      * console.log(geojson);
      * // 在地图范围内随机生成GeoJson数据，类型为点，最多生成50个点
      * let ptGeoJson = this.randomGeojsonInMapBounds({
      * type: 'point',
      * pointCount: 50
      * });
      * console.log(ptGeoJson);
     */
    randomGeojsonInMapBounds(options)  {
            options = options || {};
            options.type = options.type || 'all';
            let rect = this.getMapGeoBounds(options.boundsType || 0);
            let geoBounds = vjmap.GeoBounds.fromArray(rect);
            let FeatureCollection = {
                type: "FeatureCollection",
                features: []
            };
        
            if (options.type == 'point' || options.type == 'all') {
                let coll = geoBounds.randomGeoJsonPointCollection(Math.max(options.pointCount || 10, 2));
                if (options.pointCount == 1) {
                    // @ts-ignore
                    FeatureCollection.features.push(coll.features[0]);
                } else {
                    // @ts-ignore
                    FeatureCollection.features.push(...coll.features);
                }
        
            }
            if (options.type == 'line' || options.type == 'all') {
                let coll = geoBounds.randomGeoJsonLineCollection(options.lineCount || 10, options.linePointCount || 3);
                // @ts-ignore
                FeatureCollection.features.push(...coll.features);
            }
            if (options.type == 'polygon' || options.type == 'all') {
                let coll = geoBounds.randomGeoJsonPolygonCollection(options.polygonCount || 10, options.polygonPointCount || 3);
                // @ts-ignore
                FeatureCollection.features.push(...coll.features);
            }
            for(let i = 0; i < FeatureCollection.features.length; i++) {
                let feature = FeatureCollection.features[i];
                feature.id = i;
                feature.properties = {
                    index: i
                }
            }
            return FeatureCollection;
        }
    

    /**
     * 测量距离
     * @example
      * // 测量距离
      * await this.measureDistance();
     */
    measureDistance()  {
                vjcommon.runMeasureCmd(this.map, "measureDist");
            }
    
    

    /**
     * 创建动画符号数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {boolean} options.isHoverPointer 是否启用鼠标悬浮事件，参数可选
     * @param {boolean} options.isHoverFeatureState 是否启用悬浮实体状态，参数可选
     * @param {string} options.animateImagesType 动画类型，可选值："arrow"(箭头动画), "antpath"(蚂蚁线动画), "customImage"(自定义图片), "imageSprites"(同一个图片精灵动画集)
     * @param {string} options.animateImages_arrowFillColor 箭头填充颜色，当animateImagesType为"arrow"时有效，默认"#22B14C"
     * @param {string} options.animateImages_arrowStrokeColor 箭头边框颜色，当animateImagesType为"arrow"时有效，默认"#fff"
     * @param {number} options.animateImages_arrowStrokeWidth 箭头线宽，当animateImagesType为"arrow"时有效
     * @param {number} options.animateImages_arrowWidth 箭头大小，当animateImagesType为"arrow"时有效
     * @param {string} options.animateImages_fillColor1 蚂蚁线填充颜色1，当animateImagesType为"antpath"时有效
     * @param {string} options.animateImages_fillColor2 蚂蚁线填充颜色2，当animateImagesType为"antpath"时有效
     * @param {string} options.customImageUrl 自定义图片URL或base64，当animateImagesType为"customImage"时有效
     * @param {string} options.imageSpriteUrl 图片动画集URL或base64，当animateImagesType为"imageSprites"时有效
     * @param {number} options.animateImages_spriteWidth 来源图片中精灵图所占宽度，当animateImagesType为"imageSprites"时有效
     * @param {number} options.animateImages_spriteHeight 来源图片中精灵图所占高度，当animateImagesType为"imageSprites"时有效
     * @param {number} options.animateImages_canvasWidth 画布宽度，除animateImages类型外的所有动画类型通用
     * @param {number} options.animateImages_canvasHeight 画布高度，除animateImages类型外的所有动画类型通用
     * @param {number} options.animateImages_frameCount 动画帧数，除animateImages类型外的所有动画类型通用
     * @param {boolean} options.animateImages_directionReverse 动画方向是否反向，除animateImages类型外的所有动画类型通用
     * @param {number} options.speed 动画速度，步长0.1
     * @param {boolean} options.startAutoAnimation 是否开始时自动播放动画
     * @param {string} options.symbolPlacement 符号放置位置，可选值："point"(几何图形点上),"line"(几何图形直线上),"line-center"(几何图形线中心)
     * @param {number} options.symbolSpacing 两个符号中心点之间的距离，单位像素，默认250
     * @param {boolean} options.symbolAvoidEdges 符号是否避免碰撞，默认false
     * @param {string} options.symbolZOrder 重叠符号顺序，可选值："auto","viewport-y","source"
     * @param {string} options.iconImage 图标名称，用于绘制图像背景
     * @param {number} options.iconRotate 图标旋转角度，默认0
     * @param {[number, number]} options.iconOffset 图标与锚点偏移距离，默认[0,0]
     * @param {string} options.iconAnchor 图标锚定位置，可选值："center","left","right","top","bottom","top-left","top-right","bottom-left","bottom-right"
     * @param {string} options.iconRotationAlignment 图标旋转对齐，可选值："map","viewport","auto"
     * @param {string} options.iconPitchAlignment 地图倾斜时图标方向，可选值："map","viewport","auto"
     * @param {boolean} options.iconAllowOverlap 图标是否允许重叠，默认false
     * @param {number} options.iconOpacity 图标透明度，0-1之间，默认1
     * @param {boolean} options.iconIgnorePlacement 图标是否忽略碰撞，默认false
     * @param {boolean} options.iconOptional 图标重叠选项，默认false
     * @param {number} options.iconSize 图标缩放大小，默认1
     * @param {string} options.iconTextFit 图标适应文本，可选值："none","width","height","both"
     * @param {[number,number,number,number]} options.iconTextFitPadding 图标文本匹配padding，默认[0,0,0,0]
     * @param {number} options.iconPadding 图标边界填充大小，默认1
     * @param {boolean} options.iconKeepUpright 图标是否防止颠倒呈现，默认false
     * @param {string} options.iconColor SDF图标颜色，默认"#000000"
     * @param {string} options.iconHaloColor SDF图标光圈颜色，默认"rgba(0,0,0,0)"
     * @param {number} options.iconHaloWidth 光圈到图标轮廓距离，默认0
     * @param {number} options.iconHaloBlur 图标光晕模糊宽度，默认0
     * @param {[number,number]} options.iconTranslate 图标平移距离，默认[0,0]
     * @param {string} options.iconTranslateAnchor 图标平移锚点，可选值："map","viewport"
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer4的动画符号数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createAnimateSymbolLayer(geojson, {
      * layerName: 'layer4'
      * });
      * console.log(layer);
     */
    async createAnimateSymbolLayer(data, options)  {
            options = options || {};
            let type = "animateSymbol";
    
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                animateImagesType: "arrow",
                animateImages_arrowFillColor: "#22B14C",
                animateImages_arrowStrokeColor: "#fff",
                animateImages_arrowStrokeWidth: 6,
                animateImages_canvasWidth: 128,
                animateImages_canvasHeight: 32,
                animateImages_arrowWidth: 16,
                animateImages_frameCount: 4,
                animateImages_fillColor1: "#f0fb",
                animateImages_fillColor2: "#0ffb",
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createAnimateSymbolLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 创建信息窗口数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {string} options.text 要显示的文字，参数可选，默认为"index ${props.index}"
     * @param {number} options.minZoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxZoom 显示最大缩放级别，参数可选，默认为24
     * @param {boolean} options.isDrawLinePoint 绘制点时包括线上所有点, 参数可选，默认为false
     * @param {boolean} options.isHoverPointer 是否启用鼠标悬浮事件，参数可选
     * @param {boolean} options.isHoverFeatureState 是否启用悬浮实体状态，参数可选
     * @param {string} options.html 信息窗口的HTML内容，支持props内置属性变量
     * @param {[number, number]} options.offset 以像素为单位的偏移量[x,y]，对象相对于元素的中心。负数表示向左和向上，默认[0,0]
     * @param {string} options.anchor 信息窗口锚点位置，可选值：'center'、'top'、'bottom'、'left'、'right'、'top-left'、'top-right'、'bottom-left'、'bottom-right'，默认'center'
     * @param {boolean} options.closeButton 是否显示关闭按钮，参数可选
     * @param {boolean} options.closeOnClick 是否在单击地图时关闭弹出窗口，参数可选
     * @param {boolean} options.closeOnMove 是否在地图移动后关闭信息窗口，参数可选
     * @param {boolean} options.focusAfterOpen 是否尝试聚焦弹出窗口内的第一个可聚焦元素，参数可选
     * @param {string} options.className 添加到弹出容器的以空格分隔的CSS类名，参数可选
     * @param {string} options.maxWidth 设置弹出窗口最大宽度的CSS属性字符串，如'300px'。设为'none'时自适应内容，默认'240px'
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer22的信息窗口数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createPopupDataLayer(geojson, {
      * layerName: 'layer22'
      * });
      * console.log(layer);
     */
    async createPopupDataLayer(data, options)  {
            options = options || {};
            let type = "popup";
        
    
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                color: '#3FB1CE',
                html: "<span style='color:#0000ff'>index:${props.index}</span>",
                closeButton: true,
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createPopupDataLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 增加鼠标位置控件
     * @param {number} options.digits 小数点位数，参数可选
     * @param {boolean} options.trackCenter 是否跟踪中心点位置，参数可选
     * @param {boolean} options.showLatLng 是否显示经纬度，参数可选
     * @param {boolean} options.showZoom 是否显示缩放级别，参数可选
     * @param {boolean} options.showBearing 是否显示方位角，参数可选
     * @param {boolean} options.showPitch 是否显示倾斜角，参数可选
     * @param {'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'} options.position 控件位置，参数可选, 默认'top-right'
     * @example
      * // 增加鼠标位置控件,位置右下角
      * this.addMousePositionControl({ position: 'bottom-right' });
     */
    addMousePositionControl(options)  {
            this.context.global.controls = this.context.global.controls || {};
            if (this.context.global.controls.mousePositionControl) {
                this.map.removeControl(this.context.global.controls.mousePositionControl.control);
            }
            let mousePositionControl = new vjmap.MousePositionControl(options);
            this.map.addControl(mousePositionControl, options.position);
            this.context.global.controls.mousePositionControl = {
                control: mousePositionControl,
                position: options.position,
                options: options
            };
            // 增加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.map.removeControl(mousePositionControl);
                delete this.context.global.controls.mousePositionControl;
            }); 
        }
    

    /**
     * 弹出列表选择框
     * @param {Array<string>} values 可选值列表内容，必填
     * @param {Object} options 可选值列表配置，可选
     * @param {string|string[]} options.initValue 初始值，可选，默认空
     * @param {boolean} options.isMultiSelect 是否多选，可选，默认false
     * @param {string} options.title 对话框标题，可选
     * @param {Function} options.applyCallback 点击应用按钮的回调函数，可选
     * @returns {string|string[]|undefined} 返回用户选择的值, 如果用户点击取消，则返回undefined
     * @example
      * // 示例一
      * // 弹出列表选择框，列表内容为["a","b","c"]
      * let res = await this.selectOptionsValue(["a","b","c"]);
      * if (res) {
      * // 如果单选，则返回字符串
      * console.log("选择的字符串值为:", res)
      * } else {
      * console.log("点击了取消")
      * }
      * // 示例二
      * // 弹出列表选择框，列表内容为["a","b","c","d"]，多选, 标题为"选择", 初始值为["a","b"]
      * let arr = await this.selectOptionsValue(["a","b","c","d"], {
      * isMultiSelect: true,
      * title: "选择",
      * initValue: ["a","b"]
      * });
      * if (arr) {
      * // 如果多选，则返回数组
      * console.log("选择的数组值为:", arr)
      * } else {
      * console.log("点击了取消")
      * }
     */
    selectOptionsValue(values, options)  {
            options = options || {};
            return this.context.ui.selectOptionsValue(values, {
                isMultiSelect: options.isMultiSelect || false,
                initValue: options.initValue || [],
                title: options.title || "选择",
                applyCallback: options.applyCallback || undefined
            });   
         }
    

    /**
     * 手动交互式绘制一个点符号
     * @param {object} options 配置参数, 参数可选
     * @param {boolean} options.isSnap 是否启用捕捉功能, 参数可选，默认false
     * @param {string} options.icon 符号图标名称，默认为_markerIcon_，参数可选
     * @param {number} options.iconSize 符号图标放大倍数，默认为1，参数可选
     * @param {number} options.iconRotate 符号图标旋转角度，默认为0，参数可选
     * @param {string} options.text 符号文本内容，默认为空，参数可选
     * @param {string} options.textColor 符号文本颜色，默认为空，参数可选
     * @param {number} options.textSize 符号文本大小，默认为22，参数可选
     * @param {number} options.textRotate 符号文本旋转角度，默认为0，参数可选
     * @param {number} options.textOpacity 符号文本透明度，默认为0，参数可选
     * @param {number} options.data 数据值，参数可选
     * @returns {Promise<Object | undefined>} 返回绘制的图元对象，如果取消操作则返回undefined，返回的对象包含以下属性：
     * @example
      * // 示例一
      * // 手动绘制一个点符号
      * const pt = await this.drawSymbol(); // 启用捕捉功能采绘制一个点符号
      * if (!pt) return;
      * // 点的坐标
      * let point = pt.point;
      * const x = point[0];
      * const y = point[1];
      * console.log(x, y);
      * // 图元ID
      * let id = pt.id;
      * console.log(id);
      * // 示例二
      * // 手动绘制一个点符号，符号图标名称为_pulsingDot_
      * const pt2 = await this.drawSymbol({
      * icon: "_pulsingDot_"
      * });
      * console.log(pt2);
      * // 示例三
      * // 手动绘制一个点符号，符号图标名称为空，文本内容为"Hello",文本旋转角度为45
      * const pt3 = await this.drawSymbol({
      * text: "Hello",
      * textRotate: 45
      * });
      * console.log(pt3);
     */
    async drawSymbol(options)  {
            options = options || {};
            let point = await this.actionDrawPoint(options.isSnap);
            if (!point) return;
            return this.createSymbol(point, options)
        }
    

    /**
     * 手动交互式绘制一个斜矩形
     * @param {object} options 配置参数, 参数可选
     * @param {boolean} options.isSnap 是否启用捕捉功能, 参数可选，默认false
     * @param {string} options.color 颜色，格式为#RRGGBB，参数可选
     * @param {boolean} options.isFill 是否填充，默认false，参数可选
     * @param {number} options.opacity 透明度，默认1，参数可选
     * @param {number} options.data 数据值，参数可选
     * @returns {Promise<Object | undefined>} 返回绘制的图元对象，如果取消操作则返回undefined，返回的对象包含以下属性：
     * @example
      * // 手动绘制一个斜矩形
      * const rect = await this.drawSlantRectangle({
      * isSnap: true,
      * color: "#FF0000",
      * isFill: true,
      * opacity: 0.8
      * });
      * if (!rect) return;
      * console.log("斜矩形ID:", rect.id);
      * console.log("斜矩形的点坐标:", rect.points);
     */
    async drawSlantRectangle(options)  {
            let points = await this.actionDrawSlantRectangle(options.isSnap);
            if (!points) return;
            return this.createPolygon(points, options);
        }
    

    /**
     * 增加小地图(鹰眼)控件
     * @param {'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'} options.position 控件位置，参数可选, 默认'top-right'
     * @example
      * // 增加小地图(鹰眼)控件,位置左下角
      * this.addMiniMapControl({ position: 'bottom-left' });
     */
    addMiniMapControl(options)  {
            this.context.global.controls = this.context.global.controls || {};
            if (this.context.global.controls.miniMapControl) {
                this.map.removeControl(this.context.global.controls.miniMapControl.control);
            }
            let transformOrigin = "100% 0%";
            if (options.position === 'top-left') {
                transformOrigin = "0% 0%";
            } else if (options.position === 'bottom-right') {
                transformOrigin = "100% 100%";
            } else if (options.position === 'bottom-left') {
                transformOrigin = "0% 100%";
            }
            let miniMapControl = new vjmap.MiniMapControl({
                containerStyle: {
                    background: "#31346a85",
                    transform: "scale(0.6)",
                    transformOrigin: transformOrigin
                },
                ...options
            });
            this.map.addControl(miniMapControl, options.position);
            this.context.global.controls.miniMapControl = {
                control: miniMapControl,
                position: options.position,
                options: options
            };
            // 增加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.map.removeControl(miniMapControl);
                delete this.context.global.controls.miniMapControl;
            }); 
        }
    

    /**
     * 设置地图的旋转角度
     * @param {number} bearing 要设置的旋转角度
     * @example
      * this.setBearing(45); // 设置旋转角度为45度
     */
    setBearing(bearing)  {
            let oldBearing = this.map.getBearing(); // 获取当前旋转角度
            this.map.setBearing(bearing); // 设置新的旋转角度
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.map.setBearing(oldBearing);
            });
        }
    

    /**
     * 根据根据的坐标位置直接绘制一个点符号
     * @param {[number, number]} point 点符号的坐标[x, y]，参数必填
     * @param {object} options 配置参数, 参数可选
     * @param {boolean} options.isSnap 是否启用捕捉功能, 参数可选，默认false
     * @param {string} options.color 颜色，格式为#RRGGBB，参数可选
     * @param {number} options.size 点大小，参数可选
     * @param {number} options.data 数据值，参数可选
     * @returns {Promise<Object | undefined>} 返回绘制的图元对象，如果取消操作则返回undefined，返回的对象包含以下属性：
     * @example
      * // 示例一
      * // 在位置[10, 20]处绘制一个点
      * const pt = this.createPoint([10, 20]); // 启用捕捉功能采绘制一个点坐标
      * if (!pt) return;
      * // 点的坐标
      * let point = pt.point;
      * const x = point[0];
      * const y = point[1];
      * console.log(x, y);
      * // 图元ID
      * let id = pt.id;
      * console.log(id);
      * // 示例二
      * // 在地图范围内随机生成1个点, 在这个点绘制一个点，颜色为红色，大小为10，数据值为123
      * let points = this.randomPointInMapBounds(1);
      * const pt2 = this.createPoint(points[0], {
      * color: "#FF0000",
      * size: 10,
      * data: 123
      * });
      * console.log(pt2);
     */
    createPoint(point, options)  {
            options = options || {};
            let draw = this.map.getDrawLayer();
            let id = vjmap.RandomID(10)
            draw.add({
                id,
                type: 'Feature',
                geometry: {
                    type: 'Point',
                    coordinates: this.map.toLngLat(point)
                },
                properties: {
                    "color_static": options.color,
                    "radius_static": options.size,
                    "data": options.data
                }
            });
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.map.getDrawLayer().delete(id);
            });
            return {
                id,
                point
            };
        }
    

    /**
     * 设置信息窗口Popup的属性
     * @param {string} id 信息窗口Popup的id
     * @param {Record<string, any>} options 其他参数，参数可选
     * @param {[number, number]} options.position 坐标[x, y]，参数可选，默认为undefined
     * @param {any} options.content 弹窗内容，参数可选，默认为空表示不显示弹窗
     * @param {boolean} options.showInForm 弹窗内容是否以表单形式显示，参数可 选，默认为false
     * @param {number} options.elevation 坐标标高值，参数可选，默认为0
     * @param {boolean} options.visible 是否显示，参数可选，默认为true
     * @param {any} options.data 数据内容，参数可选，默认为空
     * @example
      * // 设置id为popup_1的信息窗口的属性，坐标为[10, 20]，弹窗内容为{name: "Sensor", value: 100}, 弹窗内容以表单形式展示
      * this.setPopupProperty("popup_1", {
      * position: [10, 20],
      * content: {name: "Sensor", value: 100},
      * showInForm: true
      * });
     */
    setPopupProperty(id, options)  {
            let popups = this.context.global.popups || [];
            let item = popups.find(m => m.id == id);
            if (!item) return;
            let popup = item.popup;
            let oldOptions = {...item.options, ...options};
            if (options.position) {
                popup.setLngLat(this.map.toLngLat(options.position));
            }
            if (options.data !== undefined) {
                popup.data = options.data;
            }
            if (options.content !== undefined || options.showInForm !== undefined) {
                const dom = this.context.ui.createElementByJsonData(options.content || oldOptions.content, options.showInForm || oldOptions.showInForm)
                popup.setDOMContent(dom.element)
            }
    
            if (options.elevation !== undefined) {
                popup.setHeight(options.elevation);
            }
           
            if (options.data !== undefined) {
                popup.data = options.data;
            }
            if (options.visible !== undefined) {
                if (options.visible) {
                    if (!popup._map) {
                        popup.addTo(this.map);
                    }
                    
                } else {
                    if (popup._map) {
                        popup.remove();
                    }
                }
            }
            
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.setPopupProperty(id, oldOptions);
            });
            
         }
    

    /**
     * 清除所有选择的实体对象
     * @example
      * // 清除所有选择的实体对象
      * this.clearSelectedFeatures();
     */
    clearSelectedFeatures()  {
           vjcommon.clearHighlight(this.map);
        }
    
    

    /**
     * 设置数据图层点击显示弹窗内容
     * @param {string} layerName 图层名称，参数必填
     * @param {Record<string, any>} options 其他参数，参数可选
     * @param {any} options.popupContent 弹窗内容，默认为当前数据的属性值
     * @param {boolean} options.popupShowInForm 弹窗内容是否以表单形式显示，参数可选，默认为false
     * @param {[number, number]} options.popupOffset 弹窗偏移量，参数可选，默认为[0, -40]
     * @param {number} options.popupWidth 弹窗宽度，参数可选
     * @param {number} options.popupHeight 弹窗高度，参数可选
     * @param {boolean} options.noShowPopup 是否不显示弹窗，参数可选，默认为false
     * @param {Function} options.clickCallback 点击回调函数，参数可选，默认为null（回调函数第一个参数为弹窗内容）
     * @example
      * // 示例一
      * // 设置图层名称为layer1的数据图层点击显示弹窗, 以表单形式展示
      * this.setDataLayerClickPopupContent('layer1', {
      * popupShowInForm: true
      * });
      * // 示例二
      * // 设置图层名称为layer2的数据图层点击显示弹窗, 弹窗内容为"ID:${props.index}"
      * this.setDataLayerClickPopupContent('layer2', {
      * popupContent: "ID:${props.index}",
      * });
     */
    setDataLayerClickPopupContent(layerName, options)  {
            let item = this.context.global.layers.find(layer => layer.layerName == layerName);
            if(!item) return;
            options = options || {};
            options.isHoverPointer = options.isHoverPointer === undefined ? true : options.isHoverPointer;
            let markers = item.layer.markers;
            if (Array.isArray(markers)) {
                // 如果是Marker相关的图层
                for(let m = 0; m < markers.length; m++) {
                    let marker = markers[m];
                
                    marker.on('click', () => {
                        let popup = this.context.global.popup;
                        if (!popup && options.noShowPopup !== true) {
                            popup = new vjmap.Popup({
                                offset: options.popupOffset || [0, -40],
                                closeOnClick: false, 
                                closeButton: true, 
                                anchor: "bottom" 
                            })
                            this.context.global.popup = popup;
                        }
                        if (options.noShowPopup !== true && options.popupOffset) {
                            popup.setOffset(options.popupOffset);
                        }
                        let content = options.popupContent;
                        if (item.data && item.data.features && item.data.features[m] && item.data.features[m].properties && typeof content == "string" && !(content in item.data.features[m].properties)) {
                            content = item.layer.evalValue("return `" + content + "`", item.data.features[m].properties, this.map);
                            if (options.noShowPopup !== true) {
                                popup.setHTML(content);
                            }
                            if (options.clickCallback) {
                                options.clickCallback(content);
                            }
                        } else {
                            if (!content) {
                                content = item.data.features[m].properties;
                            } else {
                                // 如果content是对象的属性，则获取对象的属性值
                                if (item.data.features[m].properties && (content in item.data.features[m].properties)) {
                                    content = item.data.features[m].properties[content];
                                    try {
                                        // 如果content是json字符串，则尝试转换为对象
                                        content = JSON.parse(content);
                                    } catch (e) {}
                                }
                            }
                            if (options.noShowPopup !== true) {
                                const dom = this.context.ui.createElementByJsonData(content, options.popupShowInForm, {
                                    width: options.popupWidth,
                                    height: options.popupHeight
                                })
                                popup.setDOMContent(dom.element)
                            }
                            if (options.clickCallback) {
                                options.clickCallback(content);
                            }
    
                        }
                        if (options.noShowPopup !== true) {
                            popup.setLngLat(marker.getLngLat());
                            popup.addTo(this.map);
                        }
                    });
                   
                }
            } else {
                // 如果是其他图层
                let layerId = item.layerId;
                if (item.layer && typeof(item.layer.getLayerId) == "function") {
                    layerId = item.layer.getLayerId();
                }
                this.map.on("click", layerId, (ev) => {
                    if (!ev || !ev.features || !ev.features[0]) return;
                   
                    let pt = ev.lngLat;
                    let popup = this.context.global.popup;
                    if (!popup && options.noShowPopup !== true) {
                        popup = new vjmap.Popup({
                            offset: options.popupOffset || [0, -40]
                        })
                        this.context.global.popup = popup;
                    }
                    if (options.noShowPopup !== true && options.popupOffset) {
                        popup.setOffset(options.popupOffset);
                    }
                    let content = options.popupContent;
                    if (ev.features[0].properties && typeof content == "string" && !(content in ev.features[0].properties)) {
                        content = item.layer.evalValue("return `" + content + "`", ev.features[0].properties, this.map);
                        if (options.noShowPopup !== true) {
                            popup.setHTML(content);
                        }
                        if (options.clickCallback) {
                            options.clickCallback(content);
                        }
                    } else {
                        ev.features[0].properties = ev.features[0].properties || {}
                        if (!content) {
                            content = ev.features[0].properties;
                        } else {
                            // 如果content是对象的属性，则获取对象的属性值
                            if (content in ev.features[0].properties) {
                                content = ev.features[0].properties[content];
                                try {
                                    // 如果content是json字符串，则尝试转换为对象
                                    content = JSON.parse(content);
                                } catch (e) {}
                            }
                        }
                        if (options.noShowPopup !== true) {
                            const dom = this.context.ui.createElementByJsonData(content, options.popupShowInForm, {
                                width: options.popupWidth,
                                height: options.popupHeight
                            })
                            popup.setDOMContent(dom.element)
                        }
                        if (options.clickCallback) {
                            options.clickCallback(content);
                        }
                    }
                    if (options.noShowPopup !== true) {
                        popup.setLngLat(pt);
                        popup.addTo(this.map);
                    }
                });
            }
        }
    

    /**
     * 手动指定公共对应点获取四参数
     * @param {Object} [leftMapOptions] 左边地图的参数，可为空，默认当前地图
     * @param {string} [leftMapOptions.mapid] 地图ID
     * @param {string} [leftMapOptions.version] 地图版本(为空时采用当前打开的地图版本)
     * @param {Object} [rightMapOptions] 右边地图的参数，可为空，高德地图
     * @param {string} [rightMapOptions.mapid] 地图ID
     * @param {string} [rightMapOptions.version] 地图版本(为空时采用当前打开的地图版本)
     * @param {number} [leftMapOptions.webmaptype] 地图类型：1: 高德地图、2: 高德地图影像、3: 天地图、4: 天地图影像
     * @returns {Promise<Object>} 返回参数，返回内容如下
     * @example
      * // 示例一
      * // 手动指定公共对应点获取当前地图和互联网高德地图的四参数
      * let fp = await this.getFourParamter();
      * if (fp) {
      * console.log("四参数", fp.fourParamter);
      * }
      * // 示例二
      * // 手动指定公共对应点获取当前地图和cad地图sys_world的四参数
      * let fp2 = await this.getFourParamter({}, {
      * mapid: "sys_world"
      * });
      * if (fp2) {
      * console.log("四参数", fp2.fourParamter);
      * }
     */
    async getFourParamter(leftMapOptions, rightMapOptions)  {
            return await this.context.ui.getFourParamter(leftMapOptions, rightMapOptions);
          }
    

    /**
     * 切换至互联网地图做为底图
     * @param {number} webmaptype 地图类型：1: 高德地图、2: 高德地图影像、3: 天地图、4: 天地图影像，可选，默认1
     * @returns {Promise<Object>} 返回参数，返回内容如下
     * @example
      * // 示例一
      * // 切换至高德地图做为底图
      * let result = await this.switchWebMap(1);
      * console.log("result", result);
      * // 示例二
      * // 切换至天地图影像做为底图
      * let result2 = await this.switchWebMap(4);
      * console.log("result2", result2);
     */
    switchWebMap(webmaptype)  {
            if (!webmaptype) webmaptype = 1;
            this.clearMapData({clearAllData: true}); 
            let prj = new vjmap.LnglatProjection();
            let webMapTiles = [];
            if (webmaptype == 1 || webmaptype == 2) {
                webMapTiles = vjcommon.gaodeProviderTiles(webmaptype == 2);
            } else if (webmaptype == 3 || webmaptype == 4) {
                webMapTiles = vjcommon.tiandituProviderTiles(webmaptype == 4);
            }
            let tiles = webMapTiles.map(t => vjcommon.getShardsTileUrl(t, this.map))
            let sourceIds = [];
            let layerIds = [];
            for(let t of tiles) {
                let id = vjmap.RandomID();
                let sourceId = "web_source_" + webmaptype + "_" + id;
                let layerId = "web_layer_" + webmaptype + "_" + id;
                this.map.addRasterSource(sourceId, {
                    type: "raster",
                    tiles: t,
                    tileSize: 256
                });
                this.map.addRasterLayer(layerId, sourceId);
                this.map.setMapProjection(prj);
                sourceIds.push(sourceId);
                layerIds.push(layerId);
            }
            
            return {
                sourceIds: sourceIds,
                layerIds: layerIds
            }
          }
    

    /**
     * 获取数据图层属性
     * @param {string} layerName 图层名称，参数必填
     * @returns {object | undefined} 返回图层属性对象, 如果没有找到图层，则返回undefined
     * @example
      * // 获取图层名称为layer1的数据图层属性
      * let layerName = 'layer1';
      * let property = this.getDataLayerProperty(layerName);
      * console.log("图层属性:", property);
     */
    getDataLayerProperty(layerName)  {
            let item = this.context.global.layers.find(layer => layer.layerName == layerName);
            if(item) {
               return item.layer.mapLayer
            }
        }
    

    /**
     * 获取地图的缩略图
     * @param {object} options 参数 可选。
     * @param {string} [options.mapid] 地图ID. 可选，为空时则获取当前打开的地图
     * @param {string} [options.version] 版本号. 可选，为空时则获取当前打开的地图版本
     * @param {number} [options.width] 缩略图宽度. 可选
     * @param {number} [options.height] 缩略图高度. 可选
     * @param {boolean} [options.darkTheme] 是否是深色主题. 可选
     * @returns {string} 返回缩略图的URL地址
     * @example
      * // 示例一
      * // 获取地图sys_hello的缩略图
      * let url1 = this.thumbnailUrl({
      * mapid: "sys_hello"
      * });
      * console.log(url1);
      * // 示例二
      * // 获取地图sys_hello的缩略图，版本v1, 宽度200，高度200，深色主题
      * let url2 = this.thumbnailUrl({
      * mapid: "sys_hello",
      * version: "v1",
      * width: 200,
      * height: 200,
      * darkTheme: true
      * });
      * console.log(url2);
     */
    thumbnailUrl(options)  {
           let svc = this.map.getService();
           return svc.thumbnailUrl(options.mapid, options.version, options.width, options.height, options.darkTheme);
       }
    

    /**
     * 创建天空图层
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {string} options.skyType 天空类型，可选值: 'gradient'(渐变) 或 'atmosphere'(模拟大气)，默认为'atmosphere'
     * @param {string[]} options.skyGradient 渐变色数组，当skyType为'gradient'时有效
     * @param {[number, number]} options.skyGradientCenter 渐变中心点坐标[经度, 纬度]
     * @param {number} options.skyGradientRadius 渐变半径，梯度延伸的角距离（度），范围0-180，默认90
     * @param {number} options.skyOpacity 天空透明度，范围0-1，默认1
     * @param {string} options.skyAtmosphereColor 大气颜色，用于调整主要大气散射系数，默认白色
     * @param {string} options.skyAtmosphereHaloColor 大气光晕颜色，应用于大气太阳光晕，默认白色
     * @param {number} options.skyAtmosphereSunIntensity 大气太阳光强度，范围0-100，默认10
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 创建天空图层
      * let layer = await this.createSkyLayer();
      * console.log(layer);
     */
    async createSkyLayer(options)  {
            options = options || {};
            let type = "sky";
    
            let randomId = vjmap.RandomID();
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                skyType: 'gradient',
                skyGradient: [
                    'interpolate',
                    ['linear'],
                    ['sky-radial-progress'],
                    0.8,
                    'rgba(135, 206, 235, 1.0)',
                    1,
                    'rgba(0,0,0,0.1)'
                ],
                skyGradientCenter: [0, 0],
                skyGradientRadius: 90,
                skyOpacity: [
                    'interpolate',
                    ['exponential', 0.1],
                    ['zoom'],
                    0,
                    0.3,
                    22,
                    1
                ],
                ...options,
                type: type
            }
           
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId: undefined, layerName, data: undefined, options, layer, visible: true, create: this.createSkyLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId: undefined,
                layerId,
                layerName
            };
        }
    

    /**
     * 选择绘制的图元
     * @param {boolean} selectSingle 是否只能选择一个图元
     * @returns {Promise<GeoJSON | undefined>} 返回选中的GeoJSON对象，如果取消操作则返回undefined
     * @example
      * const geojson = await this.actionSelect(true);
      * if (!geojson) return;
      * // 遍历geojson的每个feature
      * for(let i = 0; i < geojson.features.length; i++) {
      * let feature = geojson.features[i];
      * console.log(feature);
      * }
     */
    async actionSelect(selectSingle)  {
            this.context.tools.logInfo("请选择绘制的图元")
            let draw = this.map.getDrawLayer();
            let selection = await vjmap.Draw.actionSelect(this.map, draw, {
                selectSingle
            });
            if (selection.cancel) {
                return;
            }
            return this.map.fromLngLat(selection);
        }
    

    /**
     * 删除一个信息窗口Popup
     * @param {string} id 信息窗口Popup的id
     * @example
      * this.deletePopup("popup_1"); // 删除id为Popup_1的信息窗口
     */
    deletePopup(id)  {
           let popups = this.context.global.popups || [];
           let item = popups.find(m => m.id == id);
           if (item) {
            item.popup.remove()
            let pt = this.map.fromLngLat(item.popup.getLngLat());
             // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.addPopup(pt, item.options);
            });
           }
          
        }
    

    /**
     * 根据json数据弹出数据信息框
     * @param {object|Array} json json数据内容,json对象或数组, 必填
     * @param {boolean} showForm 是否以表单格式显示，参数可选，默认false
     * @param {string} title 对话框标题，参数可选
     * @example
      * // 示例一
      * // 获取地图的当前中心点坐标X和Y、地图级别，生成json对象，根据这数据弹出数据信息框，标题为"地图信息"，以表单格式显示
      * let center = this.getCenter();
      * const mapInfo = {
      * centerX: center[0],
      * centerY: center[1],
      * zoom: map.getZoom()
      * }
      * await this.showDataInfoDialog(mapInfo, true, "地图信息");
      * // 示例二
      * // 矩形选择图中实体对象，根据选择的实体数据弹出数据信息框，标题为"实体信息"，信息框关闭后清除所有选择的实体对象
      * const geojson1 = await this.selectFeatures({
      * isPointSel: true
      * });
      * await this.showFormInputDialog(geojson1, false, "实体信息");
      * // 清除所有选择的实体对象
      * this.clearSelectedFeatures();
     */
    async showDataInfoDialog(json, showForm, title)  {
            return await this.context.ui.showInfoDlg(json, showForm, title)
        }
    

    /**
     * 创建扩散的点数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {string} options.text 要显示的文字，参数可选，默认为"index ${props.index}"
     * @param {number} options.minZoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxZoom 显示最大缩放级别，参数可选，默认为24
     * @param {boolean} options.isDrawLinePoint 绘制点时包括线上所有点, 参数可选，默认为false
     * @param {number} options.width 像素宽度，参数可选，默认为24
     * @param {number} options.markerHeight 像素高度，参数可选
     * @param {string} options.color1 颜色值一，参数可选
     * @param {string} options.color2 颜色值二，参数可选
     * @param {number} options.textFontSize 字体大小，参数可选
     * @param {string} options.textColor 字体颜色，参数可选
     * @param {boolean} options.draggable 是否可拖动，参数可选，默认为false
     * @param {string} options.animationType 动画类型，参数可选，无动画 MAP_ANIMATION_NONE，弹跳 MAP_ANIMATION_BOUNCE, 坠落 MAP_ANIMATION_DROP
     * @param {number} options.rotation 旋转角度，参数可选
     * @param {string} options.rotationAlignment 旋转对齐，参数可选, 自动 auto, 相对于地图 map, 相对于视口 viewport
     * @param {string} options.pitchAlignment 倾斜对齐，参数可选, 自动 auto, 相对于地图 map, 相对于视口 viewport
     * @param {number} options.height 坐标标高值，参数可选
     * @param {number} options.scaleMaxZoom 能缩放的最大级别，参数可选
     * @param {boolean} options.removeWhenNoInMapView 视图范围内自动显隐，参数可选
     * @param {number} options.removeWhenNoInMapViewPadding 视图范围内自动显隐Padding像素范围，参数可选，默认500
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer9的扩散的点数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createDiffusedMarkerLayer(geojson, {
      * layerName: 'layer9'
      * });
      * console.log(layer);
     */
    async createDiffusedMarkerLayer(data, options)  {
            options = options || {};
            let type = "diffusedMarker";
        
    
            if (options.height) {
              options.height = this.map.geoLengthToHeight(options.height);
            }
            
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                text: "ID:${props.index}",
                width: 30,
                markerHeight: 30,
                color1: "#ff0000",
                color2: "#ffff00",
                textFontSize: 15,
                textColor: "#00ffff",
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createDiffusedMarkerLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 根据给定的点坐标数组直接绘制一条线
     * @param {[number, number][]} points 线的坐标点数组[[x1,y1],[x2,y2],...]，参数必填
     * @param {object} options 配置参数, 参数可选
     * @param {string} options.color 颜色，格式为#RRGGBB，参数可选
     * @param {number} options.width 线宽，默认1，参数可选
     * @param {number} options.data 数据值，参数可选
     * @returns {Object} 返回绘制的图元对象，返回的对象包含以下属性：
     * @example
      * // 根据三个地图上面的随机点创建一条线， 线的宽度为2-5，颜色红色
      * let points = this.randomPointInMapBounds(3); // 每条线3个点
      * const line = this.createLineString(points, {
      * color: vjmap.randomColor(),
      * width: 2 + Math.random() * 3
      * });
      * console.log(`线 ID:`, line.id);
      * console.log(`线 点坐标:`, line.points);
     */
    createLineString(points, options)  {
            let draw = this.map.getDrawLayer();
            let id = vjmap.RandomID(10);
            draw.add({
                id,
                type: 'Feature',
                geometry: {
                    type: 'LineString',
                    coordinates: points.map(pt => this.map.toLngLat(pt))
                },
                properties: {
                    color: options.color,
                    line_width: options.width,
                    data: options.data
                }
            });
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.map.getDrawLayer().delete(id);
            });
            return {
                id,
                points
            };
        }
    

    /**
     * 移除地图比例尺控件
     * @example
      * // 移除地图比例尺控件
      * this.removeMapScaleControl();
     */
    removeMapScaleControl()  {
            this.context.global.controls = this.context.global.controls || {};
            let options;
            if (this.context.global.controls.scaleControl) {
                options = {...this.context.global.controls.scaleControl.options}
                this.map.removeControl(this.context.global.controls.scaleControl.control);
                delete this.context.global.controls.scaleControl;
            }
            // 增加撤销记录
            this.context.ui.addUndoRecord(() => {
                if (options) {
                    this.addMapScaleControl(options);
                }
            });
        }
    
    

    /**
     * 增加地图导航条控件
     * @param {boolean} options.showCompass 是否显示指南针，参数可选
     * @param {boolean} options.showZoom 是否显示缩放按钮，参数可选
     * @param {boolean} options.visualizePitch 是否显示倾斜按钮，参数可选
     * @param {'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'} options.position 控件位置，参数可选, 默认'top-right'
     * @example
      * // 增加地图导航条控件,位置左上角
      * this.addMapNavigationControl('top-left');
     */
    addMapNavigationControl(options)  {
            options = options || {};
            this.context.global.controls = this.context.global.controls || {};
            if (this.context.global.controls.navigationControl) {
                this.map.removeControl(this.context.global.controls.navigationControl.control);
            }
            let navigationControl = new vjmap.NavigationControl(options);
            this.map.addControl(navigationControl, options.position);
            this.context.global.controls.navigationControl = {
                control: navigationControl,
                position: options.position,
                options: options
            };
            // 增加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.map.removeControl(navigationControl);
                delete this.context.global.controls.navigationControl;
            }); 
        }
    

    /**
     * 取消监听地图事件
     * @param {string} eventName 事件名称，参数必填, 事件名称有:
     * @param {function | undefined} callback 事件回调函数，可为空，为空时取消所有事件，默认undefined
     * @example
      * // 取消监听地图单击事件
      * this.cancelListenMapEvent("click");
     */
    cancelListenMapEvent(eventName, callback)  {
            if (eventName === "clickEntity") {
                this.map.disableLayerClickHighlight();
                if (this.context.global._delegatedListeners) delete this.context.global._delegatedListeners[eventName];
                return;
            }
            if (callback) {
                this.map.off(eventName, callback);
    
                if (this.context.global._delegatedListeners && this.context.global._delegatedListeners[eventName]) {
                    let index = this.context.global._delegatedListeners[eventName].indexOf(callback);
                    if (index !== -1) {
                        this.context.global._delegatedListeners[eventName].splice(index, 1);
                    }
                }
                // 添加撤销记录
                this.context.ui.addUndoRecord(() => {
                    this.map.on(eventName, callback);
    
                    this.context.global._delegatedListeners = this.context.global._delegatedListeners || {};
                    this.context.global._delegatedListeners[eventName] = this.context.global._delegatedListeners[eventName] || [];
                    this.context.global._delegatedListeners[eventName].push(callback);
                });
            } else {
                if (!this.context.global._delegatedListeners) return;
                if (!this.context.global._delegatedListeners[eventName]) return;
                const listener = this.context.global._delegatedListeners[eventName];
                if (listener) {
                    let saveListener = [...listener]; 
                    listener.forEach(func => {
                        this.map.off(eventName, func);
    
                        if (this.context.global._delegatedListeners && this.context.global._delegatedListeners[eventName]) {
                            let index = this.context.global._delegatedListeners[eventName].indexOf(func);
                            if (index !== -1) {
                                this.context.global._delegatedListeners[eventName].splice(index, 1);
                            }
                        }
                    });
                    // 添加撤销记录
                    this.context.ui.addUndoRecord(() => {
                        saveListener.forEach(func => {
                            this.map.on(eventName, func);
                            
                            this.context.global._delegatedListeners = this.context.global._delegatedListeners || {};
                            this.context.global._delegatedListeners[eventName] = this.context.global._delegatedListeners[eventName] || [];
                            this.context.global._delegatedListeners[eventName].push(func);
                        });
                    });
                }
            }
            
        }
    

    /**
     * 创建热力图数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {boolean} options.isHoverPointer 是否启用鼠标悬浮事件，参数可选
     * @param {boolean} options.isHoverFeatureState 是否启用悬浮实体状态，参数可选
     * @param {Array|number} options.heatmapWeight 热力图权重，表示点对热力图的贡献度，值越大显示越明显，参数可选，默认值为[0,1]区间
     * @param {Array|number} options.heatmapRadius 热力图点的计算半径，单位为像素，参数可选，默认值为30
     * @param {Array|string} options.heatmapColor 热力图的颜色配置，可设置不同数值的颜色表现，参数可选
     * @param {Array|number} options.heatmapIntensity 热力图整体强度，类似权重但作用于整体，参数可选，默认值随缩放级别在1-3之间
     * @param {Array|number} options.heatmapOpacity 热力图透明度，取值范围0-1，参数可选，默认值随缩放级别变化
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer16的热力图数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createHeatmapLayer(geojson, {
      * layerName: 'layer16'
      * });
      * console.log(layer);
     */
    async createHeatmapLayer(data, options)  {
            options = options || {};
            let type = "heatmap";
        
    
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                // heatmapWeight：表示一个点对热力图权重的贡献，在贡献越大的地方热力图显示应该越明显
                heatmapWeight:  [
                  'interpolate',
                  ['linear'],
                  ['get', 'value'],
                  0, // 因为上面用了0,100，最小和最大值，把这两个最小和最大值归化到0,1区间
                  0,
                  100,
                  1
                ],
                // heatmapRadius：热力图的一个点计算权重的时候计算的点的半径，单位为像素，默认为30
                heatmapRadius: [
                    'interpolate',
                    ['linear'],
                    ['zoom'],
                    0, //0级别
                    10, //半径10
                    9, // 9级别 (其余级别，线性插值)
                    50 // 半径50
                ],
                // heatmapColor：热力图的颜色，设置在各个热力图的数值上是什么颜色
                heatmapColor: [
                    'interpolate',
                    ['linear'],
                    ['heatmap-density'],
                    0,
                    'rgba(33,102,172,0)',
                    0.2,
                    'rgb(103,169,207)',
                    0.4,
                    'rgb(209,229,240)',
                    0.6,
                    'rgb(253,219,199)',
                    0.8,
                    'rgb(239,138,98)',
                    1,
                    'rgb(178,24,43)'
                ],
                //heatmapIntensity：热力图强度，有点类似于heatmapWeight属性，但是该属性是设置整体上热力图的强度
                heatmapIntensity: [
                    'interpolate',
                    ['linear'],
                    ['zoom'],
                    0,
                    1,
                    9,
                    3
                ],
                //heatmapOpacity：热力图的透明度
                heatmapOpacity: [
                    'interpolate',
                    ['linear'],
                    ['zoom'],
                    7,
                    1,
                    9,
                    0
                ],
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加��层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createHeatmapLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 给定路线、起始点、终点生成最短路径
     * @param {Object} options 配置参数
     * @param {[number, number]} options.startPoint 起点 [lng, lat]
     * @param {[number, number]} options.endPoint 终点 [lng, lat]
     * @param {number} [options.precision] 误差，小数点后几位，默认为0
     * @param {boolean} [options.hasDirection] 是否考虑方向，默认为false
     * @param {boolean} [options.disableDrawPath] 禁止自动绘制路径，默认为false
     * @param {string} [options.drawPathColor] 绘制路径颜色，默认为"#ff0000"
     * @param {number} [options.drawPathWidth] 绘制路径宽度，默认为5
     * @returns {Object | null} 返回最短路径信息，包括起点、终点、路径和详细路径信息、绘制路径的图层id、绘制路径的图层源id, 如果返回null，则表示没有找到路径, 具体返回参数如下：
     * @example
      * // 给定路线[[116.397428, 39.90923], [116.405285, 39.90923]]、[[116.405285, 39.90923], [116.425285, 39.91923]]、起始点[116.397428, 39.90923]、终点[116.405285, 39.90923]生成最短路径
      * let result = this.findShortestPath({
      * startPoint: [116.397428, 39.90923],
      * endPoint: [116.405285, 39.90923],
      * lines: [
      * {points: [[116.397428, 39.90923], [116.405285, 39.90923]]},
      * {points: [[116.405285, 39.90923], [116.425285, 39.91923]]},
      * ]
      * });
      * console.log(result);
     */
    findShortestPath(options)  {
            options = options || {};
            options.lines = options.lines || [];
            options.lines = options.lines.map(line => {
                return {
                    points: line.points.map(point => vjmap.geoPoint(point)),
                    id: line.id,
                    weight: line.weight
                }
            });
            let result = vjmap.findShortestPath(vjmap.geoPoint(options.startPoint), vjmap.geoPoint(options.endPoint), options.lines, options.precision, options.hasDirection);
        
            if (result.error) {
                this.map.logInfo(result.error.message, "error");
                return null;
            } 
            if (options.disableDrawPath !== true) {
               let polyline = new vjmap.Polyline({
                    data: this.map.toLngLat(result.route),
                    lineColor: options.drawPathColor || "#ff0000",
                    lineWidth: options.drawPathWidth || 5,
                });
                polyline.addTo(this.map);
                result.lineLayerId = polyline.layerId;
                result.lineSourceId = polyline.sourceId;
            }
            
            let sourceId = result.lineSourceId;
            if (sourceId) {
                // 添加撤销记录
                this.context.ui.addUndoRecord(() => {
                    this.map.removeSourceEx(sourceId);
                });
            }
            return result;
        }
    

    /**
     * 获取地图容器的大小
     * @returns {object} 包含width和height的对象，表示地图容器的宽度和高度
     */
    getContainerSize() {  this.map.getContainer().getBoundingClientRect() }
    

    /**
     * 通过json数据创建新的地图
     * @param {array} drawJson `drawJson` 是一个 JSON 数组，其中每个元素为一个图形实体的定义对象。绘制的坐标系是标准的迪卡尔坐标系。所有实体均继承自 `DbEntity`，并根据具体的图形类型提供额外的属性。以下是支持的实体类型及其格式：
     * @param {object} options 其他参数(可选), 支持以下参数:
     * @example
      * // 示例一
      * // 创建中心点为[10,10]，半径为5,颜色为红色的圆
      * let drawJson = [{
      * "typename": "DbCircle",
      * "center": [10, 10, 0],
      * "radius": 5,
      * "color": "#FF0000"
      * }]
      * // 创建一个多段线，包含两个点[10,10]和[20,20]，颜色为绿色，线宽为2
      * drawJson.push({
      * "typename": "DbPolyline",
      * "points": [[10, 10], [20, 20]],
      * "color": "#00FF00",
      * "lineWidth": 2
      * })
      * // 通过上面生成的drawJson数据创建地图
      * let mapid = await this.createMapData(drawJson);
      * console.log(mapid)
      * // 示例二
      * // 基于图sys_hello/v1，选择图中的"TEXT"图层，创建一个单行文本，位置为[10108,10556]，文本高2000，文本内容为"Hello, World!",水平居中对齐,垂直居中对齐, 再创建一个填充的三角形，坐标为[[10,10],[20,20],[10,20]]，填充颜色为#00FF00, 图层名称为"ly1"
      * await this.createMapData([{
      * "typename": "DbText",
      * "position": [10108, 10556, 0],
      * "height": 2000,
      * "text": "Hello, World!",
      * "horizontalMode": 1,
      * "verticalMode": 2
      * }, {
      * "typename": "DbHatch",
      * "points": [[10, 10], [20, 20], [10, 20]],
      * "color": "#00FF00",
      * "layer": "ly1"
      * }], {
      * "from": "sys_hello/v1",
      * "pickLayers": ["TEXT"]
      * });
      * // 示例三
      * // 在位置[10, 10], [5000, 0], [0, 6000], 在这三个位置分别创建块参照，这三个块参照都来源于克隆图形sys_symbols,克隆实体id为25B。第二个块参照旋转45，第三个块参照放大3倍, 创建完直接加到当前地图中
      * await this.createMapData([{
      * "typename": "DbBlockReference",
      * "position": [10, 10],
      * "cloneFromDb": "sys_symbols",
      * "cloneObjectId": "25B"
      * }, {
      * "position": [5000, 0],
      * "cloneFromDb": "sys_symbols",
      * "cloneObjectId": "25B",
      * "rotateMat": 45
      * }, {
      * "position": [0, 6000],
      * "cloneFromDb": "sys_symbols",
      * "cloneObjectId": "25B",
      * "scaleMat": 3
      * }], {
      * "openway": 1
      * });
     */
    async createMapData(drawJson, options)  {
            return await this.context.tools.createMapData(this.context, drawJson, options)
        }
    

    /**
     * 取消测量
     * @example
      * // 取消测量
      * await this.cancelMeasure();
     */
    cancelMeasure()  {
                vjcommon.runMeasureCmd(this.map, "measureCancel");
            }
    

    /**
     * 监听地图事件
     * @param {string} eventName 事件名称，参数必填, 事件名称有:
     * @param {function} callback 事件回调函数，参数必填
     * @param {boolean} isOnce 是否只触发一次，默认false，参数可选
     * @example
      * // 示例一
      * // 监听地图双击事件
      * this.listenMapEvent("dblclick", ev => {
      * console.log(ev);
      * });
      * // 示例二
      * // 监听地图单击事件，只触发一次,在点击的位置增加一个Marker
      * this.listenMapEvent("click", ev => {
      * if (ev && ev.point) {
      * // 获取当前地图的像素坐标，转换为CAD图几何坐标
      * const point =  this.pixelPointToGeoPoint(ev.point);
      * // 根据获取的CAD图几何坐标位置，在地图上增加一个Marker
      * this.addMarker(point);
      * }
      * }, true);
      * // 示例三
      * // 监听地图实体点击事件，通过点击内容显示数据面板，面板标题为"实体数据"，面板高度为500，面板以表单形式显示
      * this.listenMapEvent("clickEntity", ev => {
      * if (ev) {
      * this.showDataPanel(ev, { title: "实体数据", height: 500, showForm: true });
      * } else {
      * this.closeDataPanel();
      * }
      * });
     */
    listenMapEvent(eventName, callback, isOnce)  {
            // callback的参数中含有当前地图的像素坐标，可通过 this.pixelPointToGeoPoint(ev.point) 转换为CAD图几何坐标
            
            // 将事件监听器添加到全局对象中，以便在取消监听时使用
            this.context.global._delegatedListeners = this.context.global._delegatedListeners || {};
            this.context.global._delegatedListeners[eventName] = this.context.global._delegatedListeners[eventName] || [];
            this.context.global._delegatedListeners[eventName].push(callback);
    
            if (eventName === "clickEntity") {
                if (this.context.global._delegatedListeners[eventName].length === 1) {
                    // 第一次监听
                    let oldEventValue;
                    this.map.enableLayerClickHighlight(this.map.getService(), e => {
                        if (e !== oldEventValue) {
                            this.context.global._delegatedListeners[eventName].forEach(func => {
                                func(e);
                            });
                            oldEventValue = e;
                        }
                    })
                }
                return;
            } 
    
            if (isOnce) {
                this.map.once(eventName, callback);
            } else {
                this.map.on(eventName, callback);
            }
    
            if (!this.context.global.__listenKeyEvent) {
                // 先主动监听键盘事件，以便获取当前是否按下Shift、Ctrl、Alt键
                this.context.global.__listenKeyEvent = vjcommon.listenKeyEvent()
            }
            
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.map.off(eventName, callback);
                if (this.context.global._delegatedListeners && this.context.global._delegatedListeners[eventName]) {
                    let index = this.context.global._delegatedListeners[eventName].indexOf(callback);
                    if (index !== -1) {
                        this.context.global._delegatedListeners[eventName].splice(index, 1);
                    }
                }
            });
        }
    

    /**
     * 打开图, 通过指定地图id或手动选择打开图
     * @param {object} options 参数 可选
     * @param {string} options.mapid 地图id 可选，为空时则手动选择地图
     * @param {string} options.version 版本 可选，为空时则选择最新版本
     * @param {"Memory" | "GeomRaster" | "GeomVector"} options.openway 打开方式 可选，Memory: 内存, GeomRaster: 栅格, GeomVector: 矢量, 默认栅格
     * @param {boolean} options.lightTheme 是否使用浅色主题 可选，为空时则使用深色主题
     * @param {boolean} options.isFullExtent 是否全图范围显示 可选，默认false
     * @returns {Object | undefined} 返回对象，包含地图id和版本 {mapid: string, version: string}, 如果取消则返回undefined
     * @example
      * // 示例一
      * // 打开地图
      * this.openCadMap();
      * // 示例二
      * // 打开地图sys_world
      * this.openCadMap({
      * mapid: "sys_world"
      * });
      * // 示例三
      * // 打开地图sys_hello, 版本v1, 以矢量方式打开, 使用浅色主题
      * this.openCadMap({
      * mapid: "sys_hello",
      * version: "v1",
      * openway: "GeomVector",
      * lightTheme: true
      * });
     */
    async openCadMap(options)  {
            options = options || {};
            let mapid = options.mapid || "";
            let version = options.version || "";
            let mapopenway = vjmap.MapOpenWay.GeomRender;
            let isVectorStyle = false;
            let lightTheme = options.lightTheme ?? (this.map.getService().currentMapParam()?.darkMode === true ? false : true)
            if (!mapid) {
                let res = await this.context.ui.selectCadMap()
                if (!res) return;
                mapid = res.mapid;
                version = res.version;
                mapopenway = res.openway;
                isVectorStyle = res.isVectorStyle;
            } else {
                mapopenway = options.openway === "Memory" ? vjmap.MapOpenWay.Memory : vjmap.MapOpenWay.GeomRender;
                isVectorStyle = options.openway === "GeomVector" ? true : false;
            }
            // 打开地图
            let res = await this.map.switchMap({
                mapid: mapid,
                version: version,
                mapopenway: mapopenway,
                style: lightTheme ? vjmap.openMapLightStyle() : vjmap.openMapDarkStyle(),
                // 如果有密码保护的图，要求输入密码回调，如果不传回调函数的话，将用系统的prompt提示输入
                cbInputPassword: (param) => {
                    return new Promise((resolve) => {
                        if (param.tryPasswordCount > 3) {
                            resolve('');// 如果密码重试次数超过三次
                        } else {
                            this.messageBoxPrompt("请输入密码或AccessKey", `打开图 ${param.mapid} 需要密码`, "", true)
                            .then((value) => {
                                resolve(value) ;
                            })
                            .catch(() => {
                                resolve('') ;
                            })
                        }
                    })
                }
            }, false, isVectorStyle, true, true);
            if (options.isFullExtent !== true) {
                // 用默认的中心点和缩放级别参数
                let svc = this.map.getService();
                let param = svc.currentMapParam();
                if (param && param.view) {
                    if (param.view.center) {
                        this.map.setCenter(param.view.center);
                    }
                    if (param.view.zoom) {
                        this.map.setZoom(param.view.zoom);
                    }
                    if (param.view.bearing) {
                        this.map.setBearing(param.view.bearing);
                    }
                }
            }
            if (res && res.error) {
                this.showMessage(res.error)
                return;
            }
            // 打开新的地图了，需要清空历史记录
            this.context.ui.clearUndoRecord();
            return {
                mapid: mapid,
                version: version
            };
    
        }
    

    /**
     * 通过范围创建空白地图
     * @param {number| [number,number,number,number] | GeoJSON} boundData 范围数据,可以是一个数值，表示地图宽高, 或一个数组表示地图范围, 或一个GeoJSON对象(会自动计算范围)， 要为空时，则创建一个10000*10000的正方形地图
     * @example
      * // 示例一
      * // 创建一个空白地图
      * this.createBlankMap();
      * // 示例二
      * // 通过范围[100,100,200,200]创建空白地图
      * this.createBlankMap([100,100,200,200]);
     */
    createBlankMap(boundData)  {
            let dataExtent;
            boundData = boundData || 10000;
            if (typeof boundData === "number") {
                dataExtent = [-boundData/2, -boundData/2, boundData/2, boundData/2 ];
            } else if (Array.isArray(boundData) && boundData.length === 4 && typeof boundData[0] === "number") {
                dataExtent = vjmap.GeoBounds.fromArray(boundData);
            } else {
                dataExtent = vjmap.GeoBounds.fromDataExtent(boundData);
            }
            dataExtent = dataExtent.square(); // 地图的范围需要正方形，地图范围需要正方形才可以
            let mapBounds = dataExtent.scale(4); // 把范围调点大，这个值可以根据情况自己定，以后加的点不要超过此范围即可
    
            // 清空地图
            this.clearMapData({
                clearAllData: true
            });
            // 根据地图范围建立几何投影坐标系
            let prj = new vjmap.GeoProjection(mapBounds);
    
            let blankStyle = this.map.getService().rasterBlankStyle(0, 24)
            let sourceId = Object.keys(blankStyle.sources)[0];
            this.map.addRasterSource(sourceId, blankStyle[sourceId]);
            this.map.addLayer(blankStyle.layers[0]);
            this.map.setMapProjection(prj);
            // 打开新的地图了，需要清空历史记录
            this.context.ui.clearUndoRecord();
        }
    

    /**
     * 将地图像素点坐标转换为CAD图几何坐标
     * @returns {[number, number]} 返回包含两个数字的数组[x,y]，表示CAD图几何坐标
     * @example
      * // 将地图像素点坐标[5, 10]转换为CAD图几何坐标
      * let point = this.pixelPointToGeoPoint([5, 10]);
      * let x = point[0];
      * let y = point[1];
      * console.log(x, y);
     */
    pixelPointToGeoPoint(pt)  {
            let lnglat = this.map.unproject(pt);
            let geoPoint = this.map.fromLngLat(lnglat);
            return [geoPoint.x, geoPoint.y];
        }
    

    /**
     * 移除地图导航条控件
     * @example
      * // 移除地图导航条控件
      * this.removeMapNavigationControl();
     */
    removeMapNavigationControl()  {
            this.context.global.controls = this.context.global.controls || {};
            let options;
            if (this.context.global.controls.navigationControl) {
                options = {...this.context.global.controls.navigationControl.options}
                this.map.removeControl(this.context.global.controls.navigationControl.control);
                delete this.context.global.controls.navigationControl;
            }
            // 增加撤销记录
            this.context.ui.addUndoRecord(() => {
                if (options) {
                    this.addMapNavigationControl(options);
                }
            }); 
        }
    
    

    /**
     * 飞行至目标位置
     * @param {Object} options 缩放选项
     * @param {[number,number]} options.center 目标位置的坐标
     * @param {number} [options.zoom] 目标缩放级别。可选，如果不提供，则保持当前缩放级别
     * @param {number} [options.bearing] 目标方位角（旋转角度），以度为单位，0 表示正北。可选，如果不提供，则保持当前方位角
     * @param {number} [options.pitch] 目标倾斜角度，以度为单位，0 表示垂直于地图。可选，如果不提供，则保持当前倾斜角度
     * @param {number} [options.speed] 飞行速度的倍数。默认值为 1.2。可选
     * @param {number} [options.curve] 飞行路径的曲率，值越大路径越弯曲。默认值为 1.42。可选
     * @param {boolean} [options.essential] 是否为必要动画，如果为true，则不会受到用户设置的“减少运动”选项的影响。可选，默认值为true
     * @param {boolean} [options.useAsync] 是否使用异步动画，如果为true，则使用异步动画。可选，默认值为false
     * @param {boolean} [options.easeTo] 是否使用缓动动画，如果为true，则使用缓动动画。可选，默认值为false
     * @example
      * // 示例一
      * // 飞行至目标位置[100,20]
      * this.flyTo({center: [100,20]});
      * // 示例二
      * // 飞行至目标位置[100,20]，缩放级别为10，旋转角度为45度，倾斜角度为60度，使用缓动动画
      * this.flyTo({center: [100,20], zoom: 10, bearing: 45, pitch: 60, easeTo: true});
     */
    async flyTo(options)  {
            options = options || {};
            if (options.center) {
                options.center = this.map.toLngLat(options.center);
            }
            options.essential = options.essential === undefined ? true : options.essential;
            let oldCenter = this.map.getCenter();
            let oldZoom = this.map.getZoom();
            let oldBearing = this.map.getBearing();
            let oldPitch = this.map.getPitch();
            let method = options.easeTo === true ? "easeTo" : "flyTo";
            if (options.useAsync === true) {
                function animateAsync(options) {
                    return new Promise((resolve) => {
                        // 监听地图移动结束事件
                        this.map.once('moveend', ({ originalEvent }) => {
                            if (!originalEvent) {
                                resolve()
                            }
                        });
                        this.map[method](options);
                    });
                }
                await animateAsync(options);
            } else {
                this.map[method](options)
            }
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.map.setCenter(oldCenter);
                this.map.setZoom(oldZoom);
                this.map.setBearing(oldBearing);
                this.map.setPitch(oldPitch);
            });
         }
    

    /**
     * 创建背景图层
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {string} options.backgroundColor 背景颜色，参数可选，支持十六进制颜色值，默认为"#572782"
     * @param {number} options.backgroundOpacity 背景透明度，参数可选，取值范围0-1，默认为0.6
     * @param {string} options.backgroundPattern 背景图案，参数可选，填充图片名称，图片需要预先加载到地图中图像宽度必须是2的系数（2、4、8、...、512）
     * @param {boolean} options.visible 图层是否可见，参数可选，默认为true
     * @param {string} options.before 图层在指定图层之前绘制，参数可选
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 创建背景图层，背景颜色为#572782，背景透明度为0.6
      * let layer = await this.createBackgroundLayer({
      * backgroundColor: "#572782",
      * backgroundOpacity: 0.6,
      * });
      * console.log(layer);
     */
    async createBackgroundLayer(options)  {
            options = options || {};
            let type = "background";
    
            let randomId = vjmap.RandomID();
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                backgroundColor: "#572782",
                backgroundOpacity: 0.6,
                ...options,
                type: type
            }
           
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId: undefined, layerName, data: undefined, options, layer, visible: true, create: this.createBackgroundLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId: undefined,
                layerId,
                layerName
            };
        }
    

    /**
     * 设置绘制图元属性
     * @param {string} id 图元ID，参数必填
     * @param {object} options 配置参数, 参数可选
     * @param {string} options.color 颜色，格式为#RRGGBB，参数可选
     * @param {string} options.borderColor 边框颜色，格式为#RRGGBB，参数可选
     * @param {number} options.opacity 透明度，参数可选
     * @param {number} options.borderOpacity 边框透明度，参数可选
     * @param {number} options.width 线宽，参数可选
     * @param {number} options.extrusionHeight 拉伸高度，多边形有效，参数可选
     * @param {boolean} options.isOff 是否隐藏, false显示，true隐藏，参数可选
     * @param {boolean} options.isLocked 是否锁定, false不锁定，true锁定，参数可选
     * @param {string} options.text 文本内容, 符号文本有效，参数可选
     * @param {[number, number][] | [number, number]} options.coordinates 坐标点数组，参数可选
     * @param {number} options.data 数据值，参数可选
     * @returns {Promise<Object | undefined>} 返回绘制的图元对象，如果取消操作则返回undefined，返回的对象包含以下属性：
     * @example
      * // 示例一
      * // 选择绘制的图元，把颜色改为红色
      * let geojson = await this.actionSelect();
      * for(let feature of geojson.features) {
      * this.setFeatureProperty(feature.id, {
      * color: "#FF0000",
      * });
      * };
      * // 示例二
      * // 获取绘制的全部图元的第二个实体，设置颜色为黄色，透明度0.6，线宽2，边框颜色为红色
      * let geojson2 =  this.getDrawAll();
      * let features = geojson2.features;
      * let index = 2; // 第二个实体
      * if (index >= features.length) {
      * return;
      * }
      * let feature = features[index];
      * this.setFeatureProperty(feature.id, {
      * color: "#FFFF00",
      * opacity: 0.6,
      * width: 2,
      * borderColor: "#FF0000",
      * });
     */
    setFeatureProperty(id, options)  {
            options = options || {};
            let draw = this.map.getDrawLayer();
            let attr = {
                "color": options.color,// 颜色
                "opacity": options.opacity, // 透明度
                "outlineColor": options.borderColor,// 边框颜色
                "line_width": options.width, // 线宽
                "line_opacity": options.borderOpacity, // 透明度
                "data": options.data,
                "coordinates": options.coordinates,
                "isOff": options.isOff,
                "isLocked": options.isLocked,
                "text": options.text,
                "extrusionHeight": options.extrusionHeight,
            };
    
            for(let key in attr) {
                if (attr[key] !== undefined && attr[key] !== null) {
                    draw.setFeatureProperty(
                        id,
                        key,
                        attr[key]
                    );
                }
            }
            draw.forceRefresh();
        }
    

    /**
     * 保存临时数据
     * @param {string} name 数据名
     * @param {any} data 数据内容
     * @example
      * // 将"123"保存到临时数据test中
      * this.saveTempData("test", "123");
     */
    saveTempData(name, data)  {
            this.context.resultData[name] = data
        }
    

    /**
     * 删除数据图层
     * @param {string} layerName 图层名称，参数必填
     * @example
      * // 删除图层名称为layer1的数据图层
      * let layerName = 'layer1';
      * this.deleteDataLayer(layerName);
     */
    deleteDataLayer(layerName)  {
            let item = this.context.global.layers.find(layer => layer.layerName == layerName);
            if(item) {
                let layerId = item.layerId;
                let sourceId = item.sourceId;
                if (item.layer && typeof(item.layer.getLayerId) == "function") {
                    layerId = item.layer.getLayerId();
                    let style = this.map.getStyle();
                    let layer = style.layers.find(layer => layer.id == layerId);
                    if (layer && layer.source) {
                        sourceId = layer.source;
                    }
                }
                item.layer.removeLayer(this.map, layerId);
                if (sourceId) {
                    this.map.removeSourceEx(sourceId);
                }
                this.context.global.layers = this.context.global.layers.filter(layer => layer.layerId != item.layerId);
    
                // 添加撤销记录
                this.context.ui.addUndoRecord(() => {
                    if (item.create) {
                        item.create(item.data, item.options);
                    }
                });
            }
        }
    

    /**
     * 设置数据图层悬浮显示弹窗内容
     * @param {string} layerName 图层名称，参数必填
     * @param {Record<string, any>} options 其他参数，参数可选
     * @param {any} options.popupContent 弹窗内容，默认为当前数据的属性值
     * @param {boolean} options.popupShowInForm 弹窗内容是否以表单形式显示，参数可选，默认为false
     * @param {[number, number]} options.popupOffset 弹窗偏移量，参数可选，默认为[0, -40]
     * @param {number} options.popupWidth 弹窗宽度，参数可选
     * @param {number} options.popupHeight 弹窗高度，参数可选
     * @param {boolean} options.isHoverPointer 是否启用鼠标悬浮事件，参数可选，默认为true
     * @param {boolean} options.noShowPopup 是否不显示弹窗，参数可选，默认为false
     * @param {Function} options.hoverCallback 悬浮回调函数，参数可选，默认为null，（回调函数第一个参数为弹窗内容）
     * @example
      * // 示例一
      * // 设置图层名称为layer1的数据图层悬浮显示弹窗
      * this.setDataLayerHoverPopupContent('layer1');
      * // 示例二
      * // 设置图层名称为layer1的数据图层悬浮显示弹窗, 弹窗内容为"ID:${props.index}"
      * this.setDataLayerHoverPopupContent('layer1', {
      * popupContent: "ID:${props.index}",
      * });
     */
    setDataLayerHoverPopupContent(layerName, options)  {
            let item = this.context.global.layers.find(layer => layer.layerName == layerName);
            if(!item) return;
            options = options || {};
            options.isHoverPointer = options.isHoverPointer === undefined ? true : options.isHoverPointer;
            let markers = item.layer.markers;
            if (Array.isArray(markers)) {
                // 如果是Marker相关的图层
                for(let m = 0; m < markers.length; m++) {
                    let marker = markers[m];
                    let oldCursor;
                    marker.on('mouseover', () => {
                        if (options.isHoverPointer) {
                            oldCursor = this.map.getCanvas().style.cursor;
                            this.map.getCanvas().style.cursor = "pointer";
                        }
                        let popup = this.context.global.popup;
                        if (!popup && options.noShowPopup !== true) {
                            popup = new vjmap.Popup({
                                offset: options.popupOffset || [0, -40],
                                closeOnClick: false, 
                                closeButton: true, 
                                anchor: "bottom" 
                            })
                            this.context.global.popup = popup;
                        }
                        if (options.noShowPopup !== true &&options.popupOffset) {
                            popup.setOffset(options.popupOffset);
                        }
                        let content = options.popupContent;
                        if (item.data && item.data.features && item.data.features[m] && item.data.features[m].properties && typeof content == "string" && !(content in item.data.features[m].properties)) {
                            content = item.layer.evalValue("return `" + content + "`", item.data.features[m].properties, this.map);
                            if (options.noShowPopup !== true) {
                                popup.setHTML(content);
                            }
                            if (options.hoverCallback) {
                                options.hoverCallback(content);
                            }
                        } else {
                            if (!content) {
                                content = item.data.features[m].properties;
                            } else {
                                // 如果content是对象的属性，则获取对象的属性值
                                if (item.data.features[m].properties && (content in item.data.features[m].properties)) {
                                    content = item.data.features[m].properties[content];
                                    try {
                                        // 如果content是json字符串，则尝试转换为对象
                                        content = JSON.parse(content);
                                    } catch (e) {}
                                }
                            }
                            if (options.noShowPopup !== true) {
                                const dom = this.context.ui.createElementByJsonData(content, options.popupShowInForm, {
                                    width: options.popupWidth,
                                    height: options.popupHeight
                                })
                                popup.setDOMContent(dom.element)
                            }
                            if (options.hoverCallback) {
                                options.hoverCallback(content);
                            }
                        }
                        if (options.noShowPopup !== true) {
                            popup.setLngLat(marker.getLngLat());
                            popup.addTo(this.map);
                        }
                    });
                    marker.on('mouseleave', () => {
                        if (options.isHoverPointer && oldCursor) {
                            this.map.getCanvas().style.cursor = oldCursor;
                            oldCursor = null;
                        }
                        if (options.noShowPopup !== true) {
                            let popup = this.context.global.popup;
                            if (popup) {
                                popup.remove();
                                this.context.global.popup = null;
                            }
                        }
                        if (options.hoverCallback) {
                            options.hoverCallback(null);
                        }
                    });
                }
            } else {
                // 如果是其他图层
                let oldCursor;
                let oldFeatureId;
                let layerId = item.layerId;
                let sourceId = item.sourceId;
                if (item.layer && typeof(item.layer.getLayerId) == "function") {
                    layerId = item.layer.getLayerId();
                    let style = this.map.getStyle();
                    let layer = style.layers.find(layer => layer.id == layerId);
                    if (layer && layer.source) {
                        sourceId = layer.source;
                    }
                }
                this.map.on("mouseover", layerId, (ev) => {
                    if (!ev || !ev.features || !ev.features[0]) return;
                    if (options.isHoverPointer) {
                        oldCursor = this.map.getCanvas().style.cursor;
                        this.map.getCanvas().style.cursor = "pointer";
                    }
                    let pt = ev.lngLat;
                    let popup = this.context.global.popup;
                    if (!popup && options.noShowPopup !== true) {
                        popup = new vjmap.Popup({
                            offset: options.popupOffset || [0, -40],
                            closeOnClick: false, 
                            closeButton: true, 
                            anchor: "bottom" 
                        })
                        this.context.global.popup = popup;
                    }
                    if (options.noShowPopup !== true && options.popupOffset) {
                        popup.setOffset(options.popupOffset);
                    }
                    let content = options.popupContent;
                    if (ev.features[0].properties && typeof content == "string" && !(content in ev.features[0].properties)) {
                        content = item.layer.evalValue("return `" + content + "`", ev.features[0].properties, this.map);
                        if (options.noShowPopup !== true) {
                            popup.setHTML(content);
                        }
                        if (options.hoverCallback) {
                            options.hoverCallback(content);
                        }
                    } else {
                        if (!content ) {
                            content = ev.features[0].properties;
                        } else {    
                            // 如果content是对象的属性，则获取对象的属性值
                            if (ev.features[0].properties && (content in ev.features[0].properties)) {
                                content = ev.features[0].properties[content];
                                try {
                                    // 如果content是json字符串，则尝试转换为对象
                                    content = JSON.parse(content);
                                } catch (e) {}
                            }
                        }
                        if (options.noShowPopup !== true) {
                            const dom = this.context.ui.createElementByJsonData(content, options.popupShowInForm, {
                                width: options.popupWidth,
                                height: options.popupHeight
                            })
                            popup.setDOMContent(dom.element)
                        }
                        if (options.hoverCallback) {
                            options.hoverCallback(content);
                        }
                    }
                    if (options.noShowPopup !== true) {
                        popup.setLngLat(pt);
                        popup.addTo(this.map);
                    }
                    oldFeatureId = ev.features[0].id;
                    this.map.setFeatureState({ source: sourceId, sourceLayer: layerId, id: ev.features[0].id }, { hover: true });
                });
                this.map.on("mouseleave", layerId, () => {
                    if (options.isHoverPointer && oldCursor) {
                        this.map.getCanvas().style.cursor = oldCursor;
                        oldCursor = null;
                    }   
                    if (options.noShowPopup !== true) {
                        let popup = this.context.global.popup;
                        if (popup) {
                            popup.remove();
                            this.context.global.popup = null;
                        }
                    }
                    if (options.hoverCallback) {
                        options.hoverCallback(null);
                    }
                    if (oldFeatureId) {
                        this.map.setFeatureState({ source: sourceId, sourceLayer: layerId, id: oldFeatureId }, { hover: false });
                        oldFeatureId = null;
                    }
                });
            }
        }
    

    /**
     * 根据给定的点坐标数组直接绘制一个多边形
     * @param {[number, number][]} points 多边形的坐标点数组[[x1,y1],[x2,y2],...]，参数必填
     * @param {object} options 配置参数, 参数可选
     * @param {string} options.color 颜色，格式为#RRGGBB，参数可选
     * @param {boolean} options.isFill 是否填充，默认true，参数可选
     * @param {number} options.opacity 透明度，默认1，参数可选
     * @param {number} options.extrusionHeight 拉伸高度，有拉伸高度为拉伸对象，参数可选
     * @param {number} options.data 数据值，参数可选
     * @returns {Object} 返回绘制的图元对象，返回的对象包含以下属性：
     * @example
      * // 示例一
      * // 根据三个地图范围内随机点创建一个多边形
      * let points = this.randomPointInMapBounds(3); // 每个多边形3个点
      * points.push(points[0]); // 闭合多边形
      * const polygon = this.createPolygon(points, {
      * color: vjmap.randomColor(),
      * isFill: true,
      * opacity: 0.5
      * });
      * console.log(`多边形 ID:`, polygon.id);
      * console.log(`多边形 点坐标:`, polygon.points);
      * // 示例二
      * // 根据三个地图范围内随机点创建一个多边形,拉伸高度为地图范围宽度的五分之一
      * let points2 = this.randomPointInMapBounds(3); // 每个多边形3个点
      * points2.push(points2[0]); // 闭合多边形
      * const polygon2 = this.createPolygon(points2, {
      * color: vjmap.randomColor(),
      * isFill: true,
      * extrusionHeight: map.getGeoBounds().width() / 5
      * });
      * console.log(`拉伸多边形 ID:`, polygon2.id);
      * console.log(`拉伸多边形 点坐标:`, polygon2.points);
     */
    createPolygon(points, options)  {
            let draw = this.map.getDrawLayer();
            let id = vjmap.RandomID(10);
            draw.add({
                id,
                type: 'Feature',
                geometry: {
                    type: options.isFill !== false ? 'Polygon' : 'LineString',
                    coordinates: options.isFill !== false ? [points.map(pt => this.map.toLngLat(pt))] : points.map(pt => this.map.toLngLat(pt))
                },
                properties: {
                    color: options.color,
                    opacity: options.opacity,
                    data: options.data,
                    extrusionHeight: options.extrusionHeight ? this.map.geoLengthToHeight(options.extrusionHeight) : undefined
                }
            });
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.map.getDrawLayer().delete(id);
            });
            return {
                id,
                points
            };
        }
    

    /**
     * 设置地图的倾斜角度
     * @param {number} pitch 要设置的倾斜角度
     * @example
      * this.setPitch(60); // 设置倾斜角度为60度
     */
    setPitch(pitch)  {
            let oldPitch = this.map.getPitch(); // 获取当前倾斜角度
            this.map.setPitch(pitch); // 设置新的倾斜角度
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.map.setPitch(oldPitch);
            });
        }
    

    /**
     * 手动交互式绘制一个点
     * @param {object} options 配置参数, 参数可选
     * @param {boolean} options.isSnap 是否启用捕捉功能, 参数可选，默认false
     * @param {string} options.color 颜色，格式为#RRGGBB，参数可选
     * @param {number} options.size 点大小，参数可选
     * @param {number} options.data 数据值，参数可选
     * @returns {Promise<Object | undefined>} 返回绘制的图元对象，如果取消操作则返回undefined，返回的对象包含以下属性：
     * @example
      * // 手动绘制一个点
      * const pt = await this.drawPoint(); // 启用捕捉功能采绘制一个点坐标
      * if (!pt) return;
      * // 点的坐标
      * let point = pt.point;
      * const x = point[0];
      * const y = point[1];
      * console.log(x, y);
      * // 图元ID
      * let id = pt.id;
      * console.log(id);
      * // 手动绘制一个点，颜色为红色，大小为10，数据值为123，启用捕捉
      * const pt2 = await this.drawPoint({
      * isSnap: true,
      * color: "#FF0000",
      * size: 10,
      * data: 123
      * });
      * console.log(pt2);
     */
    async drawPoint(options)  {
            options = options || {};
            let point = await this.actionDrawPoint(options.isSnap);
            if (!point) return;
            return this.createPoint(point, options);
        }
    

    /**
     * 增加一个信息窗口Popup
     * @param {[number, number]} pt 要设置的坐标[x, y]
     * @param {Record<string, any>} options 其他参数，参数可选
     * @param {any} options.content 弹窗内容，参数可选，默认为空表示不显示弹窗
     * @param {[number, number]} options.offset 弹窗偏移量，参数可选，默认为[0, -40]
     * @param {boolean} options.showInForm 弹窗内容是否以表单形式显示，参数可 选，默认为false
     * @param {boolean} options.isOpen 弹窗内容是否打开，参数可选，默认为false
     * @param {string} options.anchor 锚点, 选项包括'center'，'top'，'bottom'，'left'，'right'，'top-left'，'top-right'，'bottom-left'，和'bottom-right'。参数可选
     * @param {string} options.maxWidth 弹窗最大宽度，参数可选，默认为'auto'
     * @param {string} options.width 弹窗宽度，参数可选
     * @param {string} options.height 弹窗高度，参数可选
     * @param {string} options.className 弹窗样式类名，参数可选，默认为空
     * @param {boolean} options.closeButton 是否显示关闭按钮，参数可选，默认为true
     * @param {boolean} options.closeOnClick 是否点击弹窗外部关闭弹窗，参数可选，默认为false
     * @param {boolean} options.closeOnMove 是否地图移动时关闭弹窗，参数可选，默认为false
     * @param {boolean} options.focusAfterOpen 是否打开弹窗后聚焦弹窗，参数可选，默认为true
     * @param {number} options.elevation 坐标标高值，参数可选，默认为0
     * @param {boolean} options.visible 是否显示，参数可选，默认为true
     * @param {any} options.data 数据内容，参数可选，默认为空
     * @returns {string} 返回id
     * @example
      * // 在坐标[30,50]处添加一个信息窗口，内容为"Hello,World"
      * let popupId = this.addPopup([30, 50], {
      * content: "Hello,World"
      * });
      * console.log(popupId);
     */
    addPopup(pt, options)  {
            options = options || {};
            let popup = new vjmap.Popup({
                offset: options.offset, 
                anchor: options.anchor,
                maxWidth: options.maxWidth,
                className: options.className,
                closeButton: options.closeButton === undefined ? true : options.closeButton,
                closeOnClick: options.closeOnClick,
                closeOnMove: options.closeOnMove,
                focusAfterOpen: options.focusAfterOpen
            })
            const dom = this.context.ui.createElementByJsonData(options.content, options.showInForm, {
                width: options.width,
                height: options.height
            })
            popup.setDOMContent(dom.element)
          
            popup.id = "popup_" + vjmap.RandomID()
            if (options.data) popup.data = options.data;
            if (pt) {
                popup.setLngLat(this.map.toLngLat(pt));
            }
            if (options.visible !== false || options.isOpen) {
                popup.addTo(this.map);
            }
    
            if (options.elevation) {
                popup.setHeight(options.elevation);
            }
    
            this.context.global.popups = this.context.global.popups || [];
            this.context.global.popups.push({id: popup.id, popup: popup, options: options});
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deletePopup(popup.id);
            });
            return popup.id
        }
    

    /**
     * 切换至栅格瓦片样式
     * @example
      * // 切换至栅格瓦片样式
      * this.switchVectorToRasterStyle();
     */
    switchVectorToRasterStyle()  {
            // 如果当前地图已经使用栅格瓦片样式，则不进行切换
            if (!this.map.hasVectorLayer()) {
                return
            }
            this.map.switchVectorToRasterStyle(this.map.getService().rasterTileUrl());
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.map.switchRasterToVectorStyle(this.map.getService().vectorTileUrl());
            });
        }
    
    

    /**
     * 创建点符号数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {boolean} options.isHoverPointer 是否启用鼠标悬浮事件，参数可选
     * @param {boolean} options.isHoverFeatureState 是否启用悬浮实体状态，参数可选
     * @param {string} options.iconImage 图标名称，用于绘制图像背景的图像名称
     * @param {number} options.iconRotate 图标旋转角度，顺时针旋转，单位为角度，默认0
     * @param {[number,number]} options.iconOffset 图标与锚点偏移距离，[x,y]格式，正值向右和向下
     * @param {string} options.iconAnchor 图标锚定位置，可选值: "center", "left", "right", "top", "bottom", "top-left", "top-right", "bottom-left", "bottom-right"
     * @param {string} options.iconRotationAlignment 图标旋转对齐方式，可选值: "map", "viewport", "auto"
     * @param {string} options.iconPitchAlignment 地图倾斜时图标方向，可选值: "map", "viewport", "auto"
     * @param {boolean} options.iconAllowOverlap 是否允许图标重叠，默认false
     * @param {number} options.iconOpacity 图标透明度，0-1之间，默认1
     * @param {boolean} options.iconIgnorePlacement 是否忽略碰撞检测，默认false
     * @param {boolean} options.iconOptional 图标重叠选项，默认false
     * @param {number} options.iconSize 图标缩放比例，默认1
     * @param {string} options.iconTextFit 图标适应文本方式，可选值: "none", "width", "height", "both"
     * @param {[number,number,number,number]} options.iconTextFitPadding 图标文本匹配padding，[top, right, bottom, left]格式
     * @param {number} options.iconPadding 图标边界填充大小，默认1像素
     * @param {boolean} options.iconKeepUpright 是否防止图标倒置，默认false
     * @param {string} options.iconColor SDF图标颜色，默认"#000000"
     * @param {string} options.iconHaloColor SDF图标光晕颜色，默认"rgba(0, 0, 0, 0)"
     * @param {number} options.iconHaloWidth 图标光晕宽度，默认0
     * @param {number} options.iconHaloBlur 图标光晕模糊度，默认0
     * @param {[number,number]} options.iconTranslate 图标平移距离，[x,y]格式
     * @param {string} options.iconTranslateAnchor 图标平移参考系，可选值: "map", "viewport"
     * @param {string} options.textField 文本内容
     * @param {string[]} options.textFont 文本字体，默认["Open Sans Regular","Arial Unicode MS Regular"]
     * @param {number} options.textSize 文本大小，默认16
     * @param {boolean} options.textAllowOverlap 是否允许文本重叠，默认false
     * @param {string} options.textColor 文本颜色，默认"#000000"
     * @param {number} options.textOpacity 文本透明度，0-1之间，默认1
     * @param {[number,number]} options.textOffset 文本偏移，[x,y]格式
     * @param {number} options.textRotate 文本旋转角度
     * @param {number} options.textPadding 文本内边距，默认2
     * @param {boolean} options.textKeepUpright 是否防止文本倒置，默认true
     * @param {number} options.textMaxWidth 文本最大宽度，默认10em
     * @param {number} options.textLineHeight 文本行高，默认1.2
     * @param {number} options.textLetterSpacing 文字间距，默认0
     * @param {string} options.textJustify 文本对齐方式，可选值: "auto", "left", "center", "right"
     * @param {string} options.textAnchor 文本锚点位置
     * @param {string} options.textRotationAlignment 文本旋转对齐方式
     * @param {string} options.textPitchAlignment 文本倾斜对齐方式
     * @param {number} options.textMaxAngle 相邻字符最大角度，默认45°
     * @param {string[]} options.textWritingMode 文本书写模式
     * @param {string} options.textTransform 文本转换方式，可选值: "none", "uppercase", "lowercase"
     * @param {number} options.textRadialOffset 文本径向偏移
     * @param {string[]} options.textVariableAnchor 文本可变锚点位置数组
     * @param {boolean} options.textIgnorePlacement 是否忽略文本位置
     * @param {boolean} options.textOptional 文本显示选项
     * @param {string} options.textHaloColor 文本光晕颜色
     * @param {number} options.textHaloWidth 文本光晕宽度
     * @param {number} options.textHaloBlur 文本光晕模糊度
     * @param {[number,number]} options.textTranslate 文本平移距离
     * @param {string} options.textTranslateAnchor 文本平移参考系
     * @param {string} options.symbolPlacement 符号放置位置，可选值: "point", "line", "line-center"
     * @param {number} options.symbolSpacing 符号间距，默认250
     * @param {boolean} options.symbolAvoidEdges 是否避免边缘碰撞
     * @param {string} options.symbolZOrder 符号绘制顺序，可选值: "auto", "viewport-y", "source"
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer24的点符号数据图层, 图标名称为_markerIcon_
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createSymbolDataLayer(geojson, {
      * layerName: 'layer24',
      * iconImage: '_markerIcon_'
      * });
      * console.log(layer);
     */
    async createSymbolDataLayer(data, options)  {
            options = options || {};
            let type = "symbol";
        
    
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
    
            if (options.textField && data.features.length > 0 && data.features[0].properties && (options.textField in data.features[0].properties)) {
                // 如果内容是geojson中的一个属性，则需要获取该属性值
                options.textField = "['get', '" + options.textField + "']";
            }
            let layerOptions = {
                layerId,
                sourceId,
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createSymbolDataLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 根据给定的坐标位置直接绘制一个矩形
     * @param {[number, number, number, number]} points 矩形的坐标[x1,y1,x2,y2]，表示左下角和右上角坐标，参数必填
     * @param {object} options 配置参数, 参数可选
     * @param {string} options.color 颜色，格式为#RRGGBB，参数可选
     * @param {boolean} options.isFill 是否填充，默认false，参数可选
     * @param {number} options.opacity 透明度，默认1，参数可选
     * @param {number} options.data 数据值，参数可选
     * @returns {Object} 返回绘制的图元对象，返回的对象包含以下属性：
     * @example
      * // 创建三个地图范围内随机位置的矩形， 矩形宽高为地图宽度的1/20,颜色随机，填充，透明度0.5, 其中第二个不填充
      * let points = this.randomPointInMapBounds(3);
      * let rect = this.getMapGeoBounds(); // 获取地图范围
      * let width = (rect[2] - rect[0]) / 20; // 矩形宽度为地图宽度的1/20
      * for(let i = 0; i < points.length; i++) {
      * const rect = this.createRectangle([
      * points[i][0], points[i][1],
      * points[i][0] + width, points[i][1] + width
      * ], {
      * color: vjmap.randomColor(),
      * isFill: i == 1 ? false : true,
      * opacity: 0.5,
      * data: i
      * });
      * console.log(`矩形${i+1} ID:`, rect.id);
      * console.log(`矩形${i+1} 坐标:`, rect.points);
      * }
     */
    createRectangle(points, options)  {
            let bounds = vjmap.GeoBounds.fromArray(points);
            let pts = bounds.toPointArray();
            pts.push(pts[0]);
            let draw = this.map.getDrawLayer();
            let id = vjmap.RandomID(10);
            draw.add({
                id,
                type: 'Feature',
                geometry: {
                    type: options.isFill ? 'Polygon' : 'LineString',
                    coordinates: options.isFill ? [pts.map(pt => this.map.toLngLat(pt))] : pts.map(pt => this.map.toLngLat(pt))
                },
                properties: {
                    color: options.color,
                    opacity: options.opacity,
                    data: options.data
                }
            });
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.map.getDrawLayer().delete(id);
            });
            return {
                id,
                points
            };
        }
    

    /**
     * 保存用户自定义数据至服务端
     * @param {any} value 键值
     * @param {any} prop 属性值
     * @param {number} ttl 有效时间，单位秒，默认长期有效
     * @returns {Promise<boolean>} 返回保存是否成功, true表示成功，false表示失败
     * @example
      * // 保存用户自定义数据至服务端，键名为testKey，值为testValue，属性为{ prop1: "value1" }
      * await this.saveCustomDataServer("testKey", "testValue", { prop1: "value1" });
      * // 批量保存用户自定义数据至服务端，键名为testKey1，值为testValue1，属性为{ prop1: "value1" }，有效时间为3600秒，键名为testKey2，值为testValue2，属性为{ prop2: "value2" }，有效时间为7200秒
      * await this.saveCustomDataServer([
      * { key: "testKey1", value: "testValue1", prop: { prop1: "value1" }, ttl: 3600 },
      * { key: "testKey2", value: "testValue2", prop: { prop2: "value2" }, ttl: 7200 }
      * ]);
     */
    async saveCustomDataServer(key, value, prop, ttl)  {
            let svc = this.map.getService();
            let res = await svc.saveCustomData(key, value, prop, ttl);
            return res?.status === "ok";
        }
    

    /**
     * 增加地图比例尺控件
     * @param {number} options.maxWidth 比例尺最大宽度，参数可选
     * @param {string} options.unit 比例尺单位，参数可选
     * @param {'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'} options.position 控件位置，参数可选, 默认'bottom-left'
     * @example
      * // 增加地图比例尺控件,位置右下角
      * this.addMapScaleControl({ position: 'bottom-right' });
     */
    addMapScaleControl(options)  {
            options = options || {};
            this.context.global.controls = this.context.global.controls || {};
            if (this.context.global.controls.scaleControl) {
                this.map.removeControl(this.context.global.controls.scaleControl.control);
            }
            let scaleControl = new vjmap.ScaleControl(options);
            this.map.addControl(scaleControl, options.position || 'bottom-left');
            this.context.global.controls.scaleControl = {
                control: scaleControl,
                position: options.position,
                options: options
            };
            // 增加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.map.removeControl(scaleControl);
                delete this.context.global.controls.scaleControl;
            }); 
        }
    

    /**
     * 增加一个功能按钮及实现功能
     * @param {string} name 按钮名，必填
     * @param {Function} func 要实现的功能函数，必填
     * @param {string} tooltip 按钮的提示信息, 参数可选
     * @example
      * // 增加一个功能按钮,按钮名为"放大一级"，功能为将地图缩放级别放大一级
      * this.addButtonItem("放大一级", () => {
      * let zoom = map.getZoom();
      * map.setZoom(zoom + 1);
      * })
     */
    addButtonItem(name, func, tooltip)  {
            this.context.ui.addButtonItem(name, func, tooltip);   
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                 this.removeButtonItem(name);
             });
         }
    

    /**
     * 删除绘制的图元
     * @param {string | string[]} id 图元ID或图元ID数组，参数必填
     * @example
      * // 删除绘制的全部图元
      * const geojson = this.getDrawAll();
      * if (!geojson) return;
      * let ids = [];
      * for(let i = 0; i < geojson.features.length; i++) {
      * let feature = geojson.features[i];
      * ids.push(feature.id);
      * }
      * this.deleteDrawFeature(ids);
     */
    deleteDrawFeature(id)  {
            let draw = this.map.getDrawLayer();
            draw.delete(id);
        }
    

    /**
     * 创建线拉伸图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {string} options.fillExtrusionColor 拉伸颜色，参数可选，默认为"#00ffff"
     * @param {number} options.fillExtrusionOpacity 透明度，参数可选，取值范围0-1
     * @param {number} options.fillExtrusionHeight 拉伸CAD图制图单位，参数可选，默认为500000
     * @param {number} options.fillExtrusionBase 拉伸底部高度，单位CAD图制图单位，参数可选，默认为0
     * @param {string} options.fillExtrusionPattern 拉伸填充图案，参数可选，使用雪碧图中的图像名称，图像宽度必须是2的系数
     * @param {[number,number]} options.fillExtrusionTranslate 平移量，参数可选，格式[x,y]，负数表示左和上，单位像素，默认[0,0]
     * @param {string} options.fillExtrusionTranslateAnchor 平移参考系，参数可选，"map"表示相对地图平移，"viewport"表示相对视窗平移
     * @param {boolean} options.fillExtrusionVerticalGradient 是否应用垂直渐变，参数可选，true时侧面下方位置会略暗
     * @param {boolean} options.isHoverPointer 是否启用鼠标悬浮事件，参数可选
     * @param {boolean} options.isHoverFeatureState 是否启用悬浮实体状态，参数可选
     * @param {number} options.offsetLine 线扩展大小，参数可选，默认为10
     * @param {boolean} options.isBreakLine 是否打散成线段，参数可选
     * @param {number} options.anmiDuration 动画持续时长，单位秒，参数可选，默认为0（表示没有动画）
     * @param {number} options.anmiRepeat 动画重复次数，参数可选，默认为0（表示无限循环）
     * @param {string} options.anmiRepeatType 动画重复类型，参数可选，可选值："reverse"(反向)、"loop"(循环)、"mirror"(镜像)
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer18的线拉伸数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createLineExtrusionsLayer(geojson, {
      * layerName: 'layer18'
      * });
      * console.log(layer);
     */
    async createLineExtrusionsLayer(data, options)  {
            options = options || {};
            let type = "lineExtrusions";
        
            if(options.fillExtrusionHeight) {
              options.fillExtrusionHeight = this.map.geoLengthToHeight(options.fillExtrusionHeight);
            }
            if(options.fillExtrusionBase) {
              options.fillExtrusionBase = this.map.geoLengthToHeight(options.fillExtrusionBase);
            }
    
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                fillExtrusionColor: "#00ffff",
                fillExtrusionHeight: this.map.geoLengthToHeight(this.map.getGeoBounds().width() / 6),
                anmiDuration: 0,
                offsetLine: 10,
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createLineExtrusionsLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 叠加CAD图
     * @param {string} [options.mapid] 地图ID
     * @param {string} [options.version] 地图版本(为空时采用当前打开的地图版本)
     * @param {string} [options.layer] 图层样式名称(如果为空时，则用默认样式)
     * @param {string} [options.epsg] cad图epsg代号
     * @param {"beijing54"|"xian80"|"cgs2000"} [options.crs] cad图的坐标系,beijing54(北京54坐标)、xian80(西安80坐标、cgs2000(CGCS2000坐标)
     * @param {string} [options.webMapTiles] 叠加地图为互联网地图时，数据瓦片地址, 可为空
     * @param {boolean} [options.overMapImage] 叠加地图为互联网地图时，是否为影像地图
     * @param {boolean} [options.isSetRotateZero] 公共点叠加时是否允许旋转
     * @param {boolean} [options.baseMapWgs84] 底图是否为互联网地图wgs84坐标
     * @returns {Promise<string>} 返回叠加的图层名称
     * @example
      * // 示例一
      * // 叠加cad图,mapid为sys_world
      * let layerName = await this.overlayCadMap({
      * mapid: "sys_world"
      * });
      * console.log("layerName", layerName);
      * // 示例二
      * // 自动叠加cad图,mapid为sys_cad2000
      * let layerName2 = await this.overlayCadMap({
      * mapid: "sys_cad2000",
      * overlayType: "auto"
      * });
      * console.log("layerName2", layerName2);
      * // 示例三
      * // 直接叠加cad图,mapid为sys_world
      * let layerName3 = await this.overlayCadMap({
      * mapid: "sys_world",
      * overlayType: "direct"
      * });
      * console.log("layerName3", layerName3);
     */
    async overlayCadMap(options)  {
            options = {...options}
            // 判断底图是否为互联网地图
            let isWebMap = this.map.getMapProjection() instanceof vjmap.LnglatProjection;
            let wmsTileUrl;
            if (!options.mapid) {
                this.showMessage("请设置要叠加的cad图的mapid");
                return;
            }
            if (isWebMap) {
               // 底图是互联网地图，互联网地图叠加cad图, 只能自动叠加或四参数叠加
                let epsg = options.epsg;
                if (!epsg) {
                    epsg = await this.getCadEpsgCode(options);
                }
                if (options.overlayType !== "param" && epsg) {
                    options.epsg = epsg;
                   // 自动叠加
                   wmsTileUrl = await this.getWmsTileUrl(options);
                   let svc = this.map.getService();
                   // 把cad图的坐标转成互联网地图的坐标，把地图移动到cad图的中心点
                   const cadToWebCoordinate = async point => {
                        let result = await svc.cmdTransform(epsg, "EPSG:4326", vjmap.geoPoint(point)); // 如果不想调服务转，也可以在前端用proj4库
                        return result[0];// 返回的第1条结果
                   }
                   let metadata = await svc.metadata(options.mapid, options.version);
                   let mapExtent = vjmap.GeoBounds.fromString(metadata.bounds);
                   let p1 = await cadToWebCoordinate(mapExtent.min);
                   let p2 = await cadToWebCoordinate(mapExtent.max);
                   this.fitMapBounds([p1[0],p1[1],p2[0],p2[1]], {padding: 0}); 
                } else {
                   // 四参数叠加
                    // 用四参数叠加, 左边是默认地图，右边是要叠加的cad图
                    let fp = await this.context.ui.getFourParamter({
                        mapid: options.mapid,
                        version: options.version,
                        layer: options.layer
                    });
                    if (!fp) return;
                    options.coordinates = [];
                    for(let n = 0; n < fp.leftMarkers.length; n++) {
                        let leftMarker = fp.leftMarkers[n];
                        let rightMarker = fp.rightMarkers[n];
                        options.coordinates.push({
                            x1: rightMarker.x,
                            y1: rightMarker.y,
                            x2: leftMarker.x,
                            y2: leftMarker.y
                        });
                    }
                    options.overlayType = "param";
                    options.isSetRotateZero = !fp.allowsRotation;
                    wmsTileUrl = await this.getWmsTileUrl(options);
                    let svc = this.map.getService();
                    // 把cad图的坐标转成互联网地图的坐标，把地图移动到cad图的中心点
                    const cadToWebCoordinate =  point => {
                        let p = vjmap.coordTransfromByFourParamter(point,{
                            dx: fp.fourParamter[0], 
                            dy: fp.fourParamter[1], 
                            scale: fp.fourParamter[2], 
                            rotate: fp.fourParamter[3],
                        });
                        let wgs84 = vjmap.Projection.mercator2LngLat(p); // 墨卡托3857转经纬度wgs84
                        return wgs84;
                    }
                    let metadata = await svc.metadata(options.mapid, options.version);
                    let mapExtent = vjmap.GeoBounds.fromString(metadata.bounds);
                    let p1 =  cadToWebCoordinate(mapExtent.min);
                    let p2 =  cadToWebCoordinate(mapExtent.max);
                    this.fitMapBounds([p1[0],p1[1],p2[0],p2[1]], {padding: 0}); 
                }
            } else {
               // 底图是cad图，cad图叠加cad图，只能直接通过坐标叠加或四参数叠加
               if (options.overlayType == "direct") {
                // 直接通过坐标叠加
                // 自动叠加
                wmsTileUrl = await this.getWmsTileUrl(options);
               } else {
                // 四参数叠加
                // 用四参数叠加, 左边是默认地图，右边是要叠加的cad图
                if (!options.mapid) {
                    this.showMessage("请设置要叠加的cad图的mapid");
                    return;
                }
                let fp = await this.context.ui.getFourParamter({}, {
                    mapid: options.mapid,
                    version: options.version,
                    layer: options.layer
                });
                if (!fp) return;
                options.coordinates = [];
                for(let n = 0; n < fp.leftMarkers.length; n++) {
                    let leftMarker = fp.leftMarkers[n];
                    let rightMarker = fp.rightMarkers[n];
                    options.coordinates.push({
                        x1: leftMarker.x,
                        y1: leftMarker.y,
                        x2: rightMarker.x,
                        y2: rightMarker.y
                    });
                }
                options.overlayType = "param";
                options.overMapType = "CAD";
                options.isSetRotateZero = !fp.allowsRotation;
                wmsTileUrl = await this.getWmsTileUrl(options);
               }
            }
            if (!wmsTileUrl) return;
            // 创建栅格瓦片图层
            let layer = await this.createRasterLayer({
                tiles: wmsTileUrl,
            });
            return layer
          }
    

    /**
     * 将CAD图几何坐标转换为地图像素点坐标
     * @returns {[number, number]} 返回包含两个数字的数组[x,y]，表示地图像素点坐标
     * @example
      * // 将CAD图几何坐标[1000, 2000]转换为地图像素点坐标
      * let pixel = this.geoPointToPixelPoint([1000, 2000]);
      * let x = pixel[0];
      * let y = pixel[1];
      * console.log(x, y);
     */
    geoPointToPixelPoint(pt)  {
            let lngLat = this.map.toLngLat(pt);
            let pixelPoint = this.map.project(lngLat);
            return [pixelPoint.x, pixelPoint.y];
        }
    

    /**
     * 切换至矢量瓦片样式
     * @example
      * // 切换至矢量瓦片样式
      * this.switchRasterToVectorStyle();
     */
    switchRasterToVectorStyle()  {
            // 如果当前地图已经使用矢量瓦片样式，则不进行切换
            if (this.map.hasVectorLayer()) {
                return
            }
            this.map.switchRasterToVectorStyle(this.map.getService().vectorTileUrl());
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.map.switchVectorToRasterStyle(this.map.getService().rasterTileUrl());
            });
        }
    
    

    /**
     * 获取所有信息窗口Popup
     * @param {string} id 信息窗口Popup的id
     * @param {[number, number]} position 信息窗口Popup的坐标[x, y]
     * @param {string} data 信息窗口Popup的数据
     * @returns {Array<Object>} 返回所有信息窗口Popup, 每个Popup对象包含:
     * @example
      * // 获取所有信息窗口Popup
      * let popups = this.getAllPopups();
      * for(let i = 0; i < popups.length; i++) {
      * let popup = popups[i];
      * let x = popup.position[0];
      * let y = popup.position[1];
      * console.log(popup.id, x, y, popup.data);
      * }
     */
    getAllPopups()  {
            let popups = this.context.global.popups || [];
            popups = popups.filter(m => m.id !== undefined);
            return popups.map(m => {
                let popup = m.popup;
                let point = this.map.fromLngLat(popup.getLngLat());
                return {
                    id: m.id,
                    position: [point.x, point.y],
                    data: popup.data
                }
            });
         }
    

    /**
     * 比较地图不同
     * @param {Object} options 参数
     * @param {string} options.mapid1 地图一ID，可选，如为空时，则使用当前打开的地图
     * @param {string} [options.version1] 地图一版本(为空时采用当前打开的地图版本)
     * @param {string} [options.layer1] 地图一样式图层，可选，如为空时，则使用当前打开的地图样式名称
     * @param {string} options.mapid2 地图二ID，可选，如为空时，则使用当前打开的地图
     * @param {string} [options.version2] 地图二版本(为空时采用当前打开的地图版本)
     * @param {string} [options.layer2] 地图二样式图层，可选，如为空时，则使用当前打开的地图样式名称
     * @param {boolean} [options.noCompareNew] 不比较新增部分
     * @param {boolean} [options.noCompareDelete] 不比较删除部分
     * @param {number} [options.size] 比较大小. 默认 4096
     * @param {number} [options.cellsize] 比较单元大小. 默认 10
     * @param {number} [options.diffMinPixel] 不同的像素最小值. 默认  0
     * @param {number} [options.diffMinAlpha] 不同的透明最小值.  默认 25
     * @param {number} [options.diffMinColor] 不同的颜色最小值. 默认 6
     * @returns {Promise<Object>} 返回比较结果, 如果失败则返回undefined, 如果成功则返回字段如下:
     * @example
      * // 示例
      * // 比较当前地图和sys_hello的不同
      * let res = await this.findMapDiff({
      * mapid2: "world"
      * });
      * console.log(res);
     */
    async findMapDiff(options)  {
           options = options || {};
           let svc = this.map.getService();
           options.mapid1 = options.mapid1 || svc.currentMapParam().mapid;
           options.mapid2 = options.mapid2 || svc.currentMapParam().mapid;
           let res = await svc.cmdMapDiff(options);
           if (res && res.status) {
            const toGeoJSON = (points) => {
                let geojson = {
                    type: "FeatureCollection",
                    features: []
                };
                if (!points || points.length === 0) return geojson;
                points.forEach(p => {
                    if (p.length > 0) {
                        p.push(p[0]);
                    }
                    geojson.features.push({
                        type: "Feature",
                        geometry: {
                            type: "LineString",
                            coordinates: p
                        }
                    });
                }) ;// 闭合
                
                
                return geojson;
            }
            let result = {
                new: toGeoJSON(res.new),
                modify: toGeoJSON(res.modify),
                del: toGeoJSON(res.del),
                diff: toGeoJSON([...res.new, ...res.modify, ...res.del])
            };   
            return result;
           }
           return undefined;
       }
    

    /**
     * 设置数据图层属性
     * @param {string} layerName 图层名称，参数必填
     * @param {object} property 图层属性对象，参数必填
     * @example
      * // 设置图层名称为layer1的数据图层属性为{"minZoom":1,"maxZoom":5}
      * let layerName = 'layer1';
      * let property = {"minZoom":1,"maxZoom":5};
      * this.setDataLayerProperty(layerName, property);
     */
    setDataLayerProperty(layerName, property)  {
            let item = this.context.global.layers.find(layer => layer.layerName == layerName);
            if(item) {
               let oldProp = {...vjcommon.toMapLayer(item.layer, item.layer.mapLayer)};
               let layerId = item.layerId;
               if (item.layer && typeof(item.layer.getLayerId) == "function") {
                    layerId = item.layer.getLayerId();
               }
               item.layer.setLayerStyle(this.map, layerId, property, oldProp);
               // 添加撤销记录
               this.context.ui.addUndoRecord(() => {
                   this.setDataLayerProperty(item.layerName, oldProp);
               });
            }
        }
    

    /**
     * 选择图中实体对象
     * @param {object} options 选择实体对象的选项，参数可选
     * @param {boolean} options.useGeomCoord 使用几何坐标还是位置坐标，false表示位置坐标，true表示几何坐标，参数可选, 默认几何坐标true
     * @param {boolean} options.includeWholeEntity 选择时是否选择上整个实体对象，true表示选择上整个实体对象，false表示选择上实体对象的一部分，参数可选, 默认false
     * @param {boolean} options.isPointSel 点选还是框选，false表示框选，true表示点选，参数可选, 默认false
     * @param {boolean} options.disableSnap 是否禁用捕捉，false表示不禁用捕捉，true表示禁用捕捉，参数可选, 默认false
     * @param {number} options.maxSelectCount 最大选择数量，参数可选, 默认不限制
     * @returns {Object} 返回标准的GeoJson格式的FeatureCollection对象, 包含features属性, 每个feature包含geometry和properties属性,properties属性中包含如下字段:
     * @example
      * // 示例一
      * // 点击选择图中实体对象
      * const geojson1 = await this.selectFeatures({
      * isPointSel: true
      * });
      * // 输出选择的实体objectid
      * let selectedObjectids = [];
      * for (let feature of geojson1.features) {
      * selectedObjectids.push(feature.properties.objectid);
      * }
      * console.log(selectedObjectids);
      * // 示例二
      * // 框选图中实体对象, 使用位置坐标, 选择整个实体对象
      * const geojson2 = await this.selectFeatures({
      * useGeomCoord: false,
      * includeWholeEntity: true,
      * isPointSel: false
      * });
      * // 输出选择实体的个数
      * console.log(geojson2.features.length);
      * // 示例三
      * // 点击选择图中的实体对象,选择整个实体对象, 最多选择1个
      * const geojson3 = await this.selectFeatures({
      * isPointSel: true,
      * includeWholeEntity: true,
      * maxSelectCount: 1
      * });
      * console.log(geojson3);
     */
    async selectFeatures(options)  {
            options = options || {};
            let features = await vjcommon.selectFeatures(this.map, options.useGeomCoord !== false, options.includeWholeEntity, options.isPointSel, options.disableSnap, undefined, options.maxSelectCount);
            return {
              features: features || [],
              type: "FeatureCollection"
            }
          }
    

    /**
     * 拆分子图
     * @returns {Promise<string[]>} 返回拆分后的每个子图的fileid (数组次序与传入的子图范围数组clipBounds次序一致), 如果失败则返回undefined
     * @example
      * // 自动提取子图范围，获取返回的子图范围数组，拆分子图
      * let res = await this.findChildMapRects();
      * if (!res) return;
      * let fileids = await this.splitChildMaps({
      * clipBounds: res.childMapBounds
      * });
      * console.log(fileids);
     */
    async splitChildMaps(options)  {
           options = options || {};
           let svc = this.map.getService();
           if (!options.clipBounds || (options.clipBounds && options.clipBounds.length == 0)) {
                if (this.map.logInfo) this.map.logInfo('参数范围为空', "error", 10000);
                return;
           }
           for(let n = 0; n < options.clipBounds.length; n++) {
            if (Array.isArray(options.clipBounds[n]) && options.clipBounds[n].length == 4) {
                options.clipBounds[n] = options.clipBounds[n].join(',');
            }
           }
           let result = await svc.cmdSplitChildMaps({
            // 方法 cloneObjects 通过深度克隆实体,效率最快(默认),  cloneDb 通过克隆文档数据库，效率较快；cloneMap 通过克隆图，效率较慢，但最能保持原样
            method: "cloneObjects",
            // 是否启动新进程处理（不影响主进程，看初始速度慢些） 默认true
            startNewProcess: true,
            // 拆分后的图是否全图 默认false 
            isFullExtent: true,
            ...options
           });
           if (result && result.error) {
               if (this.map.logInfo) this.map.logInfo(result.error, "error", 10000);
               return;
           }
           let fileids = result.fileids;
           if (fileids && fileids.length > 0) {
            for(let n = 0; n < fileids.length; n++) {
                // 去掉文件后缀
                fileids[n] = fileids[n].split(".")[0];
            }
           }
           return fileids;
       }
    

    /**
     * 在地图范围内随机生成点
     * @param {number} count 要生成的点数，默认1
     * @param {number} boundsType 范围类型(0 地图绘制范围,1 地图实际范围, 2 地图整个范围)，默认0, 参数可选
     * @returns {[number, number][]} 返回包含多个坐标点的数组[[x1,y1],[x2,y2],...]
     * @example
      * // 示例一
      * // 在地图的范围内随机生成1个点
      * let points = this.randomPointInMapBounds(1);
      * let x = points[0][0];
      * let y = points[0][1];
      * console.log(x, y);
      * // 示例二
      * // 在地图的实际范围内随机生成10个点
      * let points2 = this.randomPointInMapBounds(10, 1);
      * for (let i = 0; i < points2.length; i++) {
      * let x = points2[i][0];
      * let y = points2[i][1];
      * console.log(x, y);
      * }
     */
    randomPointInMapBounds(count, boundsType)  {
            let rect = this.getMapGeoBounds(boundsType);
            let geoBounds = vjmap.GeoBounds.fromArray(rect);
            let points = geoBounds.randomPoints(count, count);
            return points.map(p => [p.x, p.y]);
        }
    

    /**
     * 采集一个斜矩形的坐标
     * @param {boolean} isSnap 是否启用捕捉功能, 默认false
     * @returns {Promise<number[][] | undefined>} 返回包含四个点坐标的数组[[x1,y1],[x2,y2],[x3,y3],[x4,y4]]，如果取消操作则返回undefined
     * @example
      * const rect = await this.actionDrawSlantRectangle();
      * if (!rect) return;
      * console.log(rect); // 斜矩形的四个点坐标
     */
    async actionDrawSlantRectangle(isSnap)  {
            this.context.tools.logInfo("请在地图绘一个斜矩形")
            let snapObj = {};
            if (isSnap) {
                vjcommon.getMapSnapPoints(this.map, snapObj);
            }
            let drawRect = await vjmap.Draw.actionDrawSlantRectangle(this.map, isSnap ? {
                api: {
                    getSnapFeatures: snapObj
                }
            } : {});
            if (drawRect.cancel) {
                return;
            }
            let coords = drawRect.features[0].geometry.coordinates[0];
            return coords.map(c => {
                let p = this.map.fromLngLat(c);
                return [p.x, p.y];
            });
        }
    

    /**
     * 创建箭头线数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {boolean} options.isHoverPointer 是否启用鼠标悬浮事件，参数可选
     * @param {boolean} options.isHoverFeatureState 是否启用悬浮实体状态，参数可选
     * @param {string} options.lineColor 线条颜色，默认#FF9900
     * @param {number} options.lineWidth 线宽，默认10
     * @param {number} options.lineOpacity 线透明度，取值0-1
     * @param {number} options.lineGapWidth 线间隙宽度，在线的实际路径之外绘制描边时的内部间隙宽度
     * @param {number} options.lineOffset 线的偏移量，正值向右偏移，负值向左偏移
     * @param {number} options.lineBlur 线模糊度，需小于线宽度
     * @param {string} options.strokeStyle 线型，'solid'实线或'dashed'虚线
     * @param {[number,number]} options.lineDasharray 虚线样式，格式如[1,1]表示实线虚线的组合
     * @param {[number,number]} options.lineTranslate 平移量，格式[x,y]，负数分别表示左和上
     * @param {string} options.lineCap 线帽形状，'butt'尖头、'round'圆头、'square'平头
     * @param {string} options.lineJoin 拐角形状，'bevel'平拐、'round'圆拐、'miter'棱拐
     * @param {string} options.strokeImage 线图案，需与预加载的图片名对应
     * @param {string} options.lineGradient 线渐变
     * @param {boolean} options.showBorder 是否显示线边框，默认true
     * @param {string} options.borderColor 边框颜色，默认#f00
     * @param {number} options.borderWidth 边框宽度
     * @param {number} options.borderOpacity 边框透明度，取值0-1
     * @param {number} options.borderGapWidth 边框间隙宽度
     * @param {number} options.borderOffset 边框偏移量
     * @param {number} options.borderBlur 边框模糊度
     * @param {string} options.borderGradient 边框渐变
     * @param {boolean} options.showDir 是否显示箭头，默认true
     * @param {number} options.dirSize 箭头大小
     * @param {number} options.dirSpacing 箭头间距
     * @param {number} options.dirImageWidth 箭头图像宽度
     * @param {number} options.dirImageHeight 箭头图像高度
     * @param {string} options.dirImageColor 箭头图像颜色
     * @param {string} options.dirIconColor 箭头图标颜色
     * @param {string} options.dirLayout 箭头布局
     * @param {string} options.cursor 鼠标光标形状
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer5的箭头线数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createArrowLineLayer(geojson, {
      * layerName: 'layer5'
      * });
      * console.log(layer);
     */
    async createArrowLineLayer(data, options)  {
            options = options || {};
            let type = "arrowLine";
        
    
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                lineWidth: 10,
                showDir: true,
                showBorder: true,
                borderColor: "#f00",
                lineColor: '#FF9900',
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createArrowLineLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 创建多边形拉伸动画数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {string} options.fillExtrusionColor 拉伸颜色，参数可选，默认为"#00ffff"
     * @param {number} options.fillExtrusionOpacity 透明度，参数可选，取值范围0-1
     * @param {number} options.fillExtrusionHeight 拉伸高度，单位CAD图制图单位，参数可选，默认为500000
     * @param {number} options.fillExtrusionBase 拉伸底部高度，单位CAD图制图单位，参数可选，默认为0
     * @param {string} options.fillExtrusionPattern 拉伸填充图案，参数可选，使用雪碧图中的图像名称，图像宽度必须是2的系数
     * @param {[number,number]} options.fillExtrusionTranslate 平移量，参数可选，格式[x,y]，负数表示左和上，单位像素，默认[0,0]
     * @param {string} options.fillExtrusionTranslateAnchor 平移参考系，参数可选，"map"表示相对地图平移，"viewport"表示相对视窗平移
     * @param {boolean} options.fillExtrusionVerticalGradient 是否应用垂直渐变，参数可选，true时侧面下方位置会略暗
     * @param {boolean} options.isHoverPointer 是否启用鼠标悬浮事件，参数可选
     * @param {boolean} options.isHoverFeatureState 是否启用悬浮实体状态，参数可选
     * @param {number} options.anmiDuration 动画持续时长，单位秒，参数可选，默认为2，值为0表示没有动画
     * @param {number} options.anmiRepeat 动画重复次数，参数可选，值为0表示无限循环
     * @param {string} options.anmiRepeatType 动画重复类型，参数可选，可选值："reverse"(来回播放)、"loop"(循环播放)、"mirror"(镜像播放)
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer13的多边形拉伸动画数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createFillExtrusionsAnimateLayer(geojson, {
      * layerName: 'layer13'
      * });
      * console.log(layer);
     */
    async createFillExtrusionsAnimateLayer(data, options)  {
            options = options || {};
            let type = "fillExtrusionsAnimate";
        
            if(options.fillExtrusionHeight) {
              options.fillExtrusionHeight = this.map.geoLengthToHeight(options.fillExtrusionHeight);
            }
            if(options.fillExtrusionBase) {
              options.fillExtrusionBase = this.map.geoLengthToHeight(options.fillExtrusionBase);
            }
    
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                fillExtrusionColor: "#00ffff",
                fillExtrusionHeight: this.map.geoLengthToHeight(this.map.getGeoBounds().width() / 6),
                anmiDuration: 2,
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createFillExtrusionsAnimateLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 采集一个多边形的坐标
     * @param {boolean} isSnap 是否启用捕捉功能, 默认false
     * @returns {Promise<number[][] | undefined>} 返回坐标点数组[[x1,y1],[x2,y2],...]，如果取消操作则返回undefined
     * @example
      * const polygon = await this.actionDrawPolygon();
      * if (!polygon) return;
      * // 输出多边形的所有点坐标
      * for (let i = 0; i < polygon.length; i++) {
      * let x = polygon[i][0];
      * let y = polygon[i][1];
      * console.log(x, y);
      * }
     */
    async actionDrawPolygon(isSnap)  {
            this.context.tools.logInfo("请在地图上绘制一个多边形")
            let snapObj = {};
            if (isSnap) {
                vjcommon.getMapSnapPoints(this.map, snapObj);
            }
            let drawPolygon = await vjmap.Draw.actionDrawPolygon(this.map, isSnap ? {
                api: {
                    getSnapFeatures: snapObj
                }
            } : {});
            if (drawPolygon.cancel) {
                return;
            }
            let coords = drawPolygon.features[0].geometry.coordinates[0];
            return coords.map(c => {
                let p = this.map.fromLngLat(c);
                return [p.x, p.y];
            });
        }
    

    /**
     * 设置点标记Marker的属性
     * @param {string} id 点标记Marker的id
     * @param {Record<string, any>} options 其他参数，参数可选
     * @param {[number, number]} options.position 坐标[x, y]，参数可选，默认为undefined
     * @param {string} options.color 颜色，参数可选，默认为#3FB1CE
     * @param {any} options.popupContent 弹窗内容，参数可选，默认为空表示不显示弹窗
     * @param {boolean} options.popupShowInForm 弹窗内容是否以表单形式显示，参数可选，默认为false
     * @param {number} options.popupWidth 弹窗宽度，参数可选
     * @param {number} options.popupHeight 弹窗高度，参数可选
     * @param {boolean} options.popupOpen 弹窗内容是否打开，参数可选，默认为false
     * @param {boolean} options.draggable 是否可拖动，参数可选，默认为false
     * @param {number} options.rotation 旋转角度，参数可选，默认为0
     * @param {string} options.icon 自定义图标名称，参数可选，默认为空
     * @param {any} options.data 数据内容，参数可选，默认为undefined
     * @param {number} options.elevation 坐标标高值，参数可选，默认为0
     * @param {boolean} options.visible 是否显示，参数可选，默认为true
     * @example
      * // 设置id为marker_1的点标记的属性，坐标为[10, 20]，颜色为红色，弹窗内容为"Hello, World!"，图标为sensor，可拖动，旋转角度为45度，数据内容为{name: "Sensor", value: 100}
      * this.setMarkerProperty("marker_1", {
      * position: [10, 20],
      * color: "#FF0000",
      * popupContent: "Hello, World!",
      * icon: "sensor",
      * draggable: true,
      * rotation: 45,
      * data: {name: "Sensor", value: 100}
      * });
     */
    setMarkerProperty(id, options)  {
            let markers = this.map.getMarkers();
            let marker = markers.find(m => m.id == id);
            let oldOptions = {};
            if (marker) {
                if (options.position) {
                    let position = this.map.fromLngLat(marker.getLngLat());
                    oldOptions.position = [position.x, position.y];
                    marker.setLngLat(this.map.toLngLat(options.position));
                }
                if (options.color) {
                    oldOptions.color = marker._color;
                    marker.setColor(options.color);
                }
                if (options.data !== undefined) {
                    oldOptions.data = marker.data;
                    marker.data = options.data;
                }
                if (options.elevation) {
                    oldOptions.elevation = marker.getHeight();
                    marker.setHeight(options.elevation);
                }
                if (options.popupContent) {
                    let popup = marker.getPopup();
                    if (!popup) {
                        popup = new vjmap.Popup({
                            offset: options.popupOffset || [0, -40]
                        })
                        marker.setPopup(popup)
                    }
                    const dom = this.context.ui.createElementByJsonData(options.popupContent, options.popupShowInForm, {
                        width: options.popupWidth,
                        height: options.popupHeight
                    })
                    popup.setDOMContent(dom.element)
                }
                if (options.popupOpen !== undefined) {
                    let popup = marker.getPopup();
                    if (popup) {
                        if (popup.isOpen() && !options.popupOpen) {
                            marker.togglePopup()
                        } else if (!popup.isOpen() && options.popupOpen) {
                            marker.togglePopup()
                        }
                    }
                }
                if (options.icon) { 
                    // 获取自定义图标数据, 在工具栏中的"地图图像管理"中对自定义图标进行管理
                    let image = this.context.ui.getCustomImages(options.icon);
                    if (image) {
                        let el = marker.getElement();
                        if (el) {
                            el.className = 'marker';
                            let imgValue = image.value;
                            if (imgValue.toLowerCase().indexOf("<svg") == 0) {
                                //svg或base64 png
                                imgValue =  "data:image/svg+xml;base64," + window.btoa(unescape(encodeURIComponent(imgValue))) ;
                            }
                            el.style.backgroundImage =
                            `url("${imgValue}")`;
                            el.style.width = (image.width ?? 40) + 'px';
                            el.style.height = (image.height ?? 40) + 'px';
                            el.style.backgroundSize = '100%';
                        }
                    }
                }
                if (options.draggable) {
                    oldOptions.draggable = marker.isDraggable();
                    marker.setDraggable(options.draggable);
                }
                if (options.rotation) {
                    oldOptions.rotation = marker.getRotation();
                    marker.setRotation(options.rotation);
                }
                if (options.visible !== undefined) {
                    oldOptions.visible = marker.getElement().style.display !== 'none';
                    marker.setVisible(options.visible, true);
                }
               
            }
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.setMarkerProperty(id, oldOptions);
            });
            
         }
    

    /**
     * 获取所有点标记Marker
     * @param {string} id 点标记Marker的id
     * @param {[number, number]} position 点标记Marker的坐标[x, y]
     * @param {string} data 点标记Marker的数据
     * @returns {Array<Object>} 返回所有点标记Marker, 每个Marker对象包含:
     * @example
      * // 获取所有点标记Marker
      * let markers = this.getAllMarkers();
      * for(let i = 0; i < markers.length; i++) {
      * let marker = markers[i];
      * let x = marker.position[0];
      * let y = marker.position[1];
      * console.log(marker.id, x, y, marker.data);
      * }
     */
    getAllMarkers()  {
            let markers = this.map.getMarkers();
            markers = markers.filter(m => m.id !== undefined);
            return markers.map(m => {
                let point = this.map.fromLngLat(m.getLngLat());
                return {
                    id: m.id,
                    position: [point.x, point.y],
                    data: m.data
                }
            });
         }
    

    /**
     * 缩放至地图范围
     * @param {Object} [options] 缩放选项
     * @param {boolean} [options.linear] 如果true，则地图使用Map#easeTo进行转换。如果false，则地图使用Map#flyTo进行转换
     * @param {Function} [options.easing] 动画过渡的缓动函数
     * @param {Array<number>} [options.offset] 相对于地图中心的给定边界的中心，以像素为单位（默认[0,0]）
     * @param {number} [options.maxZoom] 当地图视图转换到指定边界时允许的最大缩放级别
     * @param {boolean} [options.fitFillClip] 默认为窗口包含地图，如果要使地图占满窗口超出的部分裁剪，请设置为true
     * @param {Object} [eventData] 要添加到此方法触发的事件的事件对象的附加属性
     * @param {boolean} [options.isFlash] 是否使用闪烁动画，默认false
     * @example
      * // 示例一
      * // 缩放至地图范围[0,0,10,10]，并添加10像素的填充
      * this.fitMapBounds([0,0,10,10], {padding: 10});
      * // 示例二
      * // 缩放至全图显示
      * this.fitMapBounds();
     */
    fitMapBounds(bounds, options, eventData)  {
            options = options || {}
            if (Array.isArray(bounds)) {
                bounds = vjmap.GeoBounds.fromArray(bounds);
            }
            let oldBounds = this.map.fromLngLat(this.map.getBounds()); // 获取当前地图范围
            this.map.fitMapBounds(bounds, options, eventData) // 缩放至地图范围
            if (options.isFlash === true) {
                __flashBounds(typeof bounds === 'number' ? this.map.getGeoBounds(bounds) : bounds);
            }
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.map.fitMapBounds(oldBounds);
            });
         }
    

    /**
     * 获取用户数据
     * @returns {Object} 返回GeoJson的FeatureCollection对象，包括点、线、面、多点、多线、多面类型
     * @example
      * // 获取用户数据，将数据输出至会话窗口
      * let data = this.getUserData();
      * if (data) this.chatLog(JSON.stringify(data, null, 4));
     */
    getUserData()  {
            return this.context.userData;
        }
    
    

    /**
     * 切换CAD图层
     * @example
      * // 切换CAD图层
      * this.switchCadLayers();
     */
    async switchCadLayers()  {
            // 先获取默认的图层
            let layers = this.map.getService().getMapLayers();
            let layerNames = layers.map(layer => layer.name);
            let openLayers = layers.filter(layer => layer.isOff === false).map(layer => layer.name);
            const getSelectLayers = (selectNames) => {
                let selectLayers = [];
                for (let layerName of layerNames) {
                    let isOpen = selectNames.includes(layerName);
                    if (isOpen) {
                        selectLayers.push({name: layerName, isOff: false});
                    } else {
                        selectLayers.push({name: layerName, isOff: true});
                    }
                }
                return selectLayers;
            }
            let isClickApply = false;
            let selectLayerNames = await this.selectOptionsValue(layerNames, {
                isMultiSelect: true,
                title: "选择图层",
                initValue: openLayers,
                applyCallback: async (selectNames) => {
                    isClickApply = true;
                    // 点击应用时，也切换图层
                    await vjcommon.switchCadLayers(this.map, getSelectLayers(selectNames), this.map.hasVectorLayer());
                }
            });
            if (!selectLayerNames) {
                if (isClickApply) {
                    // 如果点击了应用，取消时，切换到原来的图层
                    await vjcommon.switchCadLayers(this.map, getSelectLayers(openLayers), this.map.hasVectorLayer());
                }
                return;
            }
            // 切换图层
            await vjcommon.switchCadLayers(this.map, getSelectLayers(selectLayerNames), this.map.hasVectorLayer());
            // 添加撤销记录
            let oldLayers = getSelectLayers(openLayers);
            let isVector = this.map.hasVectorLayer();
            this.context.ui.addUndoRecord(() => {
                vjcommon.switchCadLayers(this.map, oldLayers, isVector);
            });
        }
    
    

    /**
     * 增加一个点标记Marker
     * @param {[number, number]} pt 要设置的坐标[x, y]
     * @param {Record<string, any>} options 其他参数，参数可选
     * @param {string} options.color 颜色，参数可选，默认为#3FB1CE
     * @param {any} options.popupContent 弹窗内容，参数可选，默认为空表示不显示弹窗
     * @param {[number, number]} options.popupOffset 弹窗偏移量，参数可选，默认为[0, -40]
     * @param {boolean} options.popupShowInForm 弹窗内容是否以表单形式显示，参数可 选，默认为false
     * @param {boolean} options.popupOpen 弹窗内容是否打开，参数可选，默认为false
     * @param {number} options.popupWidth 弹窗宽度，参数可选
     * @param {number} options.popupHeight 弹窗高度，参数可选
     * @param {string} options.anchor 锚点, 选项包括'center'，'top'，'bottom'，'left'，'right'，'top-left'，'top-right'，'bottom-left'，和'bottom-right'。参数可选，默认为'center'
     * @param {boolean} options.draggable 是否可拖动，参数可选，默认为false
     * @param {number} options.rotation 旋转角度，参数可选，默认为0
     * @param {string} options.icon 自定义图标名称，参数可选，默认为空
     * @param {number} options.elevation 坐标标高值，参数可选，默认为0
     * @param {any} options.data 数据内容，参数可选，默认为undefined
     * @param {boolean} options.visible 是否显示，参数可选，默认为true
     * @returns {string} 返回id
     * @example
      * // 示例一
      * // 在坐标[10,20]处添加一个红色的点标记
      * let markerId = this.addMarker([10, 20], {
      * color: "#FF0000"
      * });
      * console.log(markerId);
      * // 示例二
      * // 获取用户数据中的所有点类型的数据，在相应位置添加点标记, 弹窗内容为数据的属性值
      * let data = this.getUserData();
      * let features = data.features.filter(f => f.geometry.type == "Point");
      * features.forEach(f => {
      * this.addMarker(f.geometry.coordinates, {
      * popupContent: f.properties
      * });
      * })
      * // 示例三
      * // 在地图范围内随机生成一个点，在这个位置创建一个marker，图标为sensor，可拖动。
      * let points = this.randomPointInMapBounds(1);
      * let x = points[0][0];
      * let y = points[0][1];
      * this.addMarker([x, y], {
      * draggable: true,
      * icon: "sensor"
      * });
     */
    addMarker(pt, options)  {
            options = options || {};
            let element;
            if (options.icon) { 
                // 获取自定义图标数据, 在工具栏中的"地图图像管理"中对自定义图标进行管理
                let image = this.context.ui.getCustomImages(options.icon);
                if (image) {
                    let el = document.createElement('div');
                    el.className = 'marker';
                    let imgValue = image.value;
                    if (imgValue.toLowerCase().indexOf("<svg") == 0) {
                        //svg或//base64 png
                        imgValue =  "data:image/svg+xml;base64," + window.btoa(unescape(encodeURIComponent(imgValue))) ;
                    }
                    el.style.backgroundImage =
                    `url("${imgValue}")`;
                    el.style.width = (image.width ?? 40) + 'px';
                    el.style.height = (image.height ?? 40) + 'px';
                    el.style.backgroundSize = '100%';
                    element = el;
                }
            }
            let marker = new vjmap.Marker({
                color: options.color,
                anchor: options.anchor,
                draggable: options.draggable,
                rotation: options.rotation,
                element: element
            })
            marker.setLngLat(this.map.toLngLat(pt));
            marker.addTo(this.map);
            marker.id = "marker_" + vjmap.RandomID()
            if (options.data) marker.data = options.data;
            if (options.elevation) marker.setHeight(options.elevation);
    
            if (options.popupContent) {
                let popup = new vjmap.Popup({
                    offset: options.popupOffset || [0, -40]
                })
                const dom = this.context.ui.createElementByJsonData(options.popupContent, options.popupShowInForm, {
                    width: options.popupWidth,
                    height: options.popupHeight
                })
                popup.setDOMContent(dom.element)
                marker.setPopup(popup)
                if (options.popupOpen) {
                    marker.togglePopup()
                }
            }
            if (options.visible !== undefined) {
                marker.setVisible(options.visible, true);
            }
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteMarker(marker.id);
            });
            return marker.id
        }
    

    /**
     * 根据根据的坐标位直接绘制一个点符号
     * @param {[number, number]} point 点符号的坐标[x, y]，参数必填
     * @param {object} options 配置参数, 参数可选
     * @param {string} options.icon 符号图标名称，默认为_markerIcon_，参数可选
     * @param {number} options.iconSize 符号图标放大倍数，默认为1，参数可选
     * @param {number} options.iconRotate 符号图标旋转角度，默认为0，参数可选
     * @param {string} options.text 符号文本内容，默认为空，参数可选
     * @param {string} options.textColor 符号文本颜色，默认为空，参数可选
     * @param {number} options.textSize 符号文本大小，默认为22，参数可选
     * @param {number} options.textRotate 符号文本旋转角度，默认为0，参数可选
     * @param {number} options.textOpacity 符号文本透明度，默认为0，参数可选
     * @param {number} options.data 数据值，参数可选
     * @returns {Promise<Object | undefined>} 返回绘制的图元对象，如果取消操作则返回undefined，返回的对象包含以下属性：
     * @example
      * // 示例一
      * // 在位置[10, 20]处绘制一个点符号
      * const pt = this.createSymbol([10, 20]); // 启用捕捉功能采绘制一个点符号
      * if (!pt) return;
      * // 点的坐标
      * let point = pt.point;
      * const x = point[0];
      * const y = point[1];
      * console.log(x, y);
      * // 图元ID
      * let id = pt.id;
      * console.log(id);
      * // 示例二
      * // 在地图范围内随机生成1个点, 在这个点位置绘制一个点符号，符号图标名称为_pulsingDot_
      * let points = this.randomPointInMapBounds(1);
      * const pt2 = this.createSymbol(points[0], {
      * icon: "_pulsingDot_"
      * });
      * console.log(pt2);
      * // 示例三
      * // 在地图范围内随机生成10个点, 根据这些位置分别绘制点符号，符号图标名称为空，文本内容为当前坐标值, 颜色为红色
      * let points2 = this.randomPointInMapBounds(10);
      * for (let i = 0; i < points2.length; i++) {
      * let coord = points2[i];
      * this.createSymbol(coord, {
      * icon: "",
      * text: `[${coord[0].toFixed(2)}, ${coord[1].toFixed(2)}]`,
      * textColor: "#FF0000"
      * });
      * }
     */
    createSymbol(point, options)  {
            options = options || {};
            if (!point) return;
            let draw = this.map.getDrawLayer();
            let id = vjmap.RandomID(10)
            let icon = options.icon;
            if (icon === undefined) icon = "_markerIcon_";
            let onlyText = options.text && !icon;
            let symbolAttr = icon ? {
                "icon_image": icon, 
                "icon_size": options.iconSize || 1,
                "icon_rotate": options.iconRotate || 0,
            } : undefined;
    
            let textAttr = options.text ? {
                "text": options.text, 
                "text_size": options.textSize,
                "text_rotate": options.textRotate,
                "text_opacity": options.textOpacity,
                "color": options.textColor
            } : undefined
            draw.add({
                id,
                type: 'Feature',
                geometry: {
                    type: 'Point',
                    coordinates: this.map.toLngLat(point)
                },
                properties: {
                    "symbol": true,
                    "onlyText": onlyText,
                    "data": options.data,
                    ...symbolAttr, 
                    ...textAttr,
                }
            });
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.map.getDrawLayer().delete(id);
            });
            return {
                id,
                point
            };
        }
    

    /**
     * 弹出表单输入对话框
     * @param {object} formJsonSchema 表单的 JSON Schema 格式配置，包含表单字段、类型、校验规则和属性的定义。结构示例：
     * @returns {object} 返回用户输入的数据，格式为一个对象，其中每个字段的键为 `properties` 中的字段名，值为用户输入的值。例如：
     * @example
      * // 弹出学生表单输入对话框，包括姓名、年龄(默认18)、性别
      * let formJsonSchema = {
      * type: "object",
      * title: "学生信息表单",
      * properties: {
      * name: { type: "string", title: "姓名"},
      * age: { type: "number", title: "年龄", default: 18},
      * gender: { type: "string", title: "性别", enum: ["男", "女"]}
      * },
      * required: ["name", "age", "gender"]
      * }
      * let res = await this.showFormInputDialog(formJsonSchema);
      * let name = res.name;
      * let age = res.age;
      * let gender = res.gender;
      * console.log(name, age, gender)
     */
    async showFormInputDialog(formJsonSchema)  {
            return await this.context.ui.showFormInputDlg(formJsonSchema)
        }
    

    /**
     * 创建荧光点数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {string} options.text 要显示的文字，参数可选，默认为"index ${props.index}"
     * @param {number} options.minZoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxZoom 显示最大缩放级别，参数可选，默认为24
     * @param {boolean} options.isDrawLinePoint 绘制点时包括线上所有点, 参数可选，默认为false
     * @param {number} options.width 像素宽度，参数可选，默认为24
     * @param {number} options.markerHeight 像素高度，参数可选
     * @param {string} options.color1 颜色值一，参数可选
     * @param {string} options.color2 颜色值二，参数可选
     * @param {number} options.textFontSize 字体大小，参数可选
     * @param {string} options.textColor 字体颜色，参数可选
     * @param {boolean} options.draggable 是否可拖动，参数可选，默认为false
     * @param {string} options.animationType 动画类型，参数可选，无动画 MAP_ANIMATION_NONE，弹跳 MAP_ANIMATION_BOUNCE, 坠落 MAP_ANIMATION_DROP
     * @param {number} options.rotation 旋转角度，参数可选
     * @param {string} options.rotationAlignment 旋转对齐，参数可选, 自动 auto, 相对于地图 map, 相对于视口 viewport
     * @param {string} options.pitchAlignment 倾斜对齐，参数可选, 自动 auto, 相对于地图 map, 相对于视口 viewport
     * @param {number} options.height 坐标标高值，参数可选
     * @param {number} options.scaleMaxZoom 能缩放的最大级别，参数可选
     * @param {boolean} options.removeWhenNoInMapView 视图范围内自动显隐，参数可选
     * @param {number} options.removeWhenNoInMapViewPadding 视图范围内自动显隐Padding像素范围，参数可选，默认500
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer14的荧光点数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createFluorescenceMarkerLayer(geojson, {
      * layerName: 'layer14'
      * });
      * console.log(layer);
     */
    async createFluorescenceMarkerLayer(data, options)  {
            options = options || {};
            let type = "fluorescenceMarker";
        
    
            if (options.height) {
              options.height = this.map.geoLengthToHeight(options.height);
            }
    
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                text: "ID:${props.index}",
                width: 20,
                markerHeight: 20,
                color1: "#ff0000",
                color2: "#ffff00",
                textFontSize: 15,
                textColor: "#00ffff",
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createFluorescenceMarkerLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 创建发光的光环数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {string} options.text 要显示的文字，参数可选，默认为"index ${props.index}"
     * @param {number} options.minZoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxZoom 显示最大缩放级别，参数可选，默认为24
     * @param {boolean} options.isDrawLinePoint 绘制点时包括线上所有点, 参数可选，默认为false
     * @param {number} options.width 像素宽度，参数可选，默认为24
     * @param {number} options.markerHeight 像素高度，参数可选
     * @param {string} options.color1 颜色值一，参数可选
     * @param {string} options.color2 颜色值二，参数可选
     * @param {number} options.textFontSize 字体大小，参数可选
     * @param {string} options.textColor 字体颜色，参数可选
     * @param {boolean} options.draggable 是否可拖动，参数可选，默认为false
     * @param {string} options.animationType 动画类型，参数可选，无动画 MAP_ANIMATION_NONE，弹跳 MAP_ANIMATION_BOUNCE, 坠落 MAP_ANIMATION_DROP
     * @param {number} options.rotation 旋转角度，参数可选
     * @param {string} options.rotationAlignment 旋转对齐，参数可选, 自动 auto, 相对于地图 map, 相对于视口 viewport
     * @param {string} options.pitchAlignment 倾斜对齐，参数可选, 自动 auto, 相对于地图 map, 相对于视口 viewport
     * @param {number} options.height 坐标标高值，参数可选
     * @param {number} options.scaleMaxZoom 能缩放的最大级别，参数可选
     * @param {boolean} options.removeWhenNoInMapView 视图范围内自动显隐，参数可选
     * @param {number} options.removeWhenNoInMapViewPadding 视图范围内自动显隐Padding像素范围，参数可选，默认500
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer15的发光的光环数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createHaloRingMarkerLayer(geojson, {
      * layerName: 'layer15'
      * });
      * console.log(layer);
     */
    async createHaloRingMarkerLayer(data, options)  {
            options = options || {};
            let type = "haloRingMarker";
        
    
            if (options.height) {
              options.height = this.map.geoLengthToHeight(options.height);
            }
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                text: "ID:${props.index}",
                width: 40,
                markerHeight: 40,
                color1: "#ff0000",
                color2: "#ffff00",
                textFontSize: 15,
                textColor: "#00ffff",
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createHaloRingMarkerLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 获取所有数据图层
     * @returns {Array<Object>} 所有数据图层, 返回的数据图层对象包含
     * @example
      * // 获取所有数据图层的图层名称
      * let layers = this.getAllDataLayers();
      * let layerNames = layers.map(layer => layer.layerName);
      * console.log("所有数据图层名称:", layerNames);
     */
    getAllDataLayers()  {
            let layers = this.context.global.layers;
            if(layers) {
                
                return layers.map(item => {
                    let layerId = item.layerId;
                    let sourceId = item.sourceId;
                    if (item.layer && typeof(item.layer.getLayerId) == "function") {
                        layerId = item.layer.getLayerId();
                        let style = this.map.getStyle();
                        let layer = style.layers.find(layer => layer.id == layerId);
                        if (layer && layer.source) {
                            sourceId = layer.source;
                        }
                    }
                    return {
                        layerId: layerId,
                        sourceId: sourceId,
                        visible: item.visible,
                        type: item.type,
                        layer: item.layer,
                        name: item.name
                    }
                });
            } else {
                return [];
            }
        }
    
    

    /**
     * 导出为PDF文件
     * @param {Object} options 导出PDF的选项，参数可选
     * @param {string} options.mapid 地图id，默认当前地图
     * @param {string} options.version 地图版本，默认当前地图版本
     * @param {boolean} options.exportDrawLayer 是否导出绘图图层，默认false
     * @param {boolean} options.bIncludeOffLayers 是否包含关闭的图层，默认false
     * @param {boolean} options.bSearchableSHX 是否包含可搜索型文字，默认false
     * @param {boolean} options.bSearchableTTF 是否包含可搜索ttf文字，默认false
     * @param {number} options.pageWidth 页面宽度，单位mm，默认210 (A4)
     * @param {number} options.pageHeight 页面高度，单位mm，默认297 (A4)
     * @param {number} options.pageLeftMargin 左页边距，单位mm，默认0
     * @param {number} options.pageRightMargin 右页边距，单位mm，默认0
     * @param {number} options.pageTopMargin 上页边距，单位mm，默认0
     * @param {number} options.pageBottomMargin 下页边距，单位mm，默认0
     * @param {number} options.geomDPI 矢量图形DPI，默认600
     * @param {number} options.colorImagesDPI 图像DPI，默认400
     * @param {boolean} options.isBlackWhiteMode 是否导出为黑白模式，默认false
     * @param {boolean} options.isGrayMode 是否导出为灰色模式，默认false
     * @param {boolean} options.disableOpen 只返回地址，不打开图片，默认false
     * @example
      * // 示例一
      * // 导出为PDF文件
      * await this.exportPdf();
      * // 示例二
      * // 导出为PDF文件, 并包含绘图图层
      * await this.exportPdf({exportDrawLayer: true});
      * // 示例三
      * // 导出为PDF文件, mapid为sys_world, 灰度模式
      * await this.exportPdf({mapid: "sys_world", isGrayMode: true});
     */
    async exportPdf(options)  {
            this.showMessage("正在导出Pdf并打开，请稍候...", "info" );
            options = options || {};
            let mapid = options.mapid || this.map.getService().currentMapParam()?.mapid;
            let version = options.version || this.map.getService().currentMapParam()?.version;
            if (!options.mapid &&options.exportDrawLayer === true) {
              const res = await vjcommon.exportDwg(this.map, this.map.getDrawLayer());
              if (res.error) {
                  this.showMessage(res.error, "error" );
                  return;
              }
              mapid = res.mapid;
              version = res.version;
            }
    
            let param = options; // 不填的话，用默认的
            /*
            param = {
                bIncludeOffLayers: false, // 是否包含关闭的图层
                bSearchableSHX: false, // 可搜索型文字
                bSearchableTTF: false, // 可搜索ttf文字
                pageWidth: 210, // 宽，单位mm
                pageHeight: 297, // 高，单位mm
                pageLeftMargin: 0, // 左页边距 （pageWidth, pageHeight有值时有效）
                pageRightMargin: 0, // 右页边距 （pageWidth, pageHeight有值时有效）
                pageTopMargin: 0, // 上页边距 （pageWidth, pageHeight有值时有效）
                pageBottomMargin: 0, // 下页边距 （pageWidth, pageHeight有值时有效）
                geomDPI: 600, // 矢量dpi
                colorImagesDPI: 400, //图像dpi
                isBlackWhiteMode: false, // 是否导出为黑白模式
                isGrayMode: false, // 是否导出为灰色模式
            }*/
            let svc = this.map.getService();
            const result = await svc.execCommand("exportPdf", param, mapid, version, true);
            if (result.error) {
                this.map.logInfo(result.error, "error");
            } else {
                let pdfUrl = svc.baseUrl() + result.path + "?token=" + svc.accessToken;
                if (!options.disableOpen) {
                    window.open(pdfUrl )
                }
                return pdfUrl;
            }
          }
    

    /**
     * 弹出数据创建修改对话框
     * @param {object} options 数据面板配置，可选
     * @param {any} options.data 要修改的数据内容，可选，为空时表示是创建数据
     * @param {string} options.title 数据面板标题，可选
     * @param {number} options.width 数据面板宽度，可选
     * @param {number} options.height 数据面板高度，可选
     * @param {boolean} options.showForm 是否以表单形式显示，可选，默认false
     * @param {Function} options.applyCallback 点击应用按钮的回调函数，可选
     * @returns {any} 返回用户输入的数据, 如果用户点击取消，则返回undefined
     * @example
      * // 弹出数据创建对话框，在控制台输出数据
      * let res = await this.showDataModifyDlg();
      * if (res) {
      * console.log(res)
      * }
      * // 获取图层名称为layer1的数据图层属性，弹出数据修改对话框对其进行修改，数据为图层属性，数据面板以表单形式显示，点击应用按钮回调以及修改结束后，设置图层数据图层属性为修改后的值。
      * let layerName = 'layer1';
      * let property = this.getDataLayerProperty(layerName);
      * let value = await this.showDataModifyDlg({
      * data: property,
      * title: "图层属性",
      * showForm: true,
      * applyCallback: (val) => {
      * this.setDataLayerProperty(layerName, val);
      * }
      * });
      * if (value) {
      * this.setDataLayerProperty(layerName, value);
      * }
     */
    async showDataModifyDlg(options)  {
            options = options || {};
            if (!options.data) {
                // 如果是创建数据，则以json输入框显示
                options.showForm = false;
            }
            return await this.context.ui.showDataModifyDlg(options.data, options.showForm, options);   
         }
    

    /**
     * 采集一个圆的坐标
     * @param {boolean} isSnap 是否启用捕捉功能, 默认false
     * @returns {Promise<Object | undefined>} 返回圆心坐标和半径，如果取消操作则返回undefined， 返回的对象包含以下属性：
     * @example
      * const circle = await this.actionDrawCircle();
      * if (!circle) return;
      * // 圆心坐标
      * const centerX = circle.center[0];
      * const centerY = circle.center[1];
      * // 半径
      * const radius = circle.radius;
      * console.log(centerX, centerY, radius);
     */
    async actionDrawCircle(isSnap)  {
            this.context.tools.logInfo("请在地图上绘制一个圆")
            let snapObj = {};
            if (isSnap) {
                vjcommon.getMapSnapPoints(this.map, snapObj);
            }
            let drawCircle = await vjmap.Draw.actionDrawCircle(this.map, isSnap ? {
                api: {
                    getSnapFeatures: snapObj
                }
            } : {});
            if (drawCircle.cancel) {
                return;
            }
            let center = this.map.fromLngLat(drawCircle.features[0].properties.center);
            let circlePoint = this.map.fromLngLat(drawCircle.features[0].geometry.coordinates[0][0]);
            let radius = center.distanceTo(circlePoint);
            return {
                center: [center.x, center.y],
                radius
            };
        }
    

    /**
     * 处理图形组合生成新地图
     * @param {object | object[]} options 参数 可选。可以是一个对象或对象数组。如果是对象，属性值是以下参数说明中的。如果是数组，每项的内容和对象的内容是一样的。
     * @param {string} [options.mapid] 地图ID. 可选，为空时则获取当前打开的mapid
     * @param {string} [options.version] 地图版本(为空时采用当前打开的地图版本). 可选
     * @param {[number, number, number, number]} [options.clipbounds] 地图裁剪范围，范围如[x1,y1,x2,y2]，为空的话，表示不裁剪. 可选
     * @param {boolean} [options.selByCrossing] 选择是包含还是相交（默认false表示包含，true相交）. 可选
     * @param {[number, number, number, number]} [options.fourParameter] 四参数(x偏移,y偏移,缩放，旋转弧度)，可选，对坐标最后进行修正. 可选
     * @param {boolean} [options.isInverseFourParamter] 是否用上面的四参数进行反算，默认false. 可选
     * @param {string[]} [options.layers] 要显示的图层名称，为空的时候，表示全部图层. 可选
     * @param {boolean} [options.purge] 生成后清理图形数据以减少dwg文件大小. 可选
     * @param {boolean} [options.isZoomExtents] 是不是创建完缩放至全图 默认false (如果有多个图合并，只需设置第一个图的). 可选
     * @param {string | "*" | "cad2000" | "cad2004" | "cad2007" | "cad2010" | "cad2013" | "cad2018"} [options.cadVersion] 导出的CAD版本号。如果为*表示为当前CAD图的版本. 可选
     * @returns {Promise<string|undefined>} 返回生成的fileid, 如果失败则返回undefined
     * @example
      * //示例一
      * // 处理图形组合生成新地图, 要显示的图层列表为["0"]
      * let res = await this.composeNewMap({
      * layers: ["0"]
      * });
      * if (res) {
      * this.showMessage("生成新地图成功，fileid为" + res, "success");
      * }
      * //示例二
      * // 把mapid为sys_hello和sys_world的地图处理图形组合生成新地图
      * let res2 = await this.composeNewMap([{
      * mapid: "sys_hello"
      * }, {
      * mapid: "sys_world"
      * }]);
      * if (res2) {
      * this.showMessage("生成新地图成功，fileid为" + res2, "success");
      * }
     */
    async composeNewMap(options)  {
            let svc = this.map.getService();
            options = options || {};
            let currentMap = svc.currentMapParam() || {};
            if (Array.isArray(options)) {
               for(let opt of options) {
                if (!opt.mapid) {
                    opt.mapid = currentMap.mapid;
                }
               }
            } else {
                if (!options.mapid) {
                    options.mapid = currentMap.mapid;
                }
            }
            let res = await svc.composeNewMap(options);
            if (res && res.error) {
                this.showMessage(res.error)
                return;
            }
            return res.fileid;
        }
    

    /**
     * 获取临时数据
     * @param {string} name 数据名
     * @returns {any} 数据内容
     * @example
      * // 获取临时数据test的值
      * this.getTempData("test");
     */
    getTempData(name)  {
            return this.context.resultData[name]    
        }
    

    /**
     * 创建动画线数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {boolean} options.isHoverPointer 是否启用鼠标悬浮事件，参数可选
     * @param {boolean} options.isHoverFeatureState 是否启用悬浮实体状态，参数可选
     * @param {string} options.lineColor 线条颜色
     * @param {number} options.lineWidth 线条宽度
     * @param {number} options.lineOpacity 线条透明度，取值范围0-1
     * @param {number} options.lineGapWidth 线条间隙宽度，在线的实际路径之外绘制描边时的内部间隙宽度，单位像素，默认值1
     * @param {number} options.lineOffset 线的偏移量。线性要素正值向右偏移，负值向左偏移；多边形正值向内偏移，负值向外偏移，单位像素
     * @param {number} options.lineBlur 线条模糊度，配合宽度使用，该值应小于线宽度
     * @param {[number,number]} options.lineDasharray 虚线样式，数组格式如[1,1]表示实线、虚线的组合
     * @param {[number,number]} options.lineTranslate 几何体的平移量[x,y]，负数分别表示左和上，单位像素，默认[0,0]
     * @param {string} options.lineCap 线帽形状，可选值："butt"(尖头)、"round"(圆头)、"square"(平头)
     * @param {string} options.lineJoin 拐角形状，可选值："bevel"(平拐)、"round"(圆拐)、"miter"(棱拐)
     * @param {string} options.linePattern 线的拉伸图片类型，必须与地图已加载的图片名称对应，图像宽度必须是2的系数(2、4、8、...、512)
     * @param {string} options.lineGradient 线条渐变
     * @param {string} options.animateImagesType 动画类型，可选值："arrow"(箭头动画), "antpath"(蚂蚁线动画), "customImage"(自定义图片), "imageSprites"(同一个图片精灵动画集)
     * @param {string} options.animateImages_arrowFillColor 箭头填充颜色，当animateImagesType为"arrow"时有效，默认"#22B14C"
     * @param {string} options.animateImages_arrowStrokeColor 箭头边框颜色，当animateImagesType为"arrow"时有效，默认"#fff"
     * @param {number} options.animateImages_arrowStrokeWidth 箭头线宽，当animateImagesType为"arrow"时有效
     * @param {number} options.animateImages_arrowWidth 箭头大小，当animateImagesType为"arrow"时有效
     * @param {string} options.animateImages_fillColor1 蚂蚁线填充颜色1，当animateImagesType为"antpath"时有效
     * @param {string} options.animateImages_fillColor2 蚂蚁线填充颜色2，当animateImagesType为"antpath"时有效
     * @param {string} options.customImageUrl 自定义图片URL或base64，当animateImagesType为"customImage"时有效
     * @param {string} options.imageSpriteUrl 图片动画集URL或base64，当animateImagesType为"imageSprites"时有效
     * @param {number} options.animateImages_spriteWidth 来源图片中精灵图所占宽度，当animateImagesType为"imageSprites"时有效
     * @param {number} options.animateImages_spriteHeight 来源图片中精灵图所占高度，当animateImagesType为"imageSprites"时有效
     * @param {number} options.animateImages_canvasWidth 画布宽度，所有动画类型通用
     * @param {number} options.animateImages_canvasHeight 画布高度，所有动画类型通用
     * @param {number} options.animateImages_frameCount 动画帧数，所有动画类型通用
     * @param {boolean} options.animateImages_directionReverse 动画方向是否反向，所有动画类型通用
     * @param {number} options.speed 动画速度，步长0.1
     * @param {boolean} options.startAutoAnimation 是否开始时自动播放动画
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer3的动画线数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createAnimateLineLayer(geojson, {
      * layerName: 'layer3'
      * });
      * console.log(layer);
     */
    async createAnimateLineLayer(data, options)  {
            options = options || {};
            let type = "animateLine";
    
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                lineColor: '#ff0000',
                lineWidth: 10,
                animateImagesType: "arrow",
                animateImages_arrowFillColor: "#22B14C",
                animateImages_arrowStrokeColor: "#fff",
                animateImages_arrowStrokeWidth: 6,
                animateImages_canvasWidth: 128,
                animateImages_canvasHeight: 32,
                animateImages_arrowWidth: 16,
                animateImages_frameCount: 4,
                animateImages_fillColor1: "#f0fb",
                animateImages_fillColor2: "#0ffb",
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createAnimateLineLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 创建多边形拉伸数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {string} options.fillExtrusionColor 拉伸颜色，参数可选，默认为"#00ffff"
     * @param {number} options.fillExtrusionOpacity 透明度，参数可选，取值范围0-1
     * @param {number} options.fillExtrusionHeight 拉伸高度，单位CAD图制图单位，参数可选，默认为0
     * @param {number} options.fillExtrusionBase 拉伸底部高度，单位CAD图制图单位，参数可选，默认为0
     * @param {string} options.fillExtrusionPattern 拉伸填充图案，参数可选，使用雪碧图中的图像名称，图像宽度必须是2的系数
     * @param {[number,number]} options.fillExtrusionTranslate 平移量，参数可选，格式[x,y]，负数表示左和上，单位像素，默认[0,0]
     * @param {string} options.fillExtrusionTranslateAnchor 平移参考系，参数可选，"map"表示相对地图平移，"viewport"表示相对视窗平移
     * @param {boolean} options.fillExtrusionVerticalGradient 是否应用垂直渐变，参数可选，true时侧面下方位置会略暗
     * @param {boolean} options.isHoverPointer 是否启用鼠标悬浮事件，参数可选
     * @param {boolean} options.isHoverFeatureState 是否启用悬浮实体状态，参数可选
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer12的多边形拉伸数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createFillExtrusionLayer(geojson, {
      * layerName: 'layer12'
      * });
      * console.log(layer);
     */
    async createFillExtrusionLayer(data, options)  {
            options = options || {};
            let type = "fillExtrusion";
        
    
            if(options.fillExtrusionHeight) {
              options.fillExtrusionHeight = this.map.geoLengthToHeight(options.fillExtrusionHeight);
            }
            if(options.fillExtrusionBase) {
              options.fillExtrusionBase = this.map.geoLengthToHeight(options.fillExtrusionBase);
            }
    
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                fillExtrusionColor: "#00ffff",
                fillExtrusionHeight: this.map.geoLengthToHeight(this.map.getGeoBounds().width() / 6),
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createFillExtrusionLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 输出内容到会话窗口
     * @param {string} content 输出内容, 支持markdown格式
     * @example
      * // 将"123"输出至会话窗口
      * this.chatLog("123");
     */
    chatLog(content)  {
            return this.context.ui.chatLog(content) 
        }
    

    /**
     * 创建绘制数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {boolean} options.entColorToHtmlColor 颜色转化为html颜色，参数可选
     * @param {boolean} options.isHoverPointer 是否启用鼠标悬浮事件，参数可选
     * @param {boolean} options.isHoverFeatureState 是否启用悬浮实体状态，参数可选
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer10的绘制数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createDrawDataLayer(geojson, {
      * layerName: 'layer10',
      * });
      * console.log(layer);
     */
    async createDrawDataLayer(data, options)  {
            options = options || {};
            let type = "draw";
      
            if  (options.entColorToHtmlColor === undefined) {
              for(let f of data.features) {
                if (f.properties && typeof f.properties.color == "number") {
                    if (f.properties.color < 0) {
                        options.entColorToHtmlColor = true;
                        break;
                      }
                  }
              }
            }
    
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createDrawDataLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 导出图纸布局空间
     * @param {object} options 参数
     * @param {string} options.mapid 地图ID. 可选，为空时则使用当前打开的地图
     * @param {string} [options.version] 地图版本(为空时采用当前打开的地图版本)
     * @param {string} [options.layoutIndex] 布局索引，layoutIndex和layoutName只要输入一个就行，layoutIndex优先。如果都为空，则使用dwg当前的布局空间
     * @param {string} [options.layoutName] 布局名称，layoutIndex和layoutName只要输入一个就行，layoutIndex优先。如果都为空，则使用dwg当前的布局空间
     * @returns {Promise<string>} 返回导出的fileid, 如果失败则返回空字符串
     * @example
      * // 导出当前图纸布局空间，布局索引为0
      * let fileid = await this.exportLayout({
      * layoutIndex: "0"
      * });
      * console.log(fileid);
     */
    async exportLayout(options)  {
           let svc = this.map.getService();
           options = options || {};
           options.mapid = options.mapid || svc.currentMapParam().mapid;
           let res = await svc.cmdExportLayout(options);
           if (res && res.fileid) {
               return res.fileid;
           }
           return "";
       }
    

    /**
     * 增加一个功能菜单及实现功能
     * @param {string} name 菜单名
     * @param {Function} func 要实现的功能函数
     * @example
      * // 增加一个功能菜单,菜单名为全图显示，功能为将地图缩放至全图显示
      * this.addMenuItem("全图显示", () => {
      * //缩放至全图显示
      * this.fitMapBounds();
      * })
     */
    addMenuItem(name, func)  {
           // 默认情况下增加的功能菜单，地图右键菜单中进行显示
           this.context.ui.addMenuItem(name, func);   
           // 添加撤销记录
           this.context.ui.addUndoRecord(() => {
                this.removeMenuItem(name);
            });
        }
    

    /**
     * 查询图中数据
     * @param {string} sql sql语句，数据库表名称为geom, 表结构如下
     * @returns {Object} 返回标准的GeoJson格式的FeatureCollection对象, 包含features属性, 每个feature包含geometry和properties属性,properties属性中包含如下字段:
     * @example
      * // 示例一
      * // 查询图中类型为 line 且图层索引为 2 的记录
      * let result1 = await  this.queryMapData("SELECT * FROM geom WHERE name = 'AcDbLine' AND layerIndex = 2");
      * if (result1.features.length > 0) {
      * let feature = result1.features[0];
      * // 获取线宽
      * console.log(feature.properties.lineWidth)
      * // 获取所有点的坐标，在坐标处绘制一个Marker
      * const addMarker = (co) => {
      * // co的格式为[x, y]
      * this.addMarker(co)
      * }
      * const processGeoJSONFeature = (geometry) => {
      * if (geometry.type === "Point") {
      * let co = geometry.coordinates;
      * addMarker(co)
      * } else if (geometry.type === "MultiPoint") {
      * geometry.coordinates.forEach(co => addMarker(co))
      * } else if (geometry.type === "LineString") {
      * geometry.coordinates.forEach(co => addMarker(co))
      * } else if (geometry.type === "MultiLineString") {
      * geometry.coordinates.forEach(line => {
      * line.forEach(co => addMarker(co))
      * })
      * } else if (geometry.type === "Polygon") {
      * geometry.coordinates.forEach(line => {
      * line.forEach(co => addMarker(co))
      * })
      * } else if (geometry.type === "MultiPolygon") {
      * geometry.coordinates.forEach(polygon => {
      * polygon.forEach(line => {
      * line.forEach(co => addMarker(co))
      * })
      * })
      * }
      * }
      * if (feature.geometry.type === "GeometryCollection") {
      * feature.geometry.geometries.forEach(ft => {
      * processGeoJSONFeature(ft)
      * })
      * } else {
      * processGeoJSONFeature(feature.geometry)
      * }
      * }
      * // 示例二
      * // 查询图中全部文本,坐标类型为几何坐标
      * let result2 = await this.queryMapData("SELECT * FROM geom WHERE (name = 'AcDbText' or name = 'AcDbMText' or name = 'AcDbAttributeDefinition' or name = 'AcDbAttribute')  and coordType = true");
      * if (result2.features.length > 0) {
      * result2.features.forEach(feature => {
      * // 获取文本内容
      * console.log(feature.properties.content)
      * });
      * }
      * // 查询图中包含矩形范围为[10, 10, 20, 20]的全部线,坐标类型为位置坐标
      * let result3 = await this.queryMapData("SELECT * FROM geom WHERE  (name = 'AcDbLine' or name = 'AcDbPolyline' or name = 'AcDb2dPolyline' or name = 'AcDb3dPolyline') and bounds ='[10, 10, 20, 20]' and isContains = true  and coordType = false");
      * // 获取记录总数
      * console.log(result3.features.length)
     */
    queryMapData(sql)  {
            return this.context.tools.queryMapData(this.context, sql)
        }
    

    /**
     * 自动提取子图范围
     * @returns {Promise<Object>} 如果失败则返回undefined, 否则返回提取出的子图范围的GeoJSON数据和子图范围的bounds数组， 返回格式如下：
     * @example
      * // 自动提取子图范围
      * let res = await this.findChildMapRects();
      * console.log(res);
     */
    async findChildMapRects(options)  {
            options = options || {};
           let svc = this.map.getService();
           options = {
            mapid: options.mapid || svc.currentMapParam().mapid,
            bounds: "",
            condition: "",
            debugInfo: [],
            digit: 4,
            tol: 0,
            tableEdgeMinPoint: 12,
            tableTextMinCount: 4,
            cellMaxArea: 90,
            cellEmptyRatio: 90,
            unLinkLineRatio: 20,
            tableMaxCellCount: 100000,
            noHvLineSegCount: 0,
            seachTableMost: false,
            version:  svc.currentMapParam()?.version,
            layer: svc.currentMapParam()?.layer,
            // 如果直接内存打开的话，用表达式查询，如果几何渲染打开，用空间查询
            geom: svc.currentMapParam()?.mapopenway != vjmap.MapOpenWay.Memory,
            ...options,
           };
           if (options.isSelectBounds) {
                // 如果是手动，先在图中选择一个区域
                if (this.map.logInfo) this.map.logInfo("请先选择表格所在的矩形区域，选择区域的范围需要覆盖表格内容", "success", 10000);
                 // 先在地图上绘制一个矩形,做为裁剪区域
                let snapObj = {}; // 设置的捕捉的实体
                vjcommon.getMapSnapPoints(this.map, snapObj);
                let res = await vjmap.Draw.actionDrawRectangle(this.map, {
                    api: {
                        getSnapFeatures: snapObj //要捕捉的数据项在后面，通过属性features赋值
                    },
                    contextMenu: (e) => {
                        // 给地图发送Enter键消息即可取消，模拟按Enter键
                        this.map.fire("keyup", {keyCode:13})
                    }
                });
                if (res.cancel) {
                    return // 取消操作
                }
                // 获取所绘制的坐标
                let rectCoord = this.map.fromLngLat(res.features[0].geometry.coordinates[0]);
                // 取左下角和右下角两个点
                let  bounds = [rectCoord[0].x, rectCoord[0].y, rectCoord[2].x, rectCoord[2].y];
                options.bounds = bounds.join(',');
           }
           if (Array.isArray(options.bounds)) {
            options.bounds = JSON.stringify(options.bounds);
           }
           if (options.showSettingForm === true) {
            options = await this.showDataModifyDlg({
                data: options,
                title: "自动提取表格设置",
                showForm: true
            })
            if (!options) return;
           }
           // 只获取子图范围
           options.findChildMapRects = true;
           let result = await svc.cmdExtractTale(options);
           if (result && result.error) {
                if (this.map.logInfo) this.map.logInfo(result.error, "error", 10000);
                return;
           }
           if (!result.childMapRects) return;
           let geoDatas = {
                "type": "FeatureCollection",
                "features": []
            }
           let childMapBounds = [];
           for (let i = 0; i < result.childMapRects.length; i++) {
               let bounds = vjmap.GeoBounds.fromArray(result.childMapRects[i].bounds);
               childMapBounds.push(bounds.toString())
               let points = bounds.toPointArray();
               points.push(points[0]); // 闭合
               geoDatas.features.push({
                   type: "Feature",
                   geometry: {
                       type: "Polygon",
                       coordinates: [points]
                   },
                   properties: {
                       color: vjmap.randomColor(),
                       index: i,
                       data: result.childMapRects[i]
                   }
               });
           }
           return {
            geojson: geoDatas,
            childMapBounds: childMapBounds
           }
    
        }
    

    /**
     * 根据给定的圆心和半径直接绘制一个圆
     * @param {[number, number]} center 圆心坐标[x,y]，参数必填
     * @param {number} radius 半径，参数必填
     * @param {object} options 配置参数, 参数可选
     * @param {string} options.color 颜色，格式为#RRGGBB，参数可选
     * @param {boolean} options.isFill 是否填充，默认false，参数可选
     * @param {number} options.opacity 透明度，默认1，参数可选
     * @param {number} options.data 数据值，参数可选
     * @returns {Object} 返回绘制的图元对象，返回的对象包含以下属性：
     * @example
      * // 创建一个地图范围内随机位置的圆， 圆的半径为地图宽度的1/40, 颜色随机，填充，透明度0.5
      * let points = this.randomPointInMapBounds(1);
      * let rect = this.getMapGeoBounds(); // 获取地图范围
      * let width = (rect[2] - rect[0]) / 40; // 矩形宽度为地图宽度的1/00
      * const circle = this.createCircle(points[0], width, {
      * color: vjmap.randomColor(),
      * isFill: true,
      * opacity: 0.5
      * });
      * console.log(`圆 ID:`, circle.id);
      * console.log(`圆 圆心:`, circle.center);
      * console.log(`圆 半径:`, circle.radius);
     */
    createCircle(center, radius, options)  {
            let draw = this.map.getDrawLayer();
            let coords = vjmap.getCirclePolygonCoordinates(center, radius);
            coords = coords.map(c => this.map.toLngLat(c));
            let id = vjmap.RandomID(10);
            draw.add({
                id,
                type: 'Feature',
                geometry: {
                    type: options.isFill ? 'Polygon' : 'LineString',
                    coordinates: options.isFill ? [coords] : coords
                },
                properties: {
                    color: options.color,
                    opacity: options.opacity,
                    data: options.data
                }
            });
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.map.getDrawLayer().delete(id);
            });
            return {
                id,
                center,
                radius
            };
        }
    

    /**
     * 将地图经纬度坐标转换为CAD图几何坐标
     * @returns {[number, number]} 返回包含两个数字的数组[x,y]，表示CAD图几何坐标
     * @example
      * // 将地图经纬度坐标[10, 20]转换为CAD图几何坐标
      * let point = this.lngLatToGeoPoint([10, 20]);
      * let x = point[0];
      * let y = point[1];
      * console.log(x, y);
     */
    lngLatToGeoPoint(lngLat)  {
            let geoPoint = this.map.fromLngLat(lngLat);
            return [geoPoint.x, geoPoint.y];
        }
    

    /**
     * 根据范围进行闪烁
     * @param {[number,number,number,number]} bounds 范围
     * @param {Object} options 配置参数, 参数可选
     * @param {string} options.color 颜色，格式为#RRGGBB，默认为#FFFF00参数可选
     * @param {number} options.width 线宽，默认3，参数可选
     * @param {number} options.duration 动画持续时间，默认1000，参数可选
     * @example
      * this.flashBounds([0,0,10,10]);
     */
    flashBounds(bounds, options)  {
            options = options || {};
            return new Promise((resolve => {
               if (Array.isArray(bounds) && bounds.length === 4 && typeof bounds[0] === "number") {
                    bounds = vjmap.GeoBounds.fromArray(bounds);
                } else {
                    bounds = vjmap.GeoBounds.fromDataExtent(bounds);
                }
                const routePath = [];
                routePath.push(bounds.min);
                routePath.push(vjmap.geoPoint([bounds.max.x, bounds.min.y]));
                routePath.push(bounds.max);
                routePath.push(vjmap.geoPoint([bounds.min.x, bounds.max.y]));
                routePath.push(bounds.min);
                let geoLineDatas = [];
                geoLineDatas.push({
                    points: this.map.toLngLat(routePath),
                    properties: {
                        opacity: 1.0
                    }
                })
    
                let polylines = new vjmap.Polyline({
                    data: geoLineDatas,
                    lineColor: options.color || 'yellow',
                    lineWidth: options.width || 3,
                    lineOpacity: ['get', 'opacity'],
                    isHoverPointer: false,
                    isHoverFeatureState: false
                });
                polylines.addTo(this.map);
    
                vjmap.createAnimation({
                    from: 1,
                    to: 10,
                    duration: options.duration || 1000,
                    onUpdate: (e) => {
                        const data = polylines.getData();
                        data.features[0].properties.opacity = parseInt(e) % 2 ? 1.0 : 0 ;
                        polylines.setData(data);
                    },
                    onStop: () => {
                        polylines.remove()
                        resolve({})
                    },
                    onComplete: () => {
                        polylines.remove()
                        resolve({})
                    }
                })
            }))
        }
    

    /**
     * /**
     * @returns {Object} 返回一个对象，包含三个布尔值，分别表示Shift、Ctrl、Alt键是否被按下
     * @example
      * // 示例一
      * // 当前Shift键是否按下
      * let key = this.isShiftCtrlAltKeyPressed();
      * if (key.shift) {
      * console.log("Shift键被按下");
      * }
      * // 示例二
      * // 监听地图单击事件，如果Ctrl键被按下，获取点击的CAD图几何坐标位置，根据位置弹出数据信息框，标题为"点击位置"，以表单形式展示
      * this.listenMapEvent("click", async ev => {
      * // 获取当前是否按下Ctrl键
      * let key = this.isShiftCtrlAltKeyPressed();
      * if (key.ctrl) {
      * if (ev && ev.point) {
      * // 获取当前地图的像素坐标，转换为CAD图几何坐标
      * const point =  this.pixelPointToGeoPoint(ev.point);
      * // 根据位置弹出数据信息框，标题为"点击位置"，以表单形式展示
      * await this.showDataInfoDialog(point, true, "点击位置");
      * }
      * }
      * }, true);
     */
    isShiftCtrlAltKeyPressed()  {
            let shift = false, ctrl = false, alt = false;
            if (this.context.global.__listenKeyEvent) {
                shift = this.context.global.__listenKeyEvent.isShiftKey();
                ctrl = this.context.global.__listenKeyEvent.isCtrlKey();
                alt = this.context.global.__listenKeyEvent.isAltKey();
            }
            return {
                shift: shift,
                ctrl: ctrl,
                alt: alt,
            };
        }
    

    /**
     * 允许或禁止地图旋转、倾斜、滚轮缩放、平移、双击缩放
     * @param {boolean} options.rotation 是否允许旋转(参数可选)
     * @param {boolean} options.pitch 是否允许倾斜(参数可选)
     * @param {boolean} options.zoom 是否允许滚轮缩放(参数可选)
     * @param {boolean} options.pan 是否允许平移(参数可选)
     * @param {boolean} options.dblclickZoom 是否允许双击缩放(参数可选)
     * @example
      * // 禁止地图旋转、倾斜、双击缩放, 允许滚轮缩放
      * this.setMapInteraction({rotation: false, pitch: false, zoom: true, dblclickZoom: false});
     */
    setMapInteraction(options)  {
            options = options || {};
            if (options.rotation === true) {
                this.map.dragRotate.enable();
                this.map.touchZoomRotate.enable(); 
                this.map.keyboard.enableRotation();
            } else if (options.rotation === false) {
                this.map.dragRotate.disable();
                this.map.touchZoomRotate.disable();
                this.map.keyboard.disable();
            }
    
            if (options.pitch === true) {
                this.map.setMinPitch(this.map.getPitch());
                this.map.setMaxPitch(this.map.getPitch());
            } else if (options.pitch === false) {
                this.map.setMinPitch(0);
                this.map.setMaxPitch(90);
            }
    
            if (options.zoom === true) {
                this.map.scrollZoom.enable();
                this.map.touchZoomRotate.enable();
            } else if (options.rotation === false) {
                this.map.scrollZoom.disable();
                this.map.touchZoomRotate.disable();
            }
    
            if (options.dblclickZoom === true) {
                this.map.doubleClickZoom.enable();
            } else if (options.dblclickZoom === false) {
                this.map.doubleClickZoom.disable();
            }
    
            if (options.pan === true) {
                this.map.dragPan.enable();
                this.map.touchZoomRotate.enable();
            } else if (options.pan === false) {
                this.map.dragPan.disable();
                this.map.touchZoomRotate.disable();
            }
        }
    

    /**
     * 创建div覆盖物图层
     * @param {[number,number,number,number] | [[number,number],[number,number]] | [[number,number],[number,number],[number,number],[number,number]]} data 范围坐标，格式为左上角和右下角坐标[x1,y1,x2,y2] 或 [[x1,y1],[x2,y2]]，或四个角坐标[[x1,y1],[x2,y2],[x3,y3],[x4,y4]]
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {number} options.width div元素的像素宽度
     * @param {number} options.height div元素的像素高度
     * @param {string} options.customImage 图片名称
     * @param {string} options.customHtml 自定义Html元素的JavaScript代码，返回创建的DOM元素
     * @param {number} options.minZoom 图层显示的最小缩放级别，范围0-24
     * @param {number} options.maxZoom 图层显示的最大缩放级别，范围0-24
     * @param {number} options.maxPitch 图层显示的最大倾斜角度，范围0-90
     * @param {boolean} options.updateDivSize 是否自动更新div大小，对SVG放大特别有用
     * @param {number} options.maxDivSize div元素允许的最大像素尺寸，超过此值将使用像素放大
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 采集一个矩形坐标，根据此矩形范围坐标，创建图层名称为layer30的div覆盖物图层，图片名称为picture1
      * const rect = await this.actionDrawRectangle();
      * if (!rect) return;
      * let layer = await this.createDivOverlayLayer(rect,{
      * layerName: 'layer30',
      * customImage: 'picture1'
      * });
      * console.log(layer);
     */
    async createDivOverlayLayer(data, options)  {
            options = options || {};
            let type = "divOverlay";
    
            let randomId = vjmap.RandomID();
            let layerId =  `layer_${type}_${randomId}`;
    
            options.bounds = data;
            if (options.customImage) {
                let image = this.context.ui.getCustomImages(options.customImage);
                if (image) {
                    options.customImage = image.value;
                    options.width = image.width;
                    options.height = image.height;
                }
            }
            if (Array.isArray(options.bounds) && options.bounds.length == 4 && typeof options.bounds[0] == 'number') {
                options.bounds = vjmap.GeoBounds.fromArray(options.bounds).toPointArray();
            }
            let layerOptions = {
                layerId,
                ...options,
                type: type
            }
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId: undefined, layerName, data: undefined, options, layer, visible: true, create: this.createDivOverlayLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId: undefined,
                layerId,
                layerName
            };
        }
    

    /**
     * 手动交互式绘制一个圆
     * @param {object} options 配置参数, 参数可选
     * @param {boolean} options.isSnap 是否启用捕捉功能, 参数可选，默认false
     * @param {string} options.color 颜色，格式为#RRGGBB，参数可选
     * @param {boolean} options.isFill 是否填充，默认false，参数可选
     * @param {number} options.opacity 透明度，默认1，参数可选
     * @param {number} options.data 数据值，参数可选
     * @returns {Promise<Object | undefined>} 返回绘制的图元对象，如果取消操作则返回undefined，返回的对象包含以下属性：
     * @example
      * // 手动绘制一个圆
      * const circle = await this.drawCircle({
      * isSnap: true,
      * color: "#FF0000",
      * isFill: true,
      * opacity: 0.8
      * });
      * if (!circle) return;
      * console.log("圆ID:", circle.id);
      * console.log("圆心:", circle.center);
      * console.log("半径:", circle.radius);
     */
    async drawCircle(options)  {
            let circle = await this.actionDrawCircle(options.isSnap);
            if (!circle) return;
            return this.createCircle(circle.center, circle.radius, options);
        }
    

    /**
     * 导出当前图形指定范围的图片
     * @param {[number, number, number, number]} bounds 范围，格式为[minx, miny, maxx, maxy]
     * @param {object} options 导出图片的选项，参数可选
     * @param {number} options.pictureWidth 图片宽度，单位像素, 默认1024
     * @param {number} options.scale 缩放比例，默认1.01
     * @param {boolean} options.transparent 是否透明背景，默认false
     * @param {string} options.backgroundColor 背景颜色，默认'rgb(0,0,0)'
     * @param {boolean} options.disableOpen 只返回地址，不打开图片，默认false
     * @example
      * // 把当前地图范围[100, 200, 300, 400]导出为png图片
      * this.exportPng([100, 200, 300, 400]);
     */
    exportPng(bounds, options)  {
            options = options || {};
            if (Array.isArray(bounds) && bounds.length === 4 && typeof bounds[0] === 'number') {
                bounds = vjmap.GeoBounds.fromArray(bounds);
            } else {
               bounds = vjmap.GeoBounds.fromDataExtent(bounds);
            }
            bounds = bounds.scale(options.scale || 1.01); // 稍大点，要不边框线有可能刚好看不见了
            let pictureWidth = options.pictureWidth || 1024;
            let svc = this.map.getService();
            let wmsUrl = svc.wmsTileUrl({
                width: pictureWidth,
                height: Math.round(pictureWidth * bounds.height() / bounds.width()),
                srs: "",
                bbox: bounds.toString(),
                transparent: options.transparent || false,
                backgroundColor: options.backgroundColor || 'rgb(0,0,0)'
            });
            if (!options.disableOpen) {
              window.open(wmsUrl);
            }
            return wmsUrl;
        }
    

    /**
     * 移除小地图(鹰眼)控件
     * @example
      * // 移除小地图(鹰眼)控件
      * this.removeMiniMapControl();
     */
    removeMiniMapControl()  {
            this.context.global.controls = this.context.global.controls || {};
            let options;
            if (this.context.global.controls.miniMapControl) {
                options = {...this.context.global.controls.miniMapControl.options}
                this.map.removeControl(this.context.global.controls.miniMapControl.control);
                delete this.context.global.controls.miniMapControl;
            }
            // 增加撤销记录
            this.context.ui.addUndoRecord(() => {
                if (options) {
                    this.addMiniMapControl(options);
                }
            }); 
        }
    

    /**
     * 删除所有功能菜单
     * @example
      * // 删除所有功能菜单
      * this.deleteMenuItems()
     */
    deleteMenuItems()  {
            this.context.ui.deleteMenuItems();   
         }
    
    

    /**
     * 获取地图的倾斜角度
     * @returns {number} 当前地图的倾斜角度
     */
    getPitch() {  this.map.getPitch() }
    

    /**
     * 创建或更新地图，通过图的fileid、url地址或手动选择本地图纸创建或更新地图
     * @param {object} options 参数 可选
     * @param {string} options.fileid 图纸的fileid, 为空时则手动选择本地图纸, 可以是一个url地址或文件id
     * @param {string} options.mapid 地图id 可选，为空时则自动生成mapid
     * @param {"Memory" | "GeomRaster" | "GeomVector"} options.openway 打开方式 可选，Memory: 内存, GeomRaster: 栅格, GeomVector: 矢量, 默认栅格
     * @param {boolean} options.lightTheme 是否使用浅色主题 可选，为空时则使用深色主题
     * @param {boolean} options.isFullExtent 是否全图范围显示 可选，默认false
     * @returns {Object | undefined} 返回对象，包含地图id和版本和fileid {mapid: string, version: string, fileid: string}, 如果取消则返回undefined
     * @example
      * // 手动选择本地图纸创建地图
      * await this.createOrUpdateMap();
      * // 通过url地址创建地图,url地址为https://vjmap.com/static/assets/data/gym.dwg, mapid为newgym,以矢量方式打开, 使用深色主题
      * await this.createOrUpdateMap({
      * fileid: "https://vjmap.com/static/assets/data/gym.dwg",
      * mapid: "newgym",
      * openway: "GeomVector",
      * lightTheme: false
      * });
     */
    async createOrUpdateMap(options)  {
            options = options || {};
            let fileid = options.fileid || "";
            let svc = this.map.getService();
            let uploadname;
            if (!fileid) {
                let formats = await svc.cmdGetSupportFormat();
                let acceptFormat = '';
                if (formats.cad) {
                    acceptFormat = formats.cad.split(";").map(f => '.' + f).join(",")
                }
                if (formats.image) {
                    acceptFormat += "," + formats.image.split(";").map(f => '.' + f).join(",")
                }
                const selectFile = () => {
                    return new Promise((resolve, reject) => {
                        // 创建一个隐藏的文件输入元素
                        const input = document.createElement('input');
                        input.type = 'file';
                    
                        // 设置允许选择的文件类型
                        input.accept = acceptFormat;
                    
                        // 添加事件监听器，当用户选择文件时触发
                        input.addEventListener('change', () => {
                        if (input.files && input.files.length > 0) {
                            resolve(input.files[0]); // 返回选中的第一个文件对象
                        } else {
                            reject(new Error('No file selected'));
                        }
                        });
                    
                        // 触发文件选择窗口
                        input.click();
                    });
                }
                try {
                    // 选择文件 
                    let file = await selectFile();
                    // 上传文件
                    let res = await svc.uploadMap(file);
                    if (res && res.error) {
                        this.showMessage(res.error)
                        return;
                    }
                    fileid = res.fileid;
                    uploadname = res.uploadname;
                }
                catch(err) {
                    this.showMessage(err)
                    return;
                }
            }
            let mapid = options.mapid || "";
            if (!mapid) {
                mapid = fileid;
                // 如果是url地址
                if (fileid.toLowerCase().endsWith('.dwg')) {
                    mapid = svc.strMd5(fileid).substring(0, 16);
                }
            }
            let mapopenway =  options.openway === "Memory" ? vjmap.MapOpenWay.Memory : vjmap.MapOpenWay.GeomRender;
            let isVectorStyle = options.openway === "GeomVector" ? true : false;
            let lightTheme = options.lightTheme ?? (this.map.getService().currentMapParam()?.darkMode === true ? false : true)
          
            this.showMessage("正在打开地图中，请稍等...", "success")
            // 打开地图
            let result = await svc.updateMap({
                mapid: mapid,
                fileid: fileid,
                mapopenway: mapopenway,
                uploadname: uploadname,
                style: lightTheme ? vjmap.openMapLightStyle() : vjmap.openMapDarkStyle(),
            });
    
            // 切换成新图
            let res = await this.map.switchMap({
                mapid: result.mapid,
                version: result.version,
                mapopenway: mapopenway,
                style: lightTheme ? vjmap.openMapLightStyle() : vjmap.openMapDarkStyle()
            }, false, isVectorStyle, true, true);
            if (options.isFullExtent !== true) {
                // 用默认的中心点和缩放级别参数
                let svc = this.map.getService();
                let param = svc.currentMapParam();
                if (param && param.view) {
                    if (param.view.center) {
                        this.map.setCenter(param.view.center);
                    }
                    if (param.view.zoom) {
                        this.map.setZoom(param.view.zoom);
                    }
                    if (param.view.bearing) {
                        this.map.setBearing(param.view.bearing);
                    }
                }
            }
            if (res && res.error) {
                this.showMessage(res.error)
                return;
            }
            // 打开新的地图了，需要清空历史记录
            this.context.ui.clearUndoRecord();
            return {
                mapid: result.mapid,
                fileid: fileid,
                version: result.version
            };
    
        }
    

    /**
     * 删除CAD图实体对象
     * @param {boolean} rectSelect 是否矩形选择, false表示点选，true表示矩形选择, 参数可选, 默认false
     * @example
      * // 示例一
      * // 点选删除CAD图实体对象
      * this.deleteCadEntity(false);
      * // 示例二
      * // 矩形选择删除CAD图实体对象
      * this.deleteCadEntity(true);
     */
    async deleteCadEntity(rectSelect)  {
            let draw = this.map.getDrawLayer();
            if (!this.context.global.updateMapStyleObj) {
                this.context.global.updateMapStyleObj = vjcommon.createUpdateMapStyleObj(this.map);
            }
            const updateMapStyleObj = this.context.global.updateMapStyleObj;
            // 保存旧的数据，用于撤销记录
            let oldHideObjectIds = new Set(updateMapStyleObj.hideObjectIds);
            const deleteEntitys = vjcommon.getTrackFeatureProperty(draw, "deleteEntitys") || [];
            const hideEntityObjectIds =
              vjcommon.getTrackFeatureProperty(draw, "hideEntityObjectIds") || [];
            const hideEntityIds = vjcommon.getTrackFeatureProperty(draw, "hideEntityIds") || [];
    
            await vjcommon.deleteCadEntity(this.map, draw, updateMapStyleObj,  this.map.logInfo.bind(this.map), async (content) => {
                return await this.context.ui.confirm(content, "提示", "warning");
              }, rectSelect);
    
            let newHideObjectIds = new Set(updateMapStyleObj.hideObjectIds);
            let diffObjectIds = newHideObjectIds.difference(oldHideObjectIds);
            // 添加撤销记录
            this.context.ui.addUndoRecord(async () => {
                await updateMapStyleObj.removeHideObjectIds(diffObjectIds);
                vjcommon.setTrackFeatureProperty(draw, {
                  deleteEntitys: Array.from(new Set(deleteEntitys)),
                  hideEntityObjectIds: Array.from(new Set(hideEntityObjectIds)),
                  hideEntityIds: Array.from(new Set(hideEntityIds)),
                });
            });
        }
    

    /**
     * 复制CAD图实体对象
     * @param {boolean} rectSelect 是否矩形选择, false表示点选，true表示矩形选择, 参数可选, 默认false
     * @example
      * // 示例一
      * // 通过点选复制CAD图实体对象
      * await this.copyCadEntity(false);
      * // 示例二
      * // 通过矩形选择复制CAD图实体对象
      * await this.copyCadEntity(true);
     */
    async copyCadEntity(rectSelect)  {
            let draw = this.map.getDrawLayer();
            if (!this.context.global.updateMapStyleObj) {
                this.context.global.updateMapStyleObj = vjcommon.createUpdateMapStyleObj(this.map);
            }
            const updateMapStyleObj = this.context.global.updateMapStyleObj;
            // 保存旧的数据，用于撤销记录
            let features = draw.getAll().features;
            let oldFeatureLength = features ? features.length : 0;
    
            await vjcommon.copyCadEntity(this.map, draw, updateMapStyleObj,  this.map.logInfo.bind(this.map), async (content) => {
                return await this.context.ui.confirm(content, "提示", "warning");
              }, rectSelect, (content) => {
                return this.context.ui.prompt("请输入内容", "提示", content);
              });
    
           
            let addFeatureIds = [];
            let newFeatures = draw.getAll().features;
            let newFeatureLength = newFeatures ? newFeatures.length : 0;
            for (let i = oldFeatureLength; i < newFeatureLength; i++) {
                addFeatureIds.push(newFeatures[i].id);
            }
            // 添加撤销记录
            this.context.ui.addUndoRecord(async () => {
                this.map.getDrawLayer().delete(addFeatureIds);
            });
        }
    

    /**
     * 采集一条线的坐标
     * @param {boolean} isSnap 是否启用捕捉功能, 默认false
     * @returns {Promise<number[][] | undefined>} 返回坐标点数组[[x1,y1],[x2,y2],...]，如果取消操作则返回undefined
     * @example
      * const line = await this.actionDrawLineString();
      * if (!line) return;
      * // 输出线的所有点坐标
      * for (let i = 0; i < line.length; i++) {
      * let x = line[i][0];
      * let y = line[i][1];
      * console.log(x, y);
      * }
     */
    async actionDrawLineString(isSnap)  {
            this.context.tools.logInfo("请在地图上绘制一条线")
            let snapObj = {};
            if (isSnap) {
                vjcommon.getMapSnapPoints(this.map, snapObj);
            }
            let drawLine = await vjmap.Draw.actionDrawLineSting(this.map, isSnap ? {
                api: {
                    getSnapFeatures: snapObj
                }
            } : {});
            if (drawLine.cancel) {
                return;
            }
            let coords = drawLine.features[0].geometry.coordinates;
            return coords.map(c => {
                let p = this.map.fromLngLat(c);
                return [p.x, p.y];
            });
        }
    

    /**
     * 手动交互式绘制一个椭圆或椭圆弧
     * @param {object} options 配置参数, 参数可选
     * @param {boolean} options.isSnap 是否启用捕捉功能, 参数可选，默认false
     * @param {string} options.color 颜色，格式为#RRGGBB，参数可选
     * @param {string} options.borderColor 边框颜色，格式为#RRGGBB，参数可选
     * @param {boolean} options.arc 是否绘制椭圆弧，默认false，参数可选
     * @param {boolean} options.isFill 是否填充，默认false，参数可选
     * @param {number} options.width 线宽，默认1，参数可选
     * @param {number} options.opacity 透明度，默认1，参数可选
     * @param {number} options.borderOpacity 边框透明度，默认1，参数可选
     * @param {number} options.data 数据值，参数可选
     * @returns {Promise<Object | undefined>} 返回绘制的图元对象，如果取消操作则返回undefined，返回的对象包含以下属性：
     * @example
      * // 示例一
      * // 手动绘制一个填充椭圆，启用捕捉，颜色为红色，透明度0.6，线宽2，边框颜色为黄色，透明度0.8
      * const ellipseFill = await this.drawEllipse({
      * isSnap: true,
      * color: "#FF0000",
      * isFill: true,
      * opacity: 0.6,
      * width: 2,
      * borderColor: "#FFFF00",
      * borderOpacity: 0.8
      * });
      * if (ellipseFill && ellipseFill.id) {
      * console.log("椭圆ID:", ellipseFill.id);
      * }
      * // 示例二
      * // 手动绘制一个椭圆，颜色为红色，线宽3
      * const ellipse = await this.drawEllipse({
      * color: "#FF0000",
      * isFill: false,
      * width: 3,
      * });
      * if (ellipse && ellipse.id) {
      * console.log("椭圆ID:", ellipse.id);
      * }
      * // 示例三
      * // 手动绘制一个椭圆弧，颜色为黄色，线宽2
      * const ellipseArc = await this.drawEllipse({
      * color: "#FFFF00",
      * isFill: false,
      * arc: true,
      * width: 2,
      * });
      * if (ellipseArc && ellipseArc.id) {
      * console.log("椭圆弧ID:", ellipseArc.id);
      * }
     */
    async drawEllipse(options)  {
            options = options || {};
            let draw = this.map.getDrawLayer();
            let snapObj = {};
            if (options.isSnap) {
                vjcommon.getMapSnapPoints(this.map, snapObj);
            }
            let drawOptions = options.isSnap ? {
                api: {
                    getSnapFeatures: snapObj
                }
            } : {};
            let featureId; 
            if (options.isFill) {
                if (options.arc) {
                    featureId = await vjcommon.drawEllipseFillArc(this.map, draw, drawOptions, {
                        "color": options.color,// 颜色
                        "opacity": options.opacity, // 透明度
                        "outlineColor": options.borderColor || options.color,// 边框颜色
                        "line_width": options.width, // 线宽
                        "line_opacity": options.borderOpacity, // 透明度
                        "data": options.data
                    }, () => this.map.logInfo);
                } else {
                    featureId = await vjcommon.drawEllipseFill(this.map, draw, drawOptions, {
                        "color": options.color,// 颜色
                        "opacity": options.opacity, // 透明度
                        "outlineColor": options.borderColor || options.color,// 边框颜色
                        "line_width": options.width, // 线宽
                        "line_opacity": options.borderOpacity, // 透明度
                        "data": options.data
                    }, () => this.map.logInfo);
                }
            } else {
                if (options.arc) {
                    featureId = await vjcommon.drawEllipseArc(this.map, draw, drawOptions, {
                        "color": options.borderColor || options.color,// 颜色
                        "line_width": options.width, // 线宽
                        "data": options.data
                    }, () => this.map.logInfo);
                } else {
                    featureId = await vjcommon.drawEllipseEdge(this.map, draw, drawOptions, {
                        "color": options.borderColor || options.color,// 颜色
                        "line_width": options.width, // 线宽
                        "data": options.data
                    }, () => this.map.logInfo);
                }
            }
            if (featureId) {
                // 添加撤销记录
                this.context.ui.addUndoRecord(() => {
                    this.map.getDrawLayer().delete(featureId);
                });
                return { id: featureId };
            }
        }
    

    /**
     * 设置数据图层是否可见
     * @param {string} layerName 图层名称，参数必填
     * @param {boolean} visible 是否可见，参数必填
     * @example
      * // 设置图层名称为layer1的数据图层隐藏
      * let layerName = 'layer1';
      * this.setDataLayerVisible(layerName, false);
     */
    setDataLayerVisible(layerName, visible)  {
            let item = this.context.global.layers.find(layer => layer.layerName == layerName);
            if(item && item.visible != visible) {
                item.visible = visible;
                let layerId = item.layerId;
                if (item.layer && typeof(item.layer.getLayerId) == "function") {
                    layerId = item.layer.getLayerId();
                }
                item.layer.setVisible(this.map, layerId, !visible);
    
                // 添加撤销记录
                this.context.ui.addUndoRecord(() => {
                    this.setDataLayerVisible(item.layerName, visible);
                });
            }
        }
    

    /**
     * 创建调试瓦片图层
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {number} options.rasterOpacity 图片的不透明度，参数可选，取值范围为 0 ~ 1，默认值为 1
     * @param {number} options.rasterHueRotate 在色轮上旋转色相的角度，参数可选，默认值为 0，单位：角度
     * @param {number} options.rasterBrightnessMin 图片的最小亮度，参数可选，取值范围为 0 ~ 1，默认值为 0
     * @param {number} options.rasterBrightnessMax 图片的最大亮度，参数可选，取值范围为 0 ~ 1，默认值为 1
     * @param {number} options.rasterSaturation 图片的饱和度，参数可选，取值范围为 -1 ~ 1，默认值为 0
     * @param {number} options.rasterContrast 图片的对比度，参数可选，取值范围为 -1 ~ 1，默认值为 0
     * @param {number} options.rasterFadeDuration 切换瓦片时的渐隐时间，参数可选，默认值为 300，单位：毫秒
     * @param {string} options.rasterResampling 采样方式，参数可选，可选值为 linear、nearest，默认值为 linear
     * @param {string} options.color 颜色, 格式为#RRGGBB
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 创建调试瓦片图层
      * let layer = await this.createDebugLayer();
      * console.log(layer);
     */
    async createDebugLayer(options)  {
            options = options || {};
            let type = "raster";
    
            let randomId = vjmap.RandomID();
            let sourceId = `source_${type}_${randomId}`;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                sourceId,
                layerId,
                ...options,
                type: type
            }
    
            let isVector = this.map.hasVectorLayer() ? "&vector=true" : ''
            let r = 255, g = 255, b = 0, a = 255;
           
            if (options.color && options.color.startsWith('#') && options.color.length === 7) {
                r = parseInt(options.color.slice(1, 3), 16);
                g = parseInt(options.color.slice(3, 5), 16);
                b = parseInt(options.color.slice(5, 7), 16);
            }
            let svc = this.map.getService();
            let tiles = svc.serviceUrl('this.map/') + `tile/debug/{z}/{x}/{y}?r=${r}&g=${g}&b=${b}&a=${a}${isVector}&token=${svc.accessToken}`;
            let sourceData = {
                type: "raster",
                tiles: [tiles],
                tileSize: this.map.hasVectorLayer() ? 512 : 256
            }
           
            this.map.addRasterSource(sourceId, sourceData);
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data: sourceData, options, layer, visible: true, create: this.createDebugLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 获取cad图epsg代号
     * @param {object} options 配置选项
     * @param {string} [options.mapid] 地图ID，为空的话，采用当前打开的地图
     * @param {string} [options.version] 地图版本(为空时采用当前打开的地图版本)
     * @param {"beijing54"|"xian80"|"cgs2000"} [options.crs] cad图的坐标系,beijing54(北京54坐标)、xian80(西安80坐标、cgs2000(CGCS2000坐标)
     * @returns {Promise<string|undefined>} 返回cad图的epsg代号, 如果获取不到，则返回undefined
     * @example
      * //示例一
      * // 获取当前图的epsg代号, 把结果输出至会话窗口
      * let epsg = await this.getCadEpsgCode();
      * if (epsg) {
      * this.chatLog(epsg);
      * } else {
      * this.chatLog("获取cad图的epsg代号失败");
      * }
      * //示例二
      * // 获取图ca422a931980的epsg代号,cad图的坐标系为西安80
      * let epsg2 = await this.getCadEpsgCode({
      * mapid: "ca422a931980",
      * crs: "xian80"
      * });
      * console.log(epsg2);
     */
    async getCadEpsgCode(options)  {
            options = options || {}
            let svc = this.map.getService();
            // 获取中心点，要叠加互联网地图要求是地理坐标X是8位，Y是7位，如果X没有带系的话，X可以是6位
            let center = this.map.fromLngLat(this.map.getCenter());
            if (options.mapid) {
                let metadata = await svc.metadata(options.mapid, options.version);
                let mapExtent = vjmap.GeoBounds.fromString(metadata.bounds);
                center = mapExtent.center();
            }
            let strX = parseInt(center.x) + "";
            let xLen = strX.length;
            let yLen = (parseInt(center.y) + "").length;
            if (yLen == 7) {
                let crs = vjmap.transform.EpsgCrsTypes.CGCS2000;
                if (options.crs == "beijing54") {
                    crs = vjmap.transform.EpsgCrsTypes.Beijing54;
                } else if (options.crs == "xian80") {
                    crs = vjmap.transform.EpsgCrsTypes.Xian80;
                }
                if (xLen == 8) {
                    let epsg = vjmap.transform.getEpsgParam(crs, +strX.substring(0, 2))?.epsg;
                    if (epsg) {
                        return epsg;
                    }
                } else if (xLen == 6) {
                    // 如果是6位的坐，前面没有带号信息，需要在互联网地图上选择地图所有的大概位置的经纬度来确定
                    let lngLat = await this.context.ui.selectWebMapLngLat({
                        isGaode: true,
                        isRoadway: true
                    })
                    if (lngLat) {
                        let res = vjmap.transform.getEpsgCode([lngLat.lng, lngLat.lat]);
                        let curEpsg;
                        let curDist;
                        // 获取最接近的epsg
                        for(let item of res) {
                           if (crs == vjmap.transform.EpsgCrsTypes.CGCS2000 && item.crs.indexOf('2000') == -1) {
                              continue;
                            } else if (crs == vjmap.transform.EpsgCrsTypes.Beijing54 && item.crs.indexOf('54') == -1) {
                              continue;
                           } else if (crs == vjmap.transform.EpsgCrsTypes.Xian80 && item.crs.indexOf('80') == -1) {
                              continue;
                           }
                           let epsg = item.epsg;
                           let result = await svc.cmdTransform("EPSG:4326", epsg, vjmap.geoPoint([lngLat.lng, lngLat.lat])); // 如果不想调服务转，也可以在前端用proj4库
                           let pt = result[0];// 返回的第1条结果
                           let dist = vjmap.geoPoint(pt).distanceTo(vjmap.geoPoint(center))
                           if (curDist === undefined) {
                            curDist = dist;
                            curEpsg = epsg;
                           } else if (dist < curDist) {
                            curDist = dist;
                            curEpsg = epsg;
                           }
                        }
                        if (curEpsg) {
                            return curEpsg;
                        }
                    }
                }
            }
            return undefined;
        }
    

    /**
     * 修改CAD图实体对象
     * @param {boolean} rectSelect 是否矩形选择, false表示点选，true表示矩形选择, 参数可选, 默认false
     * @example
      * // 示例一
      * // 点选修改CAD图实体对象
      * this.modifyCadEntity(false);
      * // 示例二
      * // 矩形选择修改CAD图实体对象
      * this.modifyCadEntity(true);
     */
    async modifyCadEntity(rectSelect)  {
            let draw = this.map.getDrawLayer();
            if (!this.context.global.updateMapStyleObj) {
                this.context.global.updateMapStyleObj = vjcommon.createUpdateMapStyleObj(this.map);
            }
            const updateMapStyleObj = this.context.global.updateMapStyleObj;
            // 保存旧的数据，用于撤销记录
            let oldHideObjectIds = new Set(updateMapStyleObj.hideObjectIds);
            const deleteEntitys = vjcommon.getTrackFeatureProperty(draw, "deleteEntitys") || [];
            const hideEntityObjectIds =
              vjcommon.getTrackFeatureProperty(draw, "hideEntityObjectIds") || [];
            const hideEntityIds = vjcommon.getTrackFeatureProperty(draw, "hideEntityIds") || [];
            let features = draw.getAll().features;
            let oldFeatureLength = features ? features.length : 0;
    
            await vjcommon.modifyCadEntity(this.map, draw, updateMapStyleObj,  this.map.logInfo.bind(this.map), async (content) => {
                return await this.context.ui.confirm(content, "提示", "warning");
              }, rectSelect, (content) => {
                return this.context.ui.prompt("请输入内容", "提示", content);
              });
              
            let newHideObjectIds = new Set(updateMapStyleObj.hideObjectIds);
            let diffObjectIds = newHideObjectIds.difference(oldHideObjectIds);
            let addFeatureIds = [];
            let newFeatures = draw.getAll().features;
            let newFeatureLength = newFeatures ? newFeatures.length : 0;
            for (let i = oldFeatureLength; i < newFeatureLength; i++) {
                addFeatureIds.push(newFeatures[i].id);
            }
            // 添加撤销记录
            this.context.ui.addUndoRecord(async () => {
                await updateMapStyleObj.removeHideObjectIds(diffObjectIds);
                this.map.getDrawLayer().delete(addFeatureIds);
                vjcommon.setTrackFeatureProperty(draw, {
                  deleteEntitys: Array.from(new Set(deleteEntitys)),
                  hideEntityObjectIds: Array.from(new Set(hideEntityObjectIds)),
                  hideEntityIds: Array.from(new Set(hideEntityIds)),
                });
            });
        }
    

    /**
     * 获取一个点到多条线的最近点
     * @param {[number, number]} point 点坐标[x,y]
     * @param {[number, number][][] | geojson} lines 多条线的坐标数组，每个元素是一个包含两个数字的数组[x,y] 或 geojson
     * @returns {object} 返回包含最近点、最近点距离、最近点索引、最近点前一个点距离的object, 具体属性如下：
     * @example
      * // 获取一个点到多条线的最近点, 点坐标[130,140], 多条线坐标[[[100,100],[200,200]],[[100,200],[200,100]]]
      * let lines = [
      * [[100, 100], [200, 200]],
      * [[100, 200], [200, 100]]
      * ];
      * let point = [130, 140];
      * let res = this.closestPointOnPolylines(point, lines);
      * console.log(res);
     */
    closestPointOnPolylines(point, lines)  {
            if (lines && lines.type === "FeatureCollection" && lines.features.length > 0) {
                let geojsonLines = [];
                for(let i = 0; i < lines.features.length; i++) {
                    if (lines.features[i].geometry.type === "LineString") {
                        geojsonLines.push(lines.features[i].geometry.coordinates);
                    } else if (lines.features[i].geometry.type === "MultiLineString") {
                        lines.features[i].geometry.coordinates.forEach(ln => geojsonLines.push(ln));
                    }
                }
                lines = geojsonLines;
            }
            if (Array.isArray(lines)) {
                lines = lines.map(line => line.map(co => vjmap.geoPoint(co)));
            }
            // 计算与所有线的最近点
            const res = vjmap.closestPointOnPolylines(vjmap.geoPoint(point), lines);
            if (res && res.closestPoint) {
                res.closestPoint = [res.closestPoint.x, res.closestPoint.y];
            }
            return res;
        }
    

    /**
     * 通过数据获取数据范围大小
     * @param {Object} geojson 要获取范围的geojson数据，参数必填
     * @returns {[number, number, number, number]} 返回包含四个数字的数组[x1,y1,x2,y2]，表示geojson数据范围的左下角和右上角坐标
     * @example
      * // 获取geojson数据范围大小
      * let geojson = {type: "FeatureCollection", features: [{type: "Feature", geometry: {type: "Point", coordinates: [10, 20]}}]};
      * let bounds = this.getDataGeoBounds(geojson);
      * console.log(bounds);
     */
    getDataGeoBounds(geojson)  {
            let dataExtent = vjmap.GeoBounds.fromDataExtent(geojson);
            return dataExtent.toArray();
        }
    

    /**
     * 手动交互式绘制一个矩形
     * @param {object} options 配置参数, 参数可选
     * @param {boolean} options.isSnap 是否启用捕捉功能, 参数可选，默认false
     * @param {string} options.color 颜色，格式为#RRGGBB，参数可选
     * @param {boolean} options.isFill 是否填充，默认false，参数可选
     * @param {number} options.opacity 透明度，默认1，参数可选
     * @param {number} options.data 数据值，参数可选
     * @returns {Promise<Object | undefined>} 返回绘制的图元对象，如果取消操作则返回undefined，返回的对象包含以下属性：
     * @example
      * // 手动绘制一个矩形
      * const rect = await this.drawRectangle({
      * isSnap: true,
      * color: "#FF0000",
      * isFill: true,
      * opacity: 0.8
      * });
      * if (!rect) return;
      * console.log("矩形ID:", rect.id);
      * console.log("矩形坐标:", rect.points);
     */
    async drawRectangle(options)  {
            let points = await this.actionDrawRectangle(options.isSnap);
            if (!points) return;
            return this.createRectangle(points, options);
        }
    

    /**
     * 移除一个功能菜单
     * @param {string} name 菜单名
     * @example
      * // 移除菜单名为全图显示的功能菜单
      * this.removeMenuItem("全图显示")
     */
    removeMenuItem(name, func)  {
            this.context.ui.removeMenuItem(name, func);   
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.addMenuItem(name, func);
            });
         }
    

    /**
     * 选择地图, 返回选择的地图mapid和version
     * @returns {Object | undefined} 返回对象，包含地图id和版本 {mapid: string, version: string}, 如果取消则返回undefined
     * @example
      * // 选择地图, 返回选择的地图mapid和version
      * let res = await this.selectCadMap();
      * if (res) {
      * this.chatLog("选择的地图mapid为" + res.mapid + ", version为" + res.version);
      * }
     */
    async selectCadMap()  {
            let res = await this.context.ui.selectCadMap()
            if (!res) return;
            return {
                mapid: res.mapid,
                version: res.version
            };
        }
    
    

    /**
     * 创建动画多边形数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {boolean} options.isHoverPointer 是否启用鼠标悬浮事件，参数可选
     * @param {boolean} options.isHoverFeatureState 是否启用悬浮实体状态，参数可选
     * @param {string} options.fillColor 填充颜色
     * @param {string} options.fillOutlineColor 边框颜色
     * @param {number} options.fillOpacity 填充透明度，取值范围0-1
     * @param {string} options.fillPattern 填充图案，必须与地图已加载的图片名称对应，图像宽度必须是2的系数(2、4、8、...、512)
     * @param {[number,number]} options.fillTranslate 几何体的平移量[x,y]，负数分别表示左和上，单位像素，默认[0,0]
     * @param {string} options.fillTranslateAnchor 平移参考系，"map"表示相对于地图平移，"viewport"表示相对于视窗平移
     * @param {boolean} options.fillAntialias 是否启用填充反锯齿
     * @param {string} options.animateImagesType 动画类型，可选值："arrow"(箭头动画), "antpath"(蚂蚁线动画), "customImage"(自定义图片), "imageSprites"(同一个图片精灵动画集)
     * @param {string} options.animateImages_arrowFillColor 箭头填充颜色，当animateImagesType为"arrow"时有效，默认"#22B14C"
     * @param {string} options.animateImages_arrowStrokeColor 箭头边框颜色，当animateImagesType为"arrow"时有效，默认"#fff"
     * @param {number} options.animateImages_arrowStrokeWidth 箭头线宽，当animateImagesType为"arrow"时有效
     * @param {number} options.animateImages_arrowWidth 箭头大小，当animateImagesType为"arrow"时有效
     * @param {string} options.animateImages_fillColor1 蚂蚁线填充颜色1，当animateImagesType为"antpath"时有效
     * @param {string} options.animateImages_fillColor2 蚂蚁线填充颜色2，当animateImagesType为"antpath"时有效
     * @param {string} options.customImageUrl 自定义图片URL或base64，当animateImagesType为"customImage"时有效
     * @param {string} options.imageSpriteUrl 图片动画集URL或base64，当animateImagesType为"imageSprites"时有效
     * @param {number} options.animateImages_spriteWidth 来源图片中精灵图所占宽度，当animateImagesType为"imageSprites"时有效
     * @param {number} options.animateImages_spriteHeight 来源图片中精灵图所占高度，当animateImagesType为"imageSprites"时有效
     * @param {number} options.animateImages_canvasWidth 画布宽度，所有动画类型通用
     * @param {number} options.animateImages_canvasHeight 画布高度，所有动画类型通用
     * @param {number} options.animateImages_frameCount 动画帧数，所有动画类型通用
     * @param {boolean} options.animateImages_directionReverse 动画方向是否反向，所有动画类型通用
     * @param {number} options.speed 动画速度，步长0.1
     * @param {boolean} options.startAutoAnimation 是否开始时自动播放动画
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer2的动画多边形数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createAnimateFillLayer(geojson, {
      * layerName: 'layer2'
      * });
      * console.log(layer);
     */
    async createAnimateFillLayer(data, options)  {
            options = options || {};
            let type = "animateFill";
    
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                fillColor: "#00ffff",
                fillOutlineColor: "#00ff00",
                animateImagesType: "arrow",
                animateImages_arrowFillColor: "#22B14C",
                animateImages_arrowStrokeColor: "#fff",
                animateImages_arrowStrokeWidth: 6,
                animateImages_canvasWidth: 128,
                animateImages_canvasHeight: 32,
                animateImages_arrowWidth: 16,
                animateImages_frameCount: 4,
                animateImages_fillColor1: "#f0fb",
                animateImages_fillColor2: "#0ffb",
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createSymbolCircleLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 获取CAD图层列表信息
     * @param {string} mapid 地图id 可选，为空时则获取当前打开的地图
     * @param {string} version 版本 可选，为空时则获取当前打开的地图版本
     * @returns {Promise<Object[]>} 返回CAD图层列表信息, 每项的属性如下
     * @example
      * // 示例一
      * // 获取CAD图层列表信息
      * let res = await this.getCadLayers();
      * if (res) {
      * this.chatLog("图层名称为:" + res.map(layer => layer.name).join(","));
      * }
      * // 示例二
      * // 获取CAD图sys_world的v1版本图层列表信息
      * let resp = await this.getCadLayers("sys_world", "v1");
      * if (resp) {
      * console.log(resp);
      * }
     */
    async getCadLayers(mapid, version)  {
            let svc = this.map.getService();
            if (!mapid) {
                return svc.getMapLayers();
            } else {
                // 先获取元数据
                let meta = await svc.metadata(mapid, version);
                if (meta && meta.layers) {
                    return meta.layers;
                }
            }
        }
    

    /**
     * 删除所有功能按钮
     * @example
      * // 删除所有功能按钮
      * this.deleteButtonItems()
     */
    deleteButtonItems()  {
             this.context.ui.deleteButtonItems();   
          }
    
    

    /**
     * 设置绘制图层的图元数据
     * @param {<GeoJSON | undefined>} data 图元数据
     */
    setDrawLayerData(data)  {
            let draw = this.map.getDrawLayer();
            draw.set(this.map.toLngLat(data));
        }
    

    /**
     * 创建符号圆数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {number} options.circleRadius 圆半径，单位像素，参数可选
     * @param {string} options.circleColor 圆填充颜色，支持十六进制颜色值，参数可选
     * @param {number} options.circleOpacity 圆填充透明度，取值范围0-1，参数可选
     * @param {string} options.circleStrokeColor 圆边框颜色，支持十六进制颜色值，参数可选
     * @param {number} options.circleStrokeWidth 圆边框宽度，单位像素，参数可选
     * @param {number} options.circleStrokeOpacity 圆边框透明度，取值范围0-1，参数可选
     * @param {number} options.circleBlur 圆模糊度，参数可选
     * @param {[number]} options.circleTranslate 平移量，格式[x,y]，负数表示向左和向上平移，单位像素，默认[0,0]，参数可选
     * @param {string} options.circleTranslateAnchor 平移参考系，'map'表示相对地图平移，'viewport'表示相对视窗平移，参数可选
     * @param {string} options.circlePitchScale 倾斜时缩放对齐方式，'map'相对地图，'viewport'相对视窗，参数可选
     * @param {string} options.circlePitchAlignment 倾斜时对齐方式，'map'相对地图，'viewport'相对视窗，参数可选
     * @param {boolean} options.isHoverPointer 是否启用鼠标悬浮事件，参数可选
     * @param {boolean} options.isHoverFeatureState 是否启用悬浮实体状态，参数可选
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer7的圆数据图层,圆半径为5，颜色为红色
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createSymbolCircleLayer(geojson, {
      * layerName: 'layer7',
      * circleRadius: 5,
      * circleColor: "#ff0000"
      * });
      * console.log(layer);
     */
    async createSymbolCircleLayer(data, options)  {
            options = options || {};
            let type = "circle";
        
    
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                "circleRadius": 5,
                "circleColor": "#ffff00",
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createSymbolCircleLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 获取当前显示地图预览的url地址
     * @returns {Promise<string>} 返回当前显示地图的url地址
     * @example
      * // 获取当前显示地图预览的url地址
      * let url = await this.curMapPreviewUrl();
      * console.log(url);
     */
    curMapPreviewUrl()  {
            let svc = this.map.getService();
            let mapParam = svc.currentMapParam();
            let url = svc.baseUrl() + "_mapviewer?";
            let param = {};
            param["render"] = 1; // 默认几何栅格
            if (mapParam?.mapopenway == vjmap.MapOpenWay.Memory) {
                param["render"] = 0; // 内存直接打开
            } else if (mapParam?.mapopenway == vjmap.MapOpenWay.GeomRender && window.location.href.indexOf("vector=true") > 0) {
                param["render"] = 2; // 默认几何矢量
            }
    
            param["mapid"] = mapParam?.mapid;
            param["version"] = mapParam?.version;
            param["layer"] = mapParam?.layer;
    
            param["centerX"] = this.map.getCenter().lng;
            param["centerY"] = this.map.getCenter().lat;
            param["zoom"] = this.map.getZoom();
            param["pitch"] = this.map.getPitch();
            param["bearing"] = this.map.getBearing();
            param["light"] = mapParam?.darkMode !== false;
    
            param["title"] = `唯杰地图vjmap-图名称:${mapParam?.mapid}-版本:${mapParam?.version}-样式:${mapParam?.layer}`;
    
            param["mobile"] = true;
            param['token'] = svc.accessToken;
            if (svc.getCurWorkspaceName()) {
                param['workspace'] = svc.getCurWorkspaceName();
            }
            url = url + Object.keys(param).map(item => item + "=" + param[item]).join("&");
            return url;
        }
    

    /**
     * 目标匹配
     * @param {object} options 参数
     * @param {string} [options.mapid] 地图ID
     * @param {string} [options.version] 地图版本(为空时采用当前打开的地图版本)
     * @param {number} [options.selectWay] 选择方式. 0表示框选实体，1表示点选实体， 2不手动选择实体，默认0
     * @param {string} [options.layer] 图层样式名 为空时，将由选择的实体的图层来决定
     * @param {string} [options.mapBounds] 目标匹配的地图范围，默认为全图的数据范围
     * @param {string} [options.objectIds] 目标匹配的实体objectid数组，用||隔开
     * @param {string} [options.objectBounds] 目标匹配的实体范围
     * @param {string} [options.layeron] 目标匹配的地图打开的图层
     * @param {number} [options.size] 匹配时的图像尺寸，默认10000
     * @param {string} [options.method] 方法. 默认 matchPattern
     * @param {number} [options.maxCount] 允许返回的最大条数. 默认 200
     * @param {number} [options.score] 分数，小于此分类将不显示 (0 -1)，默认 0.6
     * @param {number} [options.minReduceArea] 金字塔大小 范围(64-2048).默认256， 方法为matchPattern有效
     * @param {boolean} [options.canOverlap] 允许重叠 (默认false)
     * @param {number} [options.maxOverlap] 重叠比例 范围(0-0.8)，默认 0.3
     * @param {number} [options.toleranceAngle] 角度范围(-180, 180)有旋转的时候需要。方法为matchPattern有效. 默认180
     * @param {boolean} [options.useToleranceRange] 是否使用角度区间范围,默认false. 方法为matchPattern有效
     * @param {number} [options.tolerance1] 角度区间1开始角度. 方法为matchPattern有效
     * @param {number} [options.tolerance2] 角度区间1结束角度. 方法为matchPattern有效
     * @param {number} [options.tolerance3] 角度区间2开始角度. 方法为matchPattern有效
     * @param {number} [options.tolerance4] 角度区间2结束角度. 方法为matchPattern有效
     * @param {boolean} [options.showSettingForm] 是否弹出设置对话框. 默认true
     * @returns {Promise<GeoJSON>} 返回比较结果, 如果失败则返回undefined, 如果成功则返回匹配的GeoJSON数据
     * @example
      * // 点选实体对当前地图进行目标匹配
      * let res = await this.matchObject({
      * selectWay: 1
      * });
      * console.log(res);
     */
    async matchObject(options)  {
           options = options || {};
           let svc = this.map.getService();
           options = {
            mapid: options.mapid || svc.currentMapParam().mapid,
            objectIds: '',
            objectBounds: '',
            method: "matchPattern",
            maxCount: 200,
            score: 0.6,
            size: 10000,
            canOverlap: false,
            maxOverlap: 0.3,
            toleranceAngle: 180,
            layer: "",
            layeron: "",
            ...options,
           }
           if (options.selectWay === 0) {
             let isPointSel = options.selectWay === 1;
             let param = {};
             let res = await vjcommon.selectFeatures(this.map, true, isPointSel, isPointSel, true);
             vjcommon.clearHighlight(this.map);
             if (!res || res.length == 0) return;
             const objectIds = res.map(f => f.properties.objectid);
             const layeron = `(${Array.from(new Set(res.map(f => f.properties.layerindex))).join(",")})`;
             const features = {
                type: "FeatureCollection",
                features: res
             }
             let data = this.map.fromLngLat(features);
             let bounds = vjcommon.getGeoJsonBounds(data).scale(1.01); // 稍放大点
             param.objectIds = objectIds.join("||");
             param.objectBounds = `[${bounds.toString()}]`;
             param.layeron = layeron;
             options = {
                ...options,
                ...param
             }
           }
           if (options.showSettingForm !== false) {
            options = await this.showDataModifyDlg({
                data: options,
                title: "目标匹配设置",
                showForm: true
            })
            if (!options) return;
           }
           
           let result = await svc.cmdMatchObject(options);
           if (result && result.error) {
                if (this.map.logInfo) this.map.logInfo(result.error, "error", 10000);
                return;
            }
           if (!result || !result.match) return;
           let match = result.match.split(";");
           let geojson = {
            type: "FeatureCollection",
            features: []
           };
            for(let i = 0; i < match.length; i++) {
                if (match[i].indexOf(",") <= 0) continue;
                const items = match[i].split(",");
                const pts = [[+items[0], +items[1]], [+items[2], +items[3]], [+items[4], +items[5]], [+items[6], +items[7]]];
                pts.push(pts[0])
    
                geojson.features.push({
                    type: "Feature",
                    geometry: {
                        type: "LineString",
                        coordinates: pts
                    }
                })
            }
            return geojson;
       }
    

    /**
     * 弹出消息输入框
     * @param {string} message 对话框内容，参数可选
     * @param {string} title 对话框标题，参数可选
     * @param {string} inputValue 初始值，参数可选
     * @param {boolean} isPassword 是否为密码输入框，参数可选，默认false
     * @returns {string|undefined} 返回输入的内容, 如果用户点击取消，则返回undefined
     * @example
      * // 弹出消息输入框, 消息为"请输入内容", 初始值为空
      * let res = await this.messageBoxPrompt("请输入内容", "提示", "");
      * if (res !== undefined) {
      * console.log("输入的内容为:", res)
      * } else {
      * console.log("点击了取消")
      * }
     */
    async messageBoxPrompt(message, title, inputValue, isPassword)  {
            return await this.context.ui.prompt(message || "请输入内容", title || "提示", inputValue || "", isPassword || false);
        }
    

    /**
     * 获取所有地图数据或通过指定mapid获取地图数据
     * @param {string} mapid 地图ID. 可选，为空时则获取所有地图数据
     * @param {string} version 版本号. 可选，为空时则获取最新版本， *表示所有版本
     * @returns {Promise<Array<Object>>} 返回地图元数据, 如果失败则返回undefined, 数据每项的字段如下:
     * @example
      * // 示例一
      * // 获取所有地图数据
      * let res = await this.listMaps();
      * console.log(res);
      * // 示例二
      * // 获取地图sys_hello的所有版本数据
      * let res2 = await this.listMaps("sys_hello", "*");
      * console.log(res2);
     */
    async listMaps(mapid, version)  {
            let svc = this.map.getService();
            let res =  await svc.listMaps(mapid, version);
            let result = [];
            if (mapid) {
                if (res && typeof res["version"] === "string") {
                    result.push({
                        mapid: mapid,
                        ...res 
                    });
                    return result;
                }
            } 
            for(let id in res) {
                let data = res[id];
                result.push({
                    mapid: id,
                    ...data
                });
            }
            return result;
         }
    

    /**
     * 投影prj文件WKT字符转proj4字符串
     * @param {string} wkt 投影prj文件WKT字符
     * @param {string} [from] 字符串来源，为空为话为WKT
     * @returns {Promise<string>} 返回proj4字符串
     * @example
      * // 将WKT字符串`PROJCS["CGCS2000 / 3-degree Gauss-Kruger CM 111E",GEOGCS["China Geodetic Coordinate System 2000",DATUM["China_2000",SPHEROID["CGCS2000",6378300,300,AUTHORITY["EPSG","1024"]],AUTHORITY["EPSG","1043"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.018,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4490"]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",112.1],PARAMETER["scale_factor",1],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AUTHORITY["EPSG","4546"]]`转换为proj4字符串
      * let proj4String = await this.prjWktToPrj4(`PROJCS["CGCS2000 / 3-degree Gauss-Kruger CM 111E",GEOGCS["China Geodetic Coordinate System 2000",DATUM["China_2000",SPHEROID["CGCS2000",6378300,300,AUTHORITY["EPSG","1024"]],AUTHORITY["EPSG","1043"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.018,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4490"]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",112.1],PARAMETER["scale_factor",1],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AUTHORITY["EPSG","4546"]]`);
      * console.log(proj4String);
     */
    async prjWktToPrj4(wkt, from)  {
           let svc = this.map.getService();
           let res = await svc.cmdPrjWktToPrj4(wkt, from);
           return res?.proj4;
       }
    

    /**
     * 移除鼠标位置控件
     * @example
      * // 移除鼠标位置控件
      * this.removeMousePositionControl();
     */
    removeMousePositionControl()  {
            this.context.global.controls = this.context.global.controls || {};
            let options;
            if (this.context.global.controls.mousePositionControl) {
                options = {...this.context.global.controls.mousePositionControl.options}
                this.map.removeControl(this.context.global.controls.mousePositionControl.control);
                delete this.context.global.controls.mousePositionControl;
            }
            // 增加撤销记录
            this.context.ui.addUndoRecord(() => {
                if (options) {
                    this.addMousePositionControl(options);
                }
            }); 
        }
    
    

    /**
     * 关闭数据显示面板
     * @example
      * // 关闭数据显示面板
      * this.closeDataPanel()
     */
    closeDataPanel()  {
            this.context.ui.setDataPanelContent();   
         }
    
    

    /**
     * 创建点标记聚合数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {string} options.text 要显示的文字，参数可选，默认为"index ${props.index}"
     * @param {number} options.minZoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxZoom 显示最大缩放级别，参数可选，默认为24
     * @param {boolean} options.isDrawLinePoint 绘制点时包括线上所有点, 参数可选，默认为false
     * @param {string} options.color 标记颜色，参数可选，默认为'#3FB1CE'
     * @param {number[]} options.offset 偏离XY像素值，如[0,0]，负数表示向左和向上
     * @param {number} options.rotation 标记旋转角度，顺时针旋转，默认0
     * @param {string} options.anchor 标记锚点位置，可选值：center、top、bottom、left、right、top-left、top-right、bottom-left、bottom-right，默认center
     * @param {boolean} options.draggable 是否可拖动，参数可选，默认为false
     * @param {string} options.animationType 动画类型：MAP_ANIMATION_NONE(无动画)、MAP_ANIMATION_BOUNCE(弹跳)、MAP_ANIMATION_DROP(坠落)
     * @param {string} options.rotationAlignment 旋转对齐：auto(自动)、map(相对地图)、viewport(相对视口)，默认auto
     * @param {string} options.pitchAlignment 倾斜对齐：auto(自动)、map(相对地图)、viewport(相对视口)，默认auto
     * @param {number} options.scale 缩放比例，默认1，对应高度41px宽度27px
     * @param {number} options.scaleMaxZoom 能缩放的最大级别(0-24)，小于此级别时自动缩放
     * @param {number} options.height 标高值，单位CAD图制图单位
     * @param {boolean} options.removeWhenNoInMapView 是否在视图范围内自动显隐
     * @param {number} options.removeWhenNoInMapViewPadding 视图范围内自动显隐的padding像素值，默认500
     * @param {number} options.clickTolerance 点击像素容差，默认继承地图的clickTolerance
     * @param {string} options.customImage 自定义图片地址/base64/svg内容
     * @param {number} options.customImageWidth 自定义图片宽度
     * @param {number} options.customImageHeight 自定义图片高度
     * @param {string} options.customHtml 自定义Html元素的JS代码
     * @param {string} options.popupHtml 信息窗口HTML内容
     * @param {[number,number]} options.popupOffset 信息窗口偏离XY像素值
     * @param {string} options.popupAnchor 信息窗口锚点位置，同anchor参数
     * @param {boolean} options.closeButton 是否显示关闭按钮
     * @param {boolean} options.closeOnClick 是否在点击地图时关闭弹出窗口
     * @param {boolean} options.closeOnMove 是否在地图移动后关闭信息窗口
     * @param {boolean} options.focusAfterOpen 是否尝试聚焦弹出窗口内第一个可聚焦元素
     * @param {string} options.className 添加到弹出容器的CSS类名
     * @param {string} options.maxWidth 弹出窗口最大宽度，CSS属性字符串，默认'240px'
     * @param {boolean} options.allowOverlap 是否允许标记重叠
     * @param {number} options.allowOverlapMaxZoom 允许重叠的最大缩放级别，小于或等于此级别才会处理重叠，超过此级时会全部显示，默认4级
     * @param {number} options.markerWidth marker div的像素宽，用于计算重叠时需要，默认40
     * @param {number} options.markerHeight marker div的像素高，用于计算重叠时需要，默认40
     * @param {Object} options.textStyle 聚合文本的CSS样式对象，如{cursor:"pointer", opacity:0.8等}
     * @param {Array<Array>} options.textColors 聚合文本颜色配置，格式为[[值1,文本颜色,背景色], [值2,文本颜色,背景色], ...]
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer20的点标记聚合数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createMarkerClusterLayer(geojson, {
      * layerName: 'layer20'
      * });
      * console.log(layer);
     */
    async createMarkerClusterLayer(data, options)  {
            options = options || {};
            let type = "markerCluster";
        
    
            if (options.height) {
              options.height = this.map.geoLengthToHeight(options.height);
            }
            
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                textStyle: {
                  cursor: "pointer",
                  opacity: 0.8,
                  padding: "6px",
                  "border-radius": "12px",
                  "border-width": 0,
                  "box-shadow": "0px 2px 6px 0px rgba(97,113,166,0.2)",
                  "text-align": "center",
                  "font-size": "14px",
                },
                textColors: [
                    [0, "#00FFFF", "#00FFFF"],
                    [5, "#F0F", "#80FF00"],
                    [10, "#00F", "#FFFF00"],
                    [1000, "#FFF", "#FF3D3D"]
                ],
                popupHtml: "<h3 style='color:red'>index ${props.index}</h3>",
                popupOffset: [0, -28],
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createMarkerClusterLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 创建矢量瓦片图层
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {string} options.tileMapId 瓦片的mapid，参数可选
     * @param {string} options.tileMapVersion 瓦片的版本，参数可选
     * @param {string} options.tileMapLayer 瓦片的样式图层名称，参数可选
     * @param {string|Array<string>} options.tiles 瓦片地址，支持变量 {x},{y},{z},{serviceUrl},{accessToken}
     * @param {number} options.tileSize 瓦片大小，参数可选，默认值为 512
     * @param {number[]} options.bounds 瓦片范围，参数可选，格式为[minx, miny, maxx, maxy]，默认为全图范围
     * @param {string} options.scheme 瓦片y方向，参数可选，可选值为 xyz(Y从顶部开始)、tms(Y从底部开始)，默认为 xyz
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 创建矢量瓦片图层, 瓦片的mapid为sys_hello
      * let layer = await this.createVectorLayer({
      * tileMapId: "sys_hello",
      * });
      * console.log(layer);
     */
    async createVectorLayer(options)  {
            options = options || {};
            let type = "vector";
    
            let randomId = vjmap.RandomID();
            let sourceId = `source_${type}_${randomId}`;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                sourceId,
                layerId,
                ...options,
                type: type
            }
    
            if (options.tileMapId && !options.tiles) {
                let layer = options.tileMapLayer;
                if (!layer) {
                    let style = await this.map.getService().createStyle({
                        backcolor: this.map.getService().currentMapParam().darkMode ? 0x000000 : 0xFFFFFF
                    }, options.tileMapId, options.tileMapVersion);
                    layer = style.stylename
                }
                let tileUrl = this.map.getService().vectorTileUrl({
                    mapid: options.tileMapId,
                    version: options.tileMapVersion,
                    layer: layer,
                })
                options.tiles = tileUrl;
            }
            if (options.bounds) {
                const p1 = this.map.toLngLat([options.bounds[0], options.bounds[1]]);
                const p2 = this.map.toLngLat([options.bounds[2], options.bounds[3]]);
                options.bounds = [p1[0], p1[1], p2[0], p2[1]];
            }
            let sourceData = {
                type: "vector",
                tiles: Array.isArray(options.tiles) ? options.tiles : [options.tiles],
                tileSize: options.tileSize || 512
            }
            if (options.bounds) {
                sourceData.bounds = options.bounds;
            }
            if (options.scheme) {
                sourceData.scheme = options.scheme;
            }
            this.map.addVectorSource(sourceId, sourceData);
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data: sourceData, options, layer, visible: true, create: this.createVectorLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 创建多边形数据图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {string} options.fillColor 填充颜色
     * @param {string} options.fillOutlineColor 边框颜色
     * @param {number} options.fillOpacity 填充透明度，取值范围0-1
     * @param {string} options.fillPattern 填充图案，必须与地图已加载的图片名称对应，图像宽度必须是2的系数(2、4、8、...、512)
     * @param {[number,number]} options.fillTranslate 几何体的平移量[x,y]，负数分别表示左和上，单位像素，默认[0,0]
     * @param {string} options.fillTranslateAnchor 平移参考系，"map"表示相对于地图平移，"viewport"表示相对于视窗平移
     * @param {boolean} options.fillAntialias 是否启用填充反锯齿
     * @param {boolean} options.isHoverPointer 是否启用鼠标悬浮事件，参数可选
     * @param {boolean} options.isHoverFeatureState 是否启用悬浮实体状态，参数可选
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer11的多边形数据图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createFillLayer(geojson, {
      * layerName: 'layer11'
      * });
      * console.log(layer);
     */
    async createFillLayer(data, options)  {
            options = options || {};
            let type = "fill";
        
    
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                fillColor: "#00ffff",
                fillOutlineColor: "#00ff00",
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createFillLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    

    /**
     * 创建Geojson点面线图层
     * @param {GeoJson} data 数据内容，标准的GeoJson格式，参数必填
     * @param {Record<string, any>} options 图层参数选项，参数可选
     * @param {string} options.layerName 图层名称，参数可选，默认为空, 如果为空，则使用随机id作为图层名称
     * @param {number} options.minzoom 显示最小缩放级别，参数可选，默认为0
     * @param {number} options.maxzoom 显示最大缩放级别，参数可选，默认为24
     * @param {string} options.fillColor 填充颜色
     * @param {string} options.fillOutlineColor 边框颜色
     * @param {number} options.fillOpacity 填充透明度，取值范围0-1
     * @param {string} options.fillPattern 填充图案，必须与地图已加载的图片名称对应，图像宽度必须是2的系数(2、4、8、...、512)
     * @param {[number,number]} options.fillTranslate 几何体的平移量[x,y]，负数分别表示左和上，单位像素，默认[0,0]
     * @param {string} options.fillTranslateAnchor 平移参考系，"map"表示相对于地图平移，"viewport"表示相对于视窗平移
     * @param {boolean} options.fillAntialias 是否启用填充反锯齿
     * @param {string} options.lineColor 线条颜色，参数可选，默认为'#ff0000'
     * @param {number} options.lineWidth 线宽，参数可选，默认为1
     * @param {number} options.lineOpacity 线透明度，参数可选，取值范围0-1
     * @param {number} options.lineGapWidth 线间隙宽度，参数可选，默认为1，单位像素
     * @param {number} options.lineOffset 线的偏移量，参数可选。正值向右/内偏移，负值向左/外偏移
     * @param {number} options.lineBlur 线模糊度，参数可选，需小于线宽度
     * @param {[number,number]} options.lineDasharray 虚线样式，参数可选，格式如[1,1]表示实线虚线的组合
     * @param {[number,number]} options.lineTranslate 平移量，参数可选，格式如[x,y]，负数表示左和上，单位像素
     * @param {string} options.lineCap 线帽形状，参数可选，可选值：'butt'(尖头)、'round'(圆头)、'square'(平头)
     * @param {string} options.lineJoin 拐角形状，参数可选，可选值：'bevel'(平拐)、'round'(圆拐)、'miter'(棱拐)
     * @param {string} options.linePattern 线图案，参数可选，需与预加载的图片名称对应，图像宽度必须是2的幂次
     * @param {string} options.lineGradient 线渐变，参数可选
     * @param {number} options.circleRadius 圆半径，单位像素，参数可选
     * @param {string} options.circleColor 圆填充颜色，支持十六进制颜色值，参数可选
     * @param {number} options.circleOpacity 圆填充透明度，取值范围0-1，参数可选
     * @param {string} options.circleStrokeColor 圆边框颜色，支持十六进制颜色值，参数可选
     * @param {number} options.circleStrokeWidth 圆边框宽度，单位像素，参数可选
     * @param {number} options.circleStrokeOpacity 圆边框透明度，取值范围0-1，参数可选
     * @param {number} options.circleBlur 圆模糊度，参数可选
     * @param {[number,number]} options.circleTranslate 平移量，格式[x,y]，负数表示向左和向上平移，单位像素，默认[0,0]，参数可选
     * @param {string} options.circleTranslateAnchor 平移参考系，'map'表示相对地图平移，'viewport'表示相对视窗平移，参数可选
     * @param {string} options.circlePitchScale 倾斜时缩放对齐方式，'map'相对地图，'viewport'相对视窗，参数可选
     * @param {string} options.circlePitchAlignment 倾斜时对齐方式，'map'相对地图，'viewport'相对视窗，参数可选
     * @param {boolean} options.isHoverPointer 是否启用鼠标悬浮事件，参数可选
     * @param {boolean} options.isHoverFeatureState 是否启用悬浮实体状态，参数可选
     * @returns {Object} 返回对象，包含 {sourceId: string, layerId: string, layerName: string}
     * @example
      * // 在地图范围内随机生成GeoJson数据，创建图层名称为layer29的Geojson点面线图层
      * let geojson = this.randomGeojsonInMapBounds();
      * let layer = await this.createGeojsonDataLayer(geojson, {
      * layerName: 'layer29',
      * });
      * console.log(layer);
     */
    async createGeojsonDataLayer(data, options)  {
            options = options || {};
            let type = "geojson";
      
            if  (options.entColorToHtmlColor === undefined) {
              for(let f of data.features) {
                if (f.properties && typeof f.properties.color == "number") {
                    if (f.properties.color < 0) {
                        options.entColorToHtmlColor = true;
                        break;
                      }
                  }
              }
            }
    
            let randomId = vjmap.RandomID();
            let sourceId = "source_geojson_" + randomId;
            let layerId =  `layer_${type}_${randomId}`;
            let layerOptions = {
                layerId,
                sourceId,
                fillColor: "#00ffff",
                fillOutlineColor: "#00ff00",
                lineColor: '#ff0000',
                lineWidth: 1,
                circleRadius: 5,
                circleColor: "#ffff00",
                ...options,
                type: type
            }
            // 添加数据源
            this.map.addGeoJSONSource(sourceId, this.map.toLngLat(data));
    
            // 添加图层
            let layer = new vjcommon.providerLayers[layerOptions.type];
            await layer.addLayer(this.map, layerOptions);
    
            let layerName = options.layerName || layerId;
    
            this.context.global.layers = this.context.global.layers || [];
            this.context.global.layers.push({layerId, sourceId, layerName, data, options, layer, visible: true, create: this.createGeojsonDataLayer});
    
            // 添加撤销记录
            this.context.ui.addUndoRecord(() => {
                this.deleteDataLayer(layerName);
            });
            return {
                sourceId,
                layerId,
                layerName
            };
        }
    


}
```
