# 唯杰地图 VJMAP 文档全文 > 来源 https://vjmap.com,包含 VJMAP 开发指南与唯杰地图服务 REST API。API 参考见 https://vjmap.com/llms/vjmap-api.txt。 --- # 介绍 `唯杰地图VJMAP`为用户`自定义地图格式`WebGIS`可视化`显示开发提供的一站式解决方案,支持的格式如常用的`AutoCAD`的`DWG`格式文件、`GeoJSON`等常用`GIS`文件格式,它使用WebGL`矢量图块`和`自定义样式`呈现交互式地图, 提供了全新的`大数据可视化`、`实时流数据`可视化功能,通过本产品可快速实现浏览器和移动端上美观、流畅的地图呈现与空间分析,可帮助您在网站中构建功能丰富、交互性强、可定制的地图应用。 ## 特点 - 完全兼容`AutoCAD`格式的`DWG`文件,无需转换 - 绘图技术先进:采用WebGL技术,支持`矢量地图`渲染,支持栅格、图片、视频等图形渲染,支持3D模型渲染; - 个性化地图:服务端渲染和前端渲染都支持自定义样式表达式,灵活强大; - 多视角模式:支持2D、3D视角,支持垂直视角、360度旋转视角; - 视觉特性:支持无极缩放、支持粒子、航线等动画效果、支持飞行、平移等运动特效; - 功能完善:支持所有常见的地图功能,提供丰富的js接口; - 交互控制:支持鼠标/单指拖拽、上下左右按键进行地图平移,支持鼠标滚轮、双击、双指进行地图缩放,支持Shift+拉框放大; - 大数据可视化:性能卓越,支持大数据可视化展示 - 跨平台支持(支持`windows`,`linux`); 支持AMD64、ARM64架构的Linux版本, 适配主流的国产CPU、操作系统; 支持`docker`部署;支持`私有化`部署;支持桌面端语言开发(如`C#`、`Java`、`C++`语言) - 全面支持用第三方开源的[openlayers](https://vjmap.com/demo/#/demo/map/openlayers/01olraster) 、[leaflet](https://vjmap.com/demo/#/demo/map/leaflet/01leafletraster) 、[maptalks](https://vjmap.com/demo/#/demo/map/maptalks/01maptalksraster) 来加载CAD地图进行开发 - 支持对图像格式如tiff,jpg,png等常用图像格式进行切片处理,实现几百M以上的图片前端秒开 --- 快速入手 → --- # 快速入手 ## 视频教程 [唯杰地图VJMAP整体介绍视频教程](https://www.bilibili.com/video/BV11M4y19788/?share_source=copy_web&vd_source=8da0f3e60fa8d1449934f8be7b675a52)      [唯杰地图管理平台介绍](https://www.bilibili.com/video/BV1dv4y167Cu/?vd_source=606b3092bb2ff9d13b249fdab0633fdb) [唯杰地图示例介绍](https://www.bilibili.com/video/BV1gT411C75t/?vd_source=606b3092bb2ff9d13b249fdab0633fdb) ## 上传图形 - 通过 [唯杰地图云端图纸管理平台](https://vjmap.com/app/cloud) 提供的[上传图形](https://vjmap.com/demo/#/demo/map/service/06uploadmap) 功能(推荐) 或者 演示示例中[上传图形](https://vjmap.com/demo/#/demo/map/service/06uploadmap) 功能上传图形, 或者参考代码自己通过官方提供的SDK来自己实现上传图形。 ![image-20220306184355126](https://vjmap.com/blogimages/image-20220306184355126.png) 上传成功后,取个图名称ID ![image-20220306184730008](https://vjmap.com/blogimages/image-20220306184730008.png) ::: tip 提示 如果选择存储后渲染栅格或矢量,第一次打开时由于需要保存几何数据,需耐心等待一段时间。下次打开时会很快打开图形。 ::: - 上传时给图取一个唯一的图名称mapid,在以后的开发中会用这个mapid来显示图形 ![image-20220217090623700](https://vjmap.com/blogimages/image-20220217090623700.png) - 如果需要工程化开发,请参考 html、Vue、React应用[示例代码下载](https://vjmap.com/download/vjmap_demo.zip) 工程化开发时,可通过`npm install vjmap`安装sdk ```js import 'vjmap/dist/vjmap.min.css' // 样式一定要引用!!! import vjmap from 'vjmap'; ``` ::: tip 提示 免费试用版不提供私有化部署,直接连官网的服务进行试用,有水印,上传文件大小有限制,上传数据会不定时清空,功能与正式版一致。如果需要正式版本私有化部署,请联系客服。 ::: ## 通过服务打开地图 通过`Service`对象的`openMap`方法可打开服务器上面已存在的地图,或者通过`fileid`传入`http`网络路径或服务器本地路径打开一幅`DWG`格式的`CAD图形`. ```js // 新建地图服务对象,传入服务地址和token let svc = new vjmap.Service(env.serviceUrl, env.accessToken) // 打开地图 let res = await svc.openMap({ mapid: "sys_world", // 地图ID (上传图形时,取的图名称) mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开 // 第一次打开此图时,必须传fileid,以后打开不需要再传了 // fileid: ”通过uploadMap上传图,得到一个fileid,或 一个dwg文件http地址 或 一个服务器固定目录下面的本地文件名称 或 或者新建图的json数据 style: vjmap.openMapDarkStyle() // 服务器渲染样式 }) if (res.error) { message.error(res.error) } ``` ::: tip 提示 打开一个新图: 第一次openMap时: 取个图名称唯一的mapid 第一次打开此图时,必须传fileid,以后打开不需要再传了 fileid可以是以下几个方式 (1) 通过uploadMap上传图,得到一个fileid (2) 一个dwg文件http地址 (3) 一个服务器固定目录下面的本地文件名称 (4) 或者新建图的json数据 以后直接通过 mapid打开此图形,不需要再传fileid ::: ## 建立地图对象 获取上面打开地图返回的`地理范围`建立`坐标系`,然后创建`地图对象` ```js // 根据地图范围建立几何投影坐标系 let prj = new vjmap.GeoProjection(res.bounds); // 地图对象 let map = new vjmap.Map({ container: 'map', // DIV容器ID style: svc.vectorStyle(), // 样式,这里是矢量瓦片样式 center: prj.toLngLat(prj.getMapExtent().center()), // 设置地图中心点 zoom: 2, // 设置地图缩放级别 pitch: 60, // 倾斜角度 renderWorldCopies: false // 不显示多屏地图 }); // 关联服务对象和投影对象 map.attach(svc, prj); ``` ::: tip 提示 唯杰地图也支持用openlayers、leaflet、maptalks来加载CAD图 如果需要用openlayers来开发,请参考示例 https://vjmap.com/demo/#/demo/map/openlayers/01olraster 如果需要用leaflet来开发,请参考示例 https://vjmap.com/demo/#/demo/map/leaflet/01leafletraster 如果需要用maptalks来开发,请参考示例 https://vjmap.com/demo/#/demo/map/maptalks/01maptalksraster ::: ## 获取地图范围 ![image-20230625112656838](https://vjmap.com/blogimages/image-20230625112656838.png) - 整个地图范围 map.getGeoBounds(1.0) // 正方形 - CAD图实际范围(整个地图范围的0.4倍) map.getGeoBounds(0.4) // 正方形 - CAD图的可视范围(有图的部分的范围) 方法1: 范围准确,速度稍慢 let dataBounds = await svc.cmdGetDrawBounds(); // 获取地图有数据的范围区域 方法2: 范围大致准确,速度快 svc.currentMapParam().drawBounds ## 地图交互 当鼠标在地图的某个实体上面时,需要高亮显示或者点击查看,在`矢量瓦片`模式下可调用 `enableVectorLayerHoverHighlight`;在`栅格瓦片`下,可调用 `enableLayerClickHighlight` ```js // 获取所有图层 const layers = svc.getMapLayers(); // 实体类型ID和名称映射 const { entTypeIdMap } = await svc.getConstData(); // 有高亮状态(鼠标在地图元素上时,会高亮) map.enableVectorLayerHoverHighlight((event, feature, layer) => { // 点击高亮实体回调事件 const prop = feature.properties; let content = `event: ${event}; feature: ${feature.id}; layer: ${layers[prop.layer].name}; type: ${entTypeIdMap[prop.type]}` message.info({ content, key: "info", duration: 5}); }) ``` ## 增加覆盖物或其它图层 地图提供了在地图之上绘制覆盖物的能力,比如点标记、纯文本标记、折线、圆、多边形、矩形等图形,在绘制中可以对点标记进行标题、图标等进行设置,对折线可以进行颜色、宽度等属性进行设置,对于面(圆、多边形、矩形)同样可以进行填充色、边框色、宽度等很多自定义属性进行设置。 增加一个marker覆盖物 ```js const mapBounds = map.getGeoBounds(0.6); // 获取地图地理范围,并进行比例缩放 let position = mapBounds.randomPoint(); // 在此地理范围内生成一个随机点坐标 let latLng = map.toLngLat(position); // 地理坐标转经纬度 let marker = new vjmap.Marker(); // 新建一个Marker marker.setLngLat(latLng).addTo(map); // 设置坐标并加入地图 ``` 创建很多正方形拉伸图层 ```js let lengthMin = mapBounds.width() / 200; // 随机生成正方形边长的最小长度 let lengthMax = mapBounds.width() / 100; // 随机生成正方形边长的最大长度 let geoDatas = []; // 数据 for(let i = 0; i < 100; i++) { const pts = []; const len = vjmap.randInt(lengthMin, lengthMax); // 在两个范围内随机生成一个长度 const p1 = mapBounds.randomPoint(); // 在此范围内随机生成一个点,做为正方形的右下角的点 const p2 = vjmap.geoPoint([p1.x, p1.y + len]); const p3 = vjmap.geoPoint([p1.x + len, p1.y + len]); const p4 = vjmap.geoPoint([p1.x + len, p1.y]); pts.push(p1, p2, p3, p4); // 正方形的四个点坐标 geoDatas.push({ points: map.toLngLat(pts), // 需要把地理坐标转化成经纬度去绘制 properties: { name: "square" + (i + 1), color: vjmap.randomColor(), type: "square", baseHeight: 0, height: prj.toMeter(vjmap.randInt(lengthMin * 10, lengthMax * 10)) // 高度,这里根据上面设置的最小和最大长度的十倍随机生成一个高度值 } }) } // 创建拉伸对象图层 let fillExtrusions = new vjmap.FillExtrusion({ data: geoDatas, // 数据 // 如果是hover状态时,用红色,非hover状态时,取属性中的'color'做为颜色值 fillExtrusionColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', ['get', 'color']], fillExtrusionOpacity: 0.8, // 透明度 fillExtrusionHeight:['get', 'height'], // 高度值 fillExtrusionBase: ['get', 'baseHeight'], // 基点高度,如需悬空,可改变此值 isHoverPointer: true, // 鼠标在上面时会改变鼠标形状 isHoverFeatureState: true // 鼠标在上面时会改变状态值,从而有高亮效果 }); fillExtrusions.addTo(map); // 增加至地图 // 点击事件 fillExtrusions.clickLayer(e => message.info(`您点击了第 ${e.features[0].id} 个,名称为 ${e.features[0].properties.name},颜色为 ${e.features[0].properties.color} 的 ${e.features[0].properties.type}`)) // 悬浮事件 fillExtrusions.hoverPopup(f => `

ID: ${f.properties.name}

Color: ${f.properties.color}`, { anchor: 'bottom' }); ```
// 新建地图服务对象,传入服务地址和token
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
	mapid: "sys_zp", // 地图ID (每一次打开此地图ID时,会根据fileid去获取dwg路径打开,之后会读取缓存打开)
	mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
	style: vjmap.openMapDarkStyle() // 服务器渲染样式
})
if (res.error) {
	console.error(res.error)
}
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(res.bounds);
// 地图对象
let map = new vjmap.Map({
	container: 'map', // DIV容器ID
	style: svc.vectorStyle(), // 样式,这里是矢量瓦片样式
	center: prj.toLngLat(prj.getMapExtent().center()), // 设置地图中心点
	zoom: 2, // 设置地图缩放级别
	pitch: 60, // 倾斜角度
	renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
// 获取所有图层
const layers = svc.getMapLayers();
// 实体类型ID和名称映射
const { entTypeIdMap } = await svc.getConstData();
// 有高亮状态(鼠标在地图元素上时,会高亮)
map.enableVectorLayerHoverHighlight((event, feature, layer) => {
	// 点击高亮实体回调事件
	const prop = feature.properties;
	let content = `event: ${event}; feature: ${feature.id}; layer: ${layers[prop.layer].name}; type: ${entTypeIdMap[prop.type]}`
	map.logInfo(content)
})
// 等待地图加载完成才能增加下面的其他图层
await map.onLoad();
// 增加一个marker覆盖物
const mapBounds = map.getGeoBounds(0.6); // 获取地图地理范围,并进行比例缩放
let position = mapBounds.randomPoint(); // 在此地理范围内生成一个随机点坐标
let latLng = map.toLngLat(position); // 地理坐标转经纬度
let marker = new vjmap.Marker(); // 新建一个Marker
marker.setLngLat(latLng).addTo(map); // 设置坐标并加入地图
// 创建很多正方形拉伸图层
let lengthMin = mapBounds.width() / 200; // 随机生成正方形边长的最小长度
let lengthMax = mapBounds.width() / 100; // 随机生成正方形边长的最大长度
let geoDatas = []; // 数据
for(let i = 0; i < 100; i++) {
	const pts = [];
	const len = vjmap.randInt(lengthMin, lengthMax); // 在两个范围内随机生成一个长度
	const p1 = mapBounds.randomPoint(); // 在此范围内随机生成一个点,做为正方形的右下角的点
	const p2 = vjmap.geoPoint([p1.x, p1.y + len]);
	const p3 = vjmap.geoPoint([p1.x + len, p1.y + len]);
	const p4 = vjmap.geoPoint([p1.x + len, p1.y]);
	pts.push(p1, p2, p3, p4); // 正方形的四个点坐标
	geoDatas.push({
		points: map.toLngLat(pts), // 需要把地理坐标转化成经纬度去绘制
		properties: {
			name: "square" + (i + 1),
			color:  vjmap.randomColor(),
			type: "square",
			baseHeight: 0,
			height: prj.toMeter(vjmap.randInt(lengthMin * 10, lengthMax * 10)) // 高度,这里根据上面设置的最小和最大长度的十倍随机生成一个高度值
		}
	})
}
// 创建拉伸对象图层
let fillExtrusions = new vjmap.FillExtrusion({
	data: geoDatas, // 数据
	// 如果是hover状态时,用红色,非hover状态时,取属性中的'color'做为颜色值
	fillExtrusionColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', ['get', 'color']],
	fillExtrusionOpacity: 0.8, // 透明度
	fillExtrusionHeight:['get', 'height'], // 高度值
	fillExtrusionBase: ['get', 'baseHeight'], // 基点高度,如需悬空,可改变此值
	isHoverPointer: true, // 鼠标在上面时会改变鼠标形状
	isHoverFeatureState: true // 鼠标在上面时会改变状态值,从而有高亮效果
});
fillExtrusions.addTo(map); // 增加至地图
// 点击事件
fillExtrusions.clickLayer(e => map.logInfo(`您点击了第 ${e.features[0].id} 个,名称为 ${e.features[0].properties.name},颜色为 ${e.features[0].properties.color} 的 ${e.features[0].properties.type}`))
// 悬浮事件
fillExtrusions.hoverPopup(f =>`<h3>ID: ${f.properties.name}</h3>Color: ${f.properties.color}`, { anchor: 'bottom' });


## 全部代码 ```js vjmap demo
``` --- # 文档AI助手(开发者必看) 现在`vjmap`和`vjcad` `技能文档`已发布,请点击下载`skills`:[https://vjmap.com/download/skills.zip](https://vjmap.com/download/skills.zip) MCP服务地址: `https://vjmap.com/server/aimcp/mcp` (所有产品) 唯杰地图 MCP 服务地址: `https://vjmap.com/server/aimcp/mcp?products=map2d` 唯杰地图加CAD扩展图层 MCP 服务地址: `https://vjmap.com/server/aimcp/mcp?products=map2d,mapext` 唯杰地图3维 MCP 服务地址: `https://vjmap.com/server/aimcp/mcp?products=map3d` 唯杰地图2/3维 MCP 服务地址: `https://vjmap.com/server/aimcp/mcp?products=map2d,map3d` vjcad MCP 服务地址: `https://vjmap.com/server/aimcp/mcp?products=vjcad` [唯杰智能体](https://vjmap.com/server/aiagent/app/agents) https://vjmap.com/server/aiagent/app/agents [唯杰地图文档AI搜索](https://vjmap.com/server/help/#/) https://vjmap.com/server/help/#/ [唯杰地图所有产品文档搜索](https://vjmap.com/server/aimcp/) https://vjmap.com/server/aimcp/ [唯杰地图文档MCP深度搜索](https://vjmap.com/server/_mapviewer?aidoc=true) https://vjmap.com/server/_mapviewer?aidoc=true `唯杰地图文档AI搜索`是为开发者打造的智能开发助手。在对VJMAP二次开发时,如何快速从庞大的API文档、示例代码和技术资料中找到准确的信息,一直是开发者面临的挑战。**唯杰地图文档AI搜索**应运而生,这是一个专为vjmap SDK开发者设计的智能知识库搜索系统,通过先进的MCP(Model Context Protocol)协议,为开发者提供前所未有的文档检索和AI辅助开发体验。 ## 📚 产品价值与开发者痛点解决 ### 开发者面临的文档检索难题 - **API文档分散**:vjmap SDK功能丰富,文档和示例分布在不同位置 - **示例代码难找**:需要特定功能时,很难快速定位到相关的JavaScript示例 - **语义理解缺失**:传统搜索只能匹配关键词,无法理解开发意图 - **上下文丢失**:代码片段缺乏完整的实现上下文 - **学习成本高**:新手开发者需要大量时间熟悉API体系 ### 产品核心价值 唯杰地图文档AI搜索通过以下方式革新开发体验: 1. **智能语义搜索**:理解开发意图,而非简单的关键词匹配 2. **MCP协议集成**:标准化的AI工具调用,无缝集成到开发流程 3. **实时AI对话**:像咨询技术专家一样获得开发指导 ## 🛠️ MCP协议:开发者的超级工具 ### 什么是MCP协议 MCP(Model Context Protocol)是一种标准化的AI工具调用协议,它让AI模型能够通过结构化的方式访问外部工具和数据源。 ### 核心MCP工具详解 vjmap帮助文档 MCP服务地址: `https://vjmap.com/server/aimcp/mcp` ## 如何使用MCP工具 可以使用`Cursor`(推荐)、`trae`等AI编辑器或`Cherry Studio`等软件中,加入`唯杰地图文档AI搜索MCP工具`即可。下面以`curosr`以例进行说明: 唯杰2D ```json { "mcpServers": { "vjdoc": { "url": "https://vjmap.com/server/aimcp/mcp?products=map2d" } } } ``` 如果要使用cad扩展图层,请加上 ```json { "mcpServers": { "vjdoc": { "url": "https://vjmap.com/server/aimcp/mcp?products=map2d,mapext" } } } ``` 唯杰3D ```json { "mcpServers": { "vjdoc": { "url": "https://vjmap.com/server/aimcp/mcp?products=map3d" } } } ``` 唯杰WebCAD ```json { "mcpServers": { "vjdoc": { "url": "https://vjmap.com/server/aimcp/mcp?products=vjcad" } } } ``` ![image-20250617212129751](https://vjmap.com/blogimages/202506172121833.png) ![image-20250617212148107](https://vjmap.com/blogimages/202506172121145.png) **使用示例** 在Cursor中与AI对话时,直接提问: ``` 帮我查找vjmap中创建标记点的示例代码 ``` ![image-20250617215016423](https://vjmap.com/blogimages/202506172150473.png) AI会自动调用MCP工具: - 搜索相关文档片段 - 获取完整的示例代码 - 提供详细的实现说明 注:如果AI没有自动调用MCP工具时,可以在提问下明确要求下如: ``` 请查询vjmap的帮助文档,如何创建一个Marker? ``` 文档搜索默认为唯杰地图2D, 如需唯杰地图3D文档,AI会自动判断。也可以强制指定。 ``` 请查询vjmap3d的帮助文档,如何创建一个Marker3D? ``` ![image-20250617214810000](https://vjmap.com/blogimages/202506172148057.png) ![](https://vjmap.com/blogimages/docaicursor.gif) ## 🛠️ AI 对话 **功能特色**: - **多模式切换**:一键切换AI对话、语义搜索、全文搜索三种模式 - **智能搜索框**:支持自然语言输入,自动适配不同搜索类型 - **知识库分类**:可选择唯杰地图2D、3D或全部文档库 - **深度思考模式**:可开启AI深度分析,展示推理过程 - **响应式布局**:自适应桌面和移动设备 官网地址: [唯杰地图文档AI搜索](https://vjmap.com/server/help/#/) https://vjmap.com/server/help/#/ ![image-20250617215235620](https://vjmap.com/blogimages/202506172152713.png) ![image-20250617215322261](https://vjmap.com/blogimages/202506172153345.png) ![image-20250617215452509](https://vjmap.com/blogimages/202506172154585.png) --- # html、Vue、React应用 ::: tip 提示 vue2、vue3、React、Html[示例代码下载](https://vjmap.com/download/vjmap_demo.zip) ** 强烈建议下载参考,入门开发的简单示例 ** ::: ## 安装 首先,确保您已安装 Node.js 和 npm、yarn 或 pnpm。然后在您的项目目录中运行以下命令来安装库: ### npm 安装 ``` sh npm install vjmap ``` ### yarn 安装 ``` sh yarn add vjmap ``` ### pnpm 安装 ``` sh pnpm add vjmap ``` ## 引入 引入指定模块: ```js import { Map } from 'vjmap'; // 引入样式 import "vjmap/dist/vjmap.min.css"; ``` 或引入全局模块: ```js import vjmap from 'vjmap'; // 引入样式 import "vjmap/dist/vjmap.min.css"; ``` ## 纯Html开发 对于不使用打包工具的纯 HTML 开发项目,您可以通过 `script` 标签引入库。 ```html vjmap const env = { serviceUrl: "https://vjmap.com/server/api/v1", accessToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MiwiVXNlcm5hbWUiOiJhZG1pbjEiLCJOaWNrTmFtZSI6ImFkbWluMSIsIkF1dGhvcml0eUlkIjoiYWRtaW4iLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjoxOTQyMzg5NTc0LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTYyNzAyODU3NH0.VQchVXxgjl5aCp3j3Uf5U2Mpk1NJNirH62Ys-8XOfnY" }; // 新建地图服务对象,传入服务地址和token let svc = new vjmap.Service(env.serviceUrl, env.accessToken) // 打开地图 let res = await svc.openMap({ mapid: "sys_zp", // 地图ID mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开 style: vjmap.openMapDarkStyle() // 服务器渲染样式 }) if (res.error) { console.error(res.error) } // 根据地图范围建立几何投影坐标系 let prj = new vjmap.GeoProjection(res.bounds); // 地图对象 let map = new vjmap.Map({ container: 'map', // DIV容器ID style: svc.vectorStyle(), // 样式,这里是矢量瓦片样式 center: prj.toLngLat(prj.getMapExtent().center()), // 设置地图中心点 zoom: 2, // 设置地图缩放级别 pitch: 0, // 倾斜角度 renderWorldCopies: false // 不显示多屏地图 }); // 关联服务对象和投影对象 map.attach(svc, prj); ``` ## Vue、React开发 Vue、React开发请下载示例工程参考开发 vue、React、Html[示例代码下载](https://vjmap.com/download/vjmap_demo.zip) ::: warning 提示 如果工程化开发时,提示如下错误 ![image-20240923111147721](https://vjmap.com/blogimages/202409231111747.png) 这是由于 `webpack` 或 `vite` 等工具对 `vjmap` sdk 二次混淆所引起的。请查看相关文档把 `vjmap` 排查,不要二次打包。 或者在import时加上 !号 如 ```js import vjmap from "!vjmap" ``` ::: --- # 移动端和桌面端开发 ## 移动端开发 唯杰地图完全兼容移动端查看。在每个示例中和云平台管理中都增加了手机扫一扫的功能,可直接扫描二维码在手机端查看效果。
移动端开发,如果是原生开发,请用`webview`基于h5开发。 ::: tip 提示 如果使用`uniapp`开发`小程序`时,因为地图div大量操作dom元素,需使用`uniapp`提供的`renderjs`模式。具体原因可查看文档 [https://dengzile.com/2020/10/%E5%9C%A8uni-app%E4%B8%AD%E4%BD%BF%E7%94%A8renderjs/](https://dengzile.com/2020/10/%E5%9C%A8uni-app%E4%B8%AD%E4%BD%BF%E7%94%A8renderjs/) ::: ## 桌面端开发(C#为例) 桌面端开发开发和移动端开发类似,需用`webview` Browser 加载html的思路来开发。 ### 效果 以 `C#` 为例,效果如下
### 代码 ```cs using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using CefSharp.WinForms; using CefSharp; namespace CefForm { public partial class Form1 : Form { public class AsyncHostObject { Form1 m_host; public AsyncHostObject(Form1 frm) { this.m_host = frm; } public void addMessage(string msg) { this.m_host.listBox1.Items.Add(msg); } } public Form1() { InitializeComponent(); chromiumWebBrowser1.Load("file:///d:/map/index.html"); ListBox.CheckForIllegalCrossThreadCalls = false; // 页面加载完毕后打开开发者工具 /* chromiumWebBrowser1.FrameLoadEnd += (s, eve) => { var browser = chromiumWebBrowser1.GetBrowser(); browser.ShowDevTools(); };*/ JsObjectResolver(); } private void JsObjectResolver() { // 由网页端 CefSharp.BindObjectAsync 触发 chromiumWebBrowser1.JavascriptObjectRepository.ResolveObject += (s, eve) => { var repo = eve.ObjectRepository; if (eve.ObjectName == "host") { repo.Register("host", new AsyncHostObject(this), isAsync: true, options: BindingOptions.DefaultBinder); } }; } private async void button1_Click(object sender, EventArgs e) { string strScript = String.Format("openMap('{0}')", "your dwg file path"); var result = await chromiumWebBrowser1.EvaluateScriptAsync(strScript); } private void button2_Click(object sender, EventArgs e) { setZoomDeltaAsync(1); } private void button3_Click(object sender, EventArgs e) { setZoomDeltaAsync(-1); } public async void setZoomDeltaAsync(int z) { string strScript = String.Format("var zoom = map.getZoom(); zoom += {0}; map.setZoom(zoom); zoom", z); var result = await chromiumWebBrowser1.EvaluateScriptAsync(strScript); if (result.Result == null) return; this.textBox1.Text = "当前级别:" + result.Result.ToString(); } } } ``` 前端相关代码 ```js // 地图服务对象 let svc = new vjmap.Service(env.serviceUrl, env.accessToken) // 打开地图 let res = await svc.openMap({ mapid: env.exampleMapId, // 地图ID mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开 style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式 }) if (res.error) { // 如果打开出错 message.error(res.error) } // 获取地图范围 let mapExtent = vjmap.GeoBounds.fromString(res.bounds); // 根据地图范围建立几何投影坐标系 let prj = new vjmap.GeoProjection(mapExtent); // 地图对象 let map = new vjmap.Map({ container: 'map', // DIV容器ID style: svc.rasterStyle(), // 样式,这里是栅格样式 center: prj.toLngLat(mapExtent.center()), // 设置地图中心点 zoom: 4, // 设置地图缩放级别, pitch: 60, // 倾斜角度 renderWorldCopies: false // 不显示多屏地图 }); // 关联服务对象和投影对象 map.attach(svc, prj); // 根据地图本身范围缩放地图至全图显示 //map.fitMapBounds(); await map.onLoad(); await CefSharp.BindObjectAsync("host"); map.on("zoomend", () => host.addMessage("缩放结束")) map.on("movestart", () => host.addMessage("平移开始")) map.on("moveend", () => host.addMessage("平移结束")) ``` --- # 三维应用 ## 介绍 [唯杰地图3D](https://vjmap.com/map3d) `VJMAP3D`是一款基于[threejs](https://threejs.org/)开发的三维可视化引擎框架。通过VJMAP3D提供的丰富的功能,可以在浏览器中创建出绚丽的3D可视化应用。 该框架既可做为一个单独的3D引擎用于数据可视化、产品展示、数字孪生、三维GIS等多个领域的可视化开发, 也可以结合`唯杰地图VJMAP`做为一个`3D`图层,能够在保留二维图形信息的直观性同时,融入三维空间的深度感知和交互性,为用户带来更丰富、立体和沉浸式的视觉体验及数据分析能力。 具有如下特点: - 完全兼容`AutoCAD`格式的`DWG`文件展示,无需转换 - 兼容`唯杰地图`,同一套框架,同一套代码,既兼容`VJMAP`的`3D`图层,又可单独做为三维引擎使用 - 基于`threejs`开发,兼容`threejs`写法。`threejs`拥有庞大的开发者社区和丰富的教程资源,能利用`threejs`生态资源 - 易用性高:学习门槛低,提供丰富的API接口,便于快速上手开发3D应用。 - 丰富的内置功能:提供了预设对象和材质,以及粒子系统、后期处理等高级特性,便于快速实现复杂视觉效果。 - 交互设计:提供丰富的交互组件和事件处理机制,增强用户体验和应用互动性。支持场景中的对象拾取与交互,简化了交互逻辑的实现。 - 扩展性强:基于模块化开发,支持自定义扩展,满足多样化开发需求。 `vjmap3d`既可做为一个单独的3D引擎, 也可以基于[唯杰地图VJMAP](https://vjmap.com/doc/)做为一个`3D`图层,能够在保留二维图形信息的直观性同时,融入三维空间的深度感知和交互性,为用户带来更丰富、立体和沉浸式的视觉体验及数据分析能力。 [唯杰地图3D官网](https://vjmap.com/map3d/) [唯杰地图3D文档](https://vjmap.com/map3d/docs/) [唯杰地图3D开发示例](https://vjmap.com/map3d/demo/#/gallery/map) [唯杰地图3D案例](https://vjmap.com/map3d/example.html) ## 做为唯杰地图的三维图层 其步骤为: - 引入`vjmap`sdk,创建`vjmap.Map`地图对象,具体示例可查看 [https://vjmap.com/demo](https://vjmap.com/demo) - 创建 [MapThreeLayer](https://vjmap.com/map3d/api/classes/MapThreeLayer.html) 3d图层 - 通过`map.addLayer`增加3d图层,通过3d图层获取[App](https://vjmap.com/map3d/api/classes/App.html)对象。 示例如下:
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: env.exampleMapId, // 地图ID
    // @ts-ignore
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapLightStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    showError(res.error)
    return;
}
// 获取地图范围
let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapBounds);
//
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapBounds.center()), // 设置地图中心点
    zoom: 2, // 设置地图缩放级别
    pitch: 45,
    antialias: true, // 反锯齿
    renderWorldCopies: false // 不显示多屏地图
});
//
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();
//
// 创建3d图层
let mapLayer = new vjmap3d.MapThreeLayer(map, {
    stat: {
        show: true,
        left: "0"
    }
});
map.addLayer(new vjmap.ThreeLayer({ context: mapLayer }))
let app = mapLayer.app;
//
// 在CAD图中心上方创建一个Mesh
let cadLen = mapBounds.width() / 50; // 取cad图长度的1/50
let len = vjmap3d.toDist(cadLen); // cad长度转世界坐标长度
let box = new THREE.BoxGeometry(len, len, len)
const material = new THREE.MeshStandardMaterial({
    color: "#f00",
    side: THREE.DoubleSide
});
let mesh = new THREE.Mesh(box, material);
let cadPos = [mapBounds.center().x, mapBounds.center().y, cadLen]; // mesh在cad中坐标
//
let worldPos = vjmap3d.toWorld(cadPos);// cad坐标转世界坐标
mesh.position.copy(worldPos);
//app.scene.add(mesh) // 可以直接把 mesh 加进场景中不与实体相关联
// 创建一个实体与mesh相关联
let entity = vjmap3d.Entity.fromObject3d(mesh);
entity.addTo(app);
// 增加一个事件模块,用来高亮提示
entity.add(vjmap3d.EventModule, {
    hoverSelect: true,
    hoverHtmlText: '我是用vjmap3d创建的Box',
    popupOptions: {
        anchor: "bottom"
    }
});
[点击查看 做为唯杰地图的三维图层具体 案例效果]( https://vjmap.com/effect/design3d/?map2d=true) ![image-20240923105036677](https://vjmap.com/blogimages/202409231050875.png) ## 创建CAD为底图的3D应用 通过 `vjmap3d` 中的 [MapViewEntity](https://vjmap.com/map3d/api/classes/MapViewEntity.html) 来创建一个地图实体。
// 创建一个服务类,与服务连接
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    camera: {  // 相机设置
        viewHelper: { enable: true } // 是否显示视角指示器
    }
})
let res = await app.svc.openMap({
        mapid: env.exampleMapId, // 地图ID,(请确保此ID已存在,可上传新图形新建ID)
        mapopenway: vjmap3d.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
        style: vjmap3d.openMapDarkStyle()
});
if (res.error) {
    showError(res.error);
    return;
}
// 获取当前地图的可视范围
let clipPolygon = vjmap3d.GeoBounds.fromString(res.drawBounds).toPointArray();
let provider = new vjmap3d.MapProvider(
    [
        {
            url: app.svc.rasterTileUrl()
        }
    ],
    {
        transparent: true
    }
);
let mapBounds = vjmap3d.GeoBounds.fromString(res.bounds)
let scale = 100;
let mapviewEnt = new vjmap3d.MapViewEntity({
    provider,
    baseScale: scale,
    mapBounds: mapBounds.toArray(),
    clipPolygon //  如果透明的话,不能使用裁剪区载
});
mapviewEnt.addTo(app);
//
// 在CAD图中心上方创建一个Mesh
let cadLen = mapBounds.width() / 50; // 取cad图长度的1/50
let len = vjmap3d.toDist(cadLen); // cad长度转世界坐标长度
let box = new THREE.BoxGeometry(len, len, len)
const material = new THREE.MeshStandardMaterial({
    color: "#f00",
    side: THREE.DoubleSide
});
let mesh = new THREE.Mesh(box, material);
let cadPos = [mapBounds.center().x, mapBounds.center().y, cadLen]; // mesh在cad中坐标
//
let worldPos = vjmap3d.toWorld(cadPos);// cad坐标转世界坐标
mesh.position.copy(worldPos);
//app.scene.add(mesh) // 可以直接把 mesh 加进场景中不与实体相关联
// 创建一个实体与mesh相关联
let entity = vjmap3d.Entity.fromObject3d(mesh);
entity.addTo(app);
// 增加一个事件模块,用来高亮提示
entity.add(vjmap3d.EventModule, {
    hoverSelect: true,
    hoverHtmlText: '我是用vjmap3d创建的Box',
    popupOptions: {
        anchor: "bottom"
    }
});
[点击查看 做为唯杰地图的三维图层具体 案例效果]( https://vjmap.com/effect/design3d/) ![image-20240923110109457](https://vjmap.com/blogimages/202409231101523.png) ::: tip 总结 使用`vjmap3d`创建`3d应用` 和 `做为唯杰地图的三维图层` 使用,除了初始化代码不同外,业务代码基本上可复用,做到同一套代码用在不同的框架上。 需要说明的是:当`vjmap3d`做为唯杰地图的三维图层使用时,其相机是由`唯杰地图`控制的,`vjmap3d`的相机会和`唯杰地图`相机保持同步。因此在这种模式下,不能直接操作`vjmap3d`的相机。同时因为`唯杰地图`暂不支持后期处理,所以在这种模式下,`vjmap3d`的后期处理功能会自动禁用。 ::: --- # 互联网地图应用 `唯杰地图` `vjmap`做为一款地图引擎, 不仅能加载CAD地图瓦片,也可以直接加载互联网地图瓦片,同时也支持CAD图和互联网地图叠加显示。 ## 直接加载互联网地图 通过给定互联网地图`WMTS`的瓦片地址加载互联网地图。
// --天地图地图--
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 根据地图范围建立经纬度投影坐标系
let prj = new vjmap.LnglatProjection();
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: {
        version: svc.styleVersion(),
        glyphs: svc.glyphsUrl(),
        sources: {
            tdt1: {
                type: 'raster',
                // 影像
                // tiles: ["https://t3.tianditu.gov.cn/DataServer?T=img_w&X={x}&Y={y}&L={z}&tk=7baeffb96bf61861b302d0f963cfda66"],
                // 道路
                tiles: ["https://t3.tianditu.gov.cn/DataServer?T=vec_w&X={x}&Y={y}&L={z}&tk=7baeffb96bf61861b302d0f963cfda66"]
            },
            tdt2: {
                type: 'raster',
                tiles: ["https://t3.tianditu.gov.cn/DataServer?T=cva_w&X={x}&Y={y}&L={z}&tk=7baeffb96bf61861b302d0f963cfda66"],
            }
        },
        layers: [{
            id: 'tdt1',
            type: 'raster',
            source: 'tdt1',
        },{
            id: 'tdt2',
            type: 'raster',
            source: 'tdt2',
        }]
    },
    center: prj.toLngLat([116.3912, 39.9073]),
    zoom: 8,
    pitch: 0,
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
// 根据地图本身范围缩放地图至全图显示
//map.fitMapBounds();
await map.onLoad();
const co = prj.toLngLat([116.3912, 39.9073])
const marker = new vjmap.Marker({color: "red"})
marker.setLngLat(co).addTo(map)
// 地形
// map.addSource('dem', {
//     type: 'raster-dem',
//     tiles: ["https://api.mapbox.com/v4/mapbox.terrain-rgb/{z}/{x}/{y}.pngraw?access_token=pk.eyJ1IjoiZnJhemllcm1hcmsiLCJhIjoiY2wzOTBhZzJiMDFwejNqbzJyMGs0YmZ5NCJ9.2VB9C63HoxzjCpCmbhga9A"],
// })
// map.setTerrain({ source: 'dem', exaggeration: 1})
::: warning 提示 唯杰地图不提供互联网地图瓦片数据,如需离线部署互联网地图服务, 请自行下载互联网地图离线瓦片数据或通过第三方工具下载。 ::: ## 互联网地图与CAD图叠加(为互联网地图为底图) 此场景是以互联网地图为底图(以互联网地图为坐标系),通用调用唯杰地图CAD图的 `WMS` 服务适配互联网地图来进行叠加显示。 如果CAD图有地理坐标系,如本身是2000坐标系的图,则可以自动叠加。 如果CAD图本身没有坐标系,只能通过人为指定三个以上的公共点进行校准,计算出四参数(平移xy、旋转、缩放)来进行叠加。 具体原理可查看 [坐标转换](/guide/coordtransform.html) 文档。 此场景适合于业务数据大部分是互联网地图数据,CAD图只是叠加查看效果。
// --天地图与CAD图叠加[天地图为底图]--天地图与CAD图叠加,以天地图为坐标系
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 根据地图范围建立经纬度投影坐标系
let prj = new vjmap.LnglatProjection();
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: {
        version: svc.styleVersion(),
        sources: {
            tdt1: {
                type: 'raster',
                tiles: ["https://t3.tianditu.gov.cn/DataServer?T=vec_w&X={x}&Y={y}&L={z}&tk=7baeffb96bf61861b302d0f963cfda66"],
                // 要使用影像请用这个地址
                // tiles: ["https://t3.tianditu.gov.cn/DataServer?T=img_w&X={x}&Y={y}&L={z}&tk=7baeffb96bf61861b302d0f963cfda66"]
            },
            tdt2: {
                type: 'raster',
                tiles: ["https://t3.tianditu.gov.cn/DataServer?T=cva_w&X={x}&Y={y}&L={z}&tk=7baeffb96bf61861b302d0f963cfda66"],
            }
        },
        layers: [{
            id: 'tdt1',
            type: 'raster',
            source: 'tdt1',
        },{
            id: 'tdt2',
            type: 'raster',
            source: 'tdt2',
        }]
    },
    center: prj.toLngLat([117.21242940465396, 39.196783504641644]),
    zoom: 15,
    pitch: 0,
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
// 根据地图本身范围缩放地图至全图显示
//map.fitMapBounds();
await map.onLoad();
// 先获取地图元数据来获取图层样式
let cadMapId = "sys_cad2000"
let style = await svc.createStyle({
    backcolor: 0xFFFFFF // 浅色主题
}, cadMapId)
let wmsurl = svc.wmsTileUrl({
    mapid: cadMapId,
    version:"v1",
    layers: style.stylename,
    srs: "EPSG:3857", // 底图是天地图坐标系
    crs: "EPSG:4548", // cad图是2000坐标  可通过前两位获取 vjmap.transform.getEpsgParam(vjmap.transform.EpsgCrsTypes.CGCS2000, 39).epsg
    // fourParameter: "-39000000,0,1,0"// 此图坐标前面没有加带系,需要加上带系的偏移量,用四参数调节位置
})
map.addSource('wms-test-source', {
    'type': 'raster',
    'tiles': [
        wmsurl
    ],
    'tileSize': 256
});
map.addLayer({
        'id': 'wms-test-layer',
        'type': 'raster',
        'source': 'wms-test-source',
        'paint': { "raster-opacity": 1 }
    }
);
map.addControl(new vjmap.MousePositionControl({showZoom: true}));
## CAD图与互联网地图叠加(为CAD图为底图) 此场景是以CAD为底图(以CAD图为坐标系),通用调用唯杰地图提供的互联网地图的`WMS`服务来适配CAD地图来进行叠加显示。 如果CAD图有地理坐标系,如本身是2000坐标系的图,则可以自动叠加。 如果CAD图本身没有坐标系,只能通过人为指定三个以上的公共点进行校准,计算出四参数(平移xy、旋转、缩放)来进行叠加。 具体原理可查看 [坐标转换](/guide/coordtransform.html) 文档。 此场景适合于业务数据大部分是CAD图数据,互联网地图只是叠加查看效果。
// --CAD图与互联网图叠加--CAD图与互联网图叠加显示,以CAD图为坐标系
// 假设我们希望CAD全图显示是对应级别为8级,这样地图缩小后,互联网地图还会显示
let initZoom = 8;
let style = {
    backcolor: 0, // div为深色背景颜色时,这里也传深色背景样式
    clipbounds: Math.pow(2, initZoom) // 只传值,不传范围,表示之前的范围放大多少倍
}
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
let res = await svc.openMap({
    mapid: "sys_cad2000",
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style:  style
})
if (res.error) {
    consoloe.error(res.error)
}
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
let prj = new vjmap.GeoProjection(mapExtent);
let center = mapExtent.center();
let map = new vjmap.Map({
    container: 'map', // container ID
    style: svc.rasterStyle(),
    center: prj.toLngLat(center),
    zoom: 10,
    renderWorldCopies: false
});
map.attach(svc, prj);
await map.onLoad();
map.addControl(new vjmap.NavigationControl());
map.addControl(new vjmap.MousePositionControl({showZoom: true}));
// 如果x是8位,可通过前两位获取 如 vjmap.transform.getEpsgParam(vjmap.transform.EpsgCrsTypes.CGCS2000, 39).epsg
// 如果x是6位,是通过下面此工具 https://vjmap.com/demo/#/demo/map/web/03webzgetepsg,把地图移动至cad图位置的省市,点击获取当前地图中心经纬度, 计算EPSG,找一个与 CAD图距离相近的EPSG
let epsg = "EPSG:4509" // cad图的epsg
// 增加高德地图底图
const addGaodeMap = async (isRoadway) => {
    const tileUrl = svc.webMapUrl({
        tileCrs: "gcj02",
        tileUrl:  isRoadway ? [
                "https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}"
            ] :
            /* 如果用影像 */
            [
                "https://webst0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=6&x={x}&y={y}&z={z}",
                "https://webst0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}"
            ],
        tileSize: 256,
        tileRetina: 1,
        tileMaxZoom: 18,
        tileShards: "1,2,3,4",
        tileToken: "",
        tileFlipY: false,
        mapbounds: res.bounds,
        srs: epsg
    })
    map.addSource('web-gaode-source', {
        'type': 'raster',
        'tiles': [
            tileUrl
        ],
        'tileSize': 256
    });
    map.addLayer({
            'id': 'web-gaode-layer',
            'type': 'raster',
            'source': 'web-gaode-source',
            'paint': { "raster-opacity": 1 }
        }
    );
    let layers = map.getStyle().layers
    // 把这个图层放至所有图层的最下面
    map.moveLayer('web-gaode-layer', layers[0].id)
    // cad坐标与高德坐标相互转换示例
    let webCo = await cad2webCoordinate(center, false); // cad转高德
    let cadCo = await web2cadCoordinate(webCo, false); // 高德转cad
    console.log(center, webCo, cadCo)
}
// 增加天地图图底图
const addTiandituMap = async (isRoadway) => {
    const tileUrl = svc.webMapUrl({
        tileCrs: "wgs84",
        tileUrl: [
            isRoadway ? "https://t{s}.tianditu.gov.cn/DataServer?T=vec_w&X={x}&Y={y}&L={z}&tk={t}" :
                /* 如果用影像 */"https://t{s}.tianditu.gov.cn/DataServer?T=img_w&X={x}&Y={y}&L={z}&tk={t}",
            "https://t{s}.tianditu.gov.cn/DataServer?T=cva_w&X={x}&Y={y}&L={z}&tk={t}"
        ],
        tileSize: 256,
        tileRetina: 1,
        tileMaxZoom: 18,
        tileShards: "1,2,3,4,5,6",
        tileToken: [
            "6d53378dc5f7dbef8d84ffdd2b54139b", // 有可能有一个token会导致请求过多而返回失败,可以多写几个,失败时会尝试后面的token
            "69eb2fa0de3b2a668f1ef603a3f8bc73"
        ],
        tileFlipY: false,
        mapbounds: res.bounds,
        srs: epsg
    })
    map.addSource('web-tdt-source', {
        'type': 'raster',
        'tiles': [
            tileUrl
        ],
        'tileSize': 256
    });
    map.addLayer({
            'id': 'web-tdt-layer',
            'type': 'raster',
            'source': 'web-tdt-source',
            'paint': { "raster-opacity": 1 }
        }
    );
    let layers = map.getStyle().layers
    // 把这个图层放至所有图层的最下面
    map.moveLayer('web-tdt-layer', layers[0].id)
    // cad坐标与天地图坐标相互转换示例
    let webCo = await cad2webCoordinate(center, true); // cad转天地图
    let cadCo = await web2cadCoordinate(webCo, true); // 天地图转cad
    console.log(center, webCo, cadCo)
}
const removeMapLayer = ()=> {
    // 删除source及source下面的所有图层
    map.removeSourceEx(['web-gaode-source', 'web-tdt-source'])
}
// cad转web坐标,isWgs84是否为wgs84 4326坐标,如天地图;否的话为gcj02火星坐标,如高德地图
const cad2webCoordinate = async (pt, isWgs84) => {
    // 通过坐标转换把转成wgs84 4326坐标
    let res = await svc.cmdTransform(epsg,"EPSG:4326",pt); // 如果不想调服务转,也可以在前端用proj4库
    // 如果为火星坐标,还需要从4326转火星
    let webPt = res[0];// 返回的第1条结果
    if (isWgs84 === false) {
        webPt = vjmap.transform.convert(webPt, vjmap.transform.CRSTypes.EPSG4326, vjmap.transform.CRSTypes.AMap)
    }
    return webPt;
}
// web转cad坐标,isWgs84是否为wgs84 4326坐标,如天地图;否的话为gcj02火星坐标,如高德地图
const web2cadCoordinate = async (pt, isWgs84) => {
    // (上面计算过程的逆过程), 如果为火星坐标,还需要从火星转4326,再通过坐标转换把wgs84 4326转成cad图坐标
    if (isWgs84 === false) {
        pt = vjmap.transform.convert(pt, vjmap.transform.CRSTypes.AMap, vjmap.transform.CRSTypes.EPSG4326);
    }
    // 通过坐标转换把cad epsg转成wgs84 4326坐标
    let res = await svc.cmdTransform("EPSG:4326",epsg,vjmap.geoPoint(pt)); // 如果不想调服务转,也可以在前端用proj4库
    let cadPt = res[0];// 返回的第1条结果
    return cadPt
}
map.on("zoomend", async ()=> {
    let center = map.fromLngLat(map.getCenter());// 得到cad地图地理中心坐标点
    let gcj02Web = await cad2webCoordinate(center, false);// 转高德坐标
    let wgs84Web = await cad2webCoordinate(center, true);// 转天地图坐标
    let content = `当前地图中心点坐标: CAD坐标:${center.x.toFixed(4)},${center.y.toFixed(4)}; 高德坐标:${gcj02Web[0].toFixed(4)},${gcj02Web[1].toFixed(4)}; 天地图坐标:${wgs84Web[0].toFixed(4)},${wgs84Web[1].toFixed(4)}`;
    console.log({ content, key: "info", duration: 3});
})
addGaodeMap(true);
::: tip 提示 更多互联网地图叠加显示的介绍请查看 [互联网地图](/guide/webmap.html) 文档。 ::: --- # 地图相关知识 ## 地图服务 (Service) 唯杰地图服务主要用于加载解析`AutoCAD` 的 `DWG` 格式文件、影像文件如`tiff`。并发布成 GIS 服务, 提供`栅格瓦片`、`矢量瓦片`的 `WMS` 或 `WMTS` 服务。同时提供`空间数据查询`功能、与`互联网地图叠加`、对`CAD`图进行`创建`、`修改`等功能。 ## 地图 (Map) 在 `唯杰地图` `vjmap`sdk 中,**地图**是核心对象,负责在网页上显示地图并控制用户的交互。它不仅仅是加载显示`CAD`图做为底图,还是一个高度动态的可视化工具,用户可以通过拖拽、缩放、旋转等操作与地图互动。 每张地图可以包含多个不同的图层(Layers)。这些图层以不同的形式展示数据,可以是多边形、多段线、点符号、用户自定义标记等。地图的样式可以通过 JSON 格式定义,用户可以自定义视觉元素,比如颜色、透明度、字体等。 ### 关键属性: - **样式 (Style)**:定义地图的外观,包括图层的顺序、颜色、字体等。 - **中心点 (Center)**:地图的初始显示位置,通常由经纬度坐标决定。 - **缩放级别 (Zoom)**:控制地图的缩放程度,数值越大,显示的区域越详细。 下图是 地图的组成结构,通过下图我们能对地图有一个大致的了解,能加快开发者对地图 JS API 功能的理解。
--- ## 数据源 (Source) **数据源**是地图的基础,负责为地图上的图层提供地理数据。`唯杰地图` `vjmap`sdk 支持多种数据源类型,每种数据源都可以为地图提供不同类型的空间数据,例如矢量瓦片、栅格瓦片或者 GeoJSON 数据。 数据源分为以下几类: - **矢量数据源 (Vector Source)**:包含基于CAD图矢量格式的地理信息等。矢量数据可以在不同的缩放级别下保持高质量的显示效果。 - **栅格数据源 (Raster Source)**:栅格数据由像素组成,不能像矢量数据那样进行无损缩放,但适合处理图像类数据。 - **GeoJSON 数据源**:GeoJSON 是一种基于 JSON 格式的地理数据标准,主要用于描述地理特征及其属性信息。它非常适合动态添加和管理小规模的地理数据集。 - **图像数据源 (Image Source)**:允许将静态图片作为图层覆盖在地图上,常用于显示自定义的地图标记或者注释信息。 --- ## 图层 (Layer) **图层**决定了地图中数据的显示方式。每个图层与一个或多个数据源关联,并控制这些数据在地图上的视觉呈现方式。图层是地图的核心组成部分,可以叠加、隐藏、修改,从而灵活调整地图的表现。 图层的类型多种多样,常见的包括: - **填充图层 (Fill Layer)**:用于渲染多边形区域,如建筑物、湖泊等。 - **线条图层 (Line Layer)**:用于绘制线性特征,例如道路、边界线等。 - **符号图层 (Symbol Layer)**:用于显示图标或文字标记,适合用于地点标记或者路标。 - **圆形图层 (Circle Layer)**:用于显示基于点数据的圆形标记,常用于显示城市位置等。 - **栅格图层 (Raster Layer)**:用于显示栅格图像。 - **热力图层 (Heatmap Layer)**:根据数据密度生成热力图,适合显示事件频率或数据聚集情况。 ### 图层的工作机制: 每个图层都可以根据缩放级别、地图区域等条件动态变化。当用户交互改变地图的视角或缩放时,图层的显示效果也会随之调整。例如,在低缩放级别时,地图可能仅显示国家边界,但当用户放大时,会显示道路和建筑等更详细的地理信息。 --- ## 样式 (Style) **样式**是指地图的视觉设计,包括地图背景颜色、字体、图标、线条的粗细等。`唯杰地图` `vjmap`sdk 使用 JSON 文件来定义地图的样式,称为样式文件(style file)。该文件定义了地图的整体布局以及如何从数据源中提取数据并应用到不同的图层。 ### 样式的组成部分: - **版本 (Version)**:样式的版本。 - **来源 (Source)**:定义地图中使用的所有数据源。 - **图层 (Layers)**:定义如何在地图中展示数据源的内容。 --- ## 坐标系和投影 地图投影指的是将地球球面的经纬度坐标映射到地图平面坐标的变换和映射关系,地图使用 Web 墨卡托投影,也支持经纬度投影。 --- ## 交互功能 (Interactions) `唯杰地图` `vjmap`sdk 提供了一系列交互功能,允许用户对地图进行拖动、缩放、旋转等操作。这些功能使得地图在 Web 应用中不仅仅是一个静态的展示工具,而是一个可以与用户互动的动态对象。 ### 常见的交互方式: - **拖动 (Drag)**:通过拖拽地图可以改变地图的视角,查看不同的区域。 - **缩放 (Zoom)**:用户可以通过滚动鼠标或双击来放大或缩小地图,查看不同的细节层次。 - **旋转 (Rotate)**:用户可以通过触控或鼠标操作来旋转地图,从不同角度查看地图。 - **倾斜 (Pitch)**:允许用户改变地图的视角,从而在一个倾斜的视图中查看三维建筑物或地形。 开发者也可以根据需求,禁用或启用这些交互功能,以便更好地控制用户体验。 --- ## 动态数据与事件 `唯杰地图` `vjmap`sdk 允许开发者动态加载和更新地图数据,从而实现实时的地图更新。例如,可以通过 API 将新的标记或图层添加到地图中,或者从外部数据源加载实时的位置信息。 此外,地图也可以响应用户的各种事件,比如点击、拖动、缩放等。通过监听这些事件,开发者可以创建更加互动的应用,如弹出窗口、实时数据显示等。 ## 地图容器 Container 在准备阶段创建的指定了id的div对象,这个div将作为承载所有图层、点标记、矢量图形、控件的容器。 ## 矢量图层 选择地图样式或完全自定义您自己的地图和数据。我们的地图以超高帧率渲染 - 使地图可以流畅地响应用户反馈或脚本事件。 ## 矢量图形 矢量图形,一般覆盖于底图图层之上,通过矢量的方式(路径或者实际大小)来描述其形状,用几何的方式来展示真实的地图要素,会随着地图缩放而发生视觉大小的变化,但其代表的实际路径或范围不变,如上图中红框内的折线、圆、多边形等。 ## 栅格图层 Layer 地图提供了栅格图层渲染接口,可以绘制卫星影像、山体阴影图、自己开发的瓦片图等。 ## 点标记 Marker 点标记是用来标示某个位置点信息的一种地图要素,覆盖于图层之上。如图中蓝色方框中的两个点状要素。其在屏幕上的位置会随着地图的缩放和中心变化而发生改变,但是会与图层内的要素保持相对静止。 普通的点标记 (即 Marker ) ,支持将自定义图标和DOM元素作为展示内容。除了 Marker 之外为了满足不同的场景,地图 JS API 另外提供了丰富的点标记类型,比如圆点标记 、文本标记。同时我们提供了海量点、点聚合来满足大量点标记的展示需求。 ## 信息窗体 Popup 信息窗体是用来描述某个位置点详细信息的一种地图要素,覆盖于图层之上,支持位置点的偏移调整和信息的屏幕显示方向调整。 地图中的Popup最终的呈现形式是DOM元素,支持将自定义DOM元素作为展示内容,支持Popup内部元素的自定义事件绑定。 ## 几何图形编辑 Draw 地图提供了点、线、面、圆、椭圆、扇形、曲线面、复杂箭头等几何图形编辑功能,支持图形二次编辑,并提供了相应的接口,可以获取图形编辑后的几何。 ## 地图控件 Control 控件浮在所有图层和地图要素之上,用于满足一定的交互或提示功能。一般相对于地图容器静止,不随着地图缩放和中心变化而发生位置的变化。如上图中绿色方框中的比例尺和级别控件。 ## 常用名词 ### 地图级别 级别与地图的比例尺成正比,每增大一级,地图的比例尺也增大一倍,地图显示的越详细。 ### 几何坐标 GeoPoint 是指CAD图上面的坐标 ### 经纬度 LngLat 坐标通常指经纬度坐标,地图的坐标范围大致为:东西经180度(-180——180,西半球为负,东半球为正),南北纬85度(-85——85,北半球为正,南半球为负)。 ### 像素坐标 Point 是指设备屏幕上面的像素坐标值 ### 底图 严格意义上,底图指处于所有图层和图形最下方的一个图层。 ### 地图要素 Feature 严格意义的地图要素指的是展示在地图上的地理要素。开发者自定义的点标记、矢量图形也可以看做是一种地图要素。 ### 标注 我们习惯将底图上自带的标示一定信息的文字或图标称为标注,比如 POI 标注等。 ### 互联网地图 是指天地图、高德地图、百度地图、OSM等提供互联网地图服务的地图。 --- # 栅格瓦片与矢量瓦片 # 瓦片地图 > 概念: 瓦片地图金字塔模型是一种多分辨率层次模型,从瓦片金字塔的底层到顶层,分辨率越来越低,但表示的地理范围不变。首先确定地图服务平台所要提供的缩放级别的数量N,把缩放级别最高、地图比例尺最大的地图图片作为金字塔的底层,即第0层,并对其进行分块,从地图图片的左上角开始,从左至右、从上到下进行切割,分割成相同大小(比如256x256像素)的正方形地图瓦片,形成第0层瓦片矩阵;在第0层地图图片的基础上,按每像素分割为2×2个像素的方法生成第1层地图图片,并对其进行分块,分割成与下一层相同大小的正方形地图瓦片,形成第1层瓦片矩阵;采用同样的方法生成第2层瓦片矩阵;…;如此下去,直到第N一1层,构成整个瓦片金字塔。 ![image-20211014151004827](https://vjmap.com/blogimages/image-20211014151004827.png) 地图缓存技术是一种非常有效的提高在线地图访问效率的方式,用空间换取时间的方式。按地图缓存的分类可分为 **栅格瓦片** 和 **矢量瓦片** 。 # 栅格瓦片 概念:将地图中所有图层都切分并存储为栅格格式的地图瓦片。 优点:使用最广,技术成熟 栅格瓦片的原理与机制: 栅格瓦片采用四叉树金字塔模型的分级方式,将地图切割成无数大小相等的矩形栅格图片,由这些矩形栅格图片按照一定规则拼接成不同层级的地图显示。 image-20211014151137896 # 矢量瓦片 概念:矢量图层以矢量瓦片的形式进行切分和存储。 优点:创建效率高,传输和渲染速度快,前端可自定义渲染样式 矢量瓦片的原理与机制: 矢量瓦片类似栅格瓦片,是将矢量数据用多层次模型分割成矢量要素描述文件存储在服务器端,再到客户端根据指定样式进行渲染绘制地图,在单个矢量瓦片上存储着投影于一个矩形区域内的几何信息和属性信息。当客户端通过分布式网络获取矢量瓦片、地图标注字体、图标、样式文件等数据后,最终在客户端进行渲染输出地图。 矢量瓦片数据组织分成两层,一层是地图表达范围内的瓦片数据集组织模型,另一层是单个瓦片内要素的组织模型。 矢量瓦片数据集的组织模型类似栅格瓦片金字塔模型,包含坐标系、投影方式、瓦片编号已实现任意精度、空间位置与矢量瓦片的对应关系,并被栅格瓦片规范相互兼容,这样方便将矢量瓦片转换成栅格瓦片,毕竟两者采用相似的投影方式和瓦片编号方式。 单个瓦片要素的组织模型将几何信息和属性信息分开存储,几何信息主要包括点、线、面和未知要素类,元数据信息包含了图层属性和要素属性。 矢量瓦片的数据内容示例如下: image-20211014151836057 ## 栅格瓦片和矢量瓦片的对比 | 项目 | **矢量瓦片** | **栅格瓦片** | | ---- | ---- | ---- | | 地图瓦片 | 将矢量数据通过不同的描述文件来组织和定义,在客户端实时解析数据完成绘制 | 预先在服务端绘制好固定的PNG或其他格式的图片集合 | | 瓦片体量 | 小 | 大 | | 生成瓦片效率 | 高 | 低 | | 更新机制 | 持平 | 持平 | | 样式修改 | 支持 | 不支持 | | 前端技术要求 | 高(HTML5) | 低 | | 显示差异 | 有差异 | 无差异 | | 成熟度 | 一般 | 高 | | 应用场景 | 特殊(移动端、风格修改) | 广泛 | | 管理机制 | 持平 | 持平 | | | 优势 | 劣势 | | -------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | 矢量瓦片 | 瓦片占用空间低,瓦片切图效率高,渲染地图效果快,可以随时动态调整地图样式,地图分辨率高。 | 对客户端性能要求比较高,对旧设备兼容性存在问题。 图形比较大时,加载稍慢 | | 栅格瓦片 | 瓦片提前渲染,对客户端性能要求低,性能稳定。 | 瓦片占用空间高,瓦片切图效率低,无法随时动态调整地图样式,地图分辨率低,加载速度比较慢。 | # 实现 [唯杰地图vjmap](https://vjmap.com/) 为CAD图WebGIS可视化显示开发提供的一站式解决方案, 完全兼容dwg格式,23d效果完美切换,高性能webgl渲染,个性化地图,跨平台私有化部署,矢量栅格瓦片全支持, 实现把CAD地图通过栅格瓦片或矢量瓦片Web展示,只需两步: 第一步:设置渲染方式为几何渲染 `GeomRender` 第二步:设置样式为栅格瓦片风格(`rasterStyle`)或矢量瓦片(`vectorStyle`) 相关代码如下: ```js // --打开地图[矢量瓦片]--以矢量瓦片方式打开已上传的CAD的DWG格式的图 // js代码 // 新建地图服务对象,传入服务地址和token let svc = new vjmap.Service(env.serviceUrl, env.accessToken) // 打开地图 let res = await svc.openMap({ mapid: env.exampleMapId, // 地图ID,(请确保此ID已存在,可上传新图形新建ID) mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开 style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式 }) if (res.error) { message.error(res.error) } // 获取地图的范围 let mapExtent = vjmap.GeoBounds.fromString(res.bounds); // 建立坐标系 let prj = new vjmap.GeoProjection(mapExtent); // 新建地图对象 let map = new vjmap.Map({ container: 'map', // container ID style: svc.vectorStyle(), // 矢量瓦片样式 如需栅格样式需改成 rasterStyle() center: prj.toLngLat(mapExtent.center()), // 中心点 zoom: 2, renderWorldCopies: false }); // 地图关联服务对象和坐标系 map.attach(svc, prj); // 使地图全部可见 map.fitMapBounds(); // 点击有高亮状态(鼠标点击地图元素上时,会高亮) map.enableLayerClickHighlight(svc, e => { let msg = { content: `type: ${e.name}, id: ${e.objectid}, layer: ${e.layerindex}`, key: "layerclick", duration: 5 } e && message.info(msg); }) ``` # 效果 [栅格瓦片示例](https://vjmap.com/demo/#/demo/map/service/02openMapRaster)
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: env.exampleMapId, // 地图ID
    // @ts-ignore
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapLightStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    showError(res.error)
    return;
}
// 获取地图范围
let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapBounds);
//
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapBounds.center()), // 设置地图中心点
    zoom: 2, // 设置地图缩放级别
    pitch: 0,
    antialias: true, // 反锯齿
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();


[矢量瓦片示例](https://vjmap.com/demo/#/demo/map/service/01openMapVector)
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: env.exampleMapId, // 地图ID
    // @ts-ignore
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapLightStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    showError(res.error)
    return;
}
// 获取地图范围
let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapBounds);
//
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.vectorStyle(), // 样式,这里是矢量样式
    center: prj.toLngLat(mapBounds.center()), // 设置地图中心点
    zoom: 2, // 设置地图缩放级别
    pitch: 0,
    antialias: true, // 反锯齿
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();


--- # GeoJSON ***注:*** 是一种对各种地理数据结构进行编码的格式。GeoJSON对象可以表示几何、特征或者特征集合。GeoJSON支持下面几何类型:`点`、`线`、`面`、`多点`、`多线`、`多面`和`几何集合`。GeoJSON里的特征包含一个`几何对象`和`属性对象`,特征集合表示一系列特征。 ## FeatureCollection --- #### Geojson特征集合数据格式: > 这里重点说明的是当一份geojson数据中含有`多种数据格式,点线面`时,如何通过`filter`字段实现过滤功能。 **类型筛选器** > + `"Point"` 注意传入的是`字符串` > + `"LineString"` 注意传入的是`字符串` > + `"Polygon"` 注意传入的是`字符串` ```js map.addLayer({ "id": "lineid", "type": "line", "source": "geojsonCollections", //必须和上面的geojsonCollections一致 "filter": ["==", "$type", "LineString"], //关键点:$type是固定语法,类型是Point、LineString、Polygon "layout": { ... }, "paint": { ... } }); ``` --- **标准数据格式如下:** ```js var FeaCollection = { "type": "FeatureCollection", "features": [{ "type": "Feature", "geometry": { "type": "Point", "coordinates": [] }, }, { "type": "Feature", "geometry": { "type": "LineString", "coordinates": [[]] } }, { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [[[]]] } }] } ``` ## Point ***注:*** 对类型"Point"来说,“coordinates"成员必须是一个一维数组。 **标准数据格式:** ```javascript var Point = { "type": "Feature", "geometry": { "type": "Point", "coordinates": [102.0, 0.5]}, "properties": { "prop0": "value0" } } ``` **样式说明:** ~~~ json "paint": { "circle-radius": 15,//半径 "circle-color": "#FF0000",//颜色 "circle-opacity": 0.8, //透明度 "circle-stroke-width": 5, //轮廓线宽度 "circle-stroke-color": "#0000FF",//轮廓线颜色 "circle-stroke-opacity": 0.7//轮廓线透明度 "circle-translate": [0,0] //表示显示位置基于原始位置上,再按照屏幕坐标进行偏移,这个应该绝大部分都用不上 } ~~~ ## Line ***注:*** 对类型`LineString`来说,`coordinates`成员必须是`二维数组`,数组内部又是一个数组,表示组成这条线的点集。 **标准数据格式:** ```javascript var GeoLine = { "type": "LineString", "properties":{ "prop0": "value0" }, "coordinates": [ [ 114.34776306152344, 30.623436511269382 ], [ 114.32510375976562, 30.63909360759635 ], [ 114.30673599243164, 30.634958017061198 ], [ 114.29180145263672, 30.629640569460406 ]] }; ``` ***注:*** 线性环具有4个或者更多位置的封闭的线。第一个和最后一个位置是相等的(它们表示相同的的点)。`这个不是指的封闭的区`而是指的`环状线`。 **样式说明:** ~~~ json { "layout": { "line-cap": "square", //butt 尖头,round 圆头,square平头 "line-join": "round", //bevel平拐,round 圆拐,miter棱拐 "line-miter-limit": 2, //棱拐的限制,一般用不上 "line-round-limit": 1.05,//圆拐的限制,一般用不上 "visibility": "visible", //是否可见 visible / none }, "paint": { "line-width": 20, //宽度 "line-color": "#000000", //颜色 "line-opacity": 0.8, //透明度 "line-gap-width" : 0, //线的沟宽,如果有一条线会变成2条线,中间有条沟 "line-offset" : 0, //尽量少用,如果这个值相对大的话在拐角处很容易变形变胖 "line-dasharray": [5,2],//实线、虚线的组合,可以表示铁路线等 "line-blur" : 5,//模糊度,和宽度配合使用,当宽度20,模糊度10时,出现边线模糊的效果,该值要小于线宽度 "line-pattern": "picture_name", //线的拉伸图片类型,一定要与对应的样式库的图片名字一一对应 "line-translate": [0,0] //表示显示位置基于原始位置上,再按照屏幕坐标进行偏移,这个应该绝大部分都用不上 } } ~~~ ## Polygon ***注:*** 对类型"Polygon"来说,"coordinates"成员必须是一个线性环坐标数组的数组(`三维数组`)。对拥有多个环的的面来说,第一个环必须是外部环,其他的必须是内部环或者孔。外部环是`顺时针方向`,内部环是`逆时针方向`. > 请注意, 如果只是一个简单的区的话`顺时针、逆时针`都可以显示。但是针对带洞区,最好采取外圈`顺时针`,内圈`逆时针`的方式保证显示效果的一致性。不然`一份数据`可能在前端的显示效果会`不尽相同`。 > `外顺内逆`是带洞区, `外顺内顺`是类似同心圆的`叠加区`。 **标准数据格式:** ```javascript var GeoPolygon = { "type": "Feature", "properties": {}, "geometry": { "type": "Polygon", "coordinates": [ [ [ 114.32510375976562, 30.63968439181164 ], [ 114.22931671142578, 30.608958829007946 ], [ 114.22348022460937, 30.601275914486066 ], [ 114.22931671142578, 30.580588116155223 ], [ 114.32510375976562, 30.63968439181164 ] ] ] } }; ``` --- **样式说明:** ~~~ json { "layout": { "visibility": "visible" //是否可见 visible / none }, "paint": { "fill-antialias": true, //抗锯齿,true表示针对边界缝隙进行填充 "fill-color": "#000000", //颜色 "fill-opacity": 0.8, //透明度 "fill-outline-color": "#FFFF00",//边线颜色,没错,确实没有边线宽度这个选项 "fill-pattern":"picture_name", //线的拉伸图片类型,一定要与对应的样式库的图片名字一一对应 "fill-translate": [0,0] //表示显示位置基于原始位置上,再按照屏幕坐标进行偏移,这个应该绝大部分都用不上 } } ~~~ ## MultiPoint ***注:*** 对类型`MultiPoint`来说,`coordinates`成员必须是`二维数组`。 标准数据格式: ```javascript var MultiPoint = { "type": "Feature", "properties": {}, "geometry": { "type": "MultiPoint", "coordinates": [ [ 114.29351806640625, 30.602457940999596 ],[ 114.21146392822264, 30.56580841410847 ],[ 114.30896759033203, 30.529145036680408 ] ] } }; ``` ## MultiLineString ***注:*** 对类型`MultiLineString`来说,`coordinates`成员必须是一个线坐标数组的数组(`三维数组`)。 **标准数据格式:** ```javascript var MultiLine = { "type": "Feature", "properties": {}, "geometry": { "type": "MultiLineString", "coordinates": [ [ //第一条线开始 [ 114.30862426757812, 30.65533885862785 ], [ 114.23755645751953, 30.55191344082329 ] ], //第一条线结束 [ //第二条线开始 [ 114.3134307861328, 30.677191798461496 ], [ 114.38827514648437, 30.528849308009075 ] ] //第二条线结束 ] } }; ``` ## MultiPolygon ***注:*** 对类型`MultiPlygon`来说,`coordinates`成员必须是面坐标数组的数组(`四维数组`)。 ***注:*** 对类型`Polygon`来说,`coordinates`成员必须是一个线性环坐标数组的数组(`三维数组`)。对拥有多个环的的面来说,第一个环必须是外部环,其他的必须是内部环或者孔。外部环是`顺时针方向`,内部环是`逆时针方向`. > 请注意, 如果只是一个简单的区的话`顺时针、逆时针`都可以显示。但是针对带洞区,最好采取外圈`顺时针`,内圈`逆时针`的方式,保证显示效果的一致性。不然`一份数据`可能在前端的显示效果会`不尽相同`。 > `外顺内逆`是带洞区, `外顺内顺`是类似同心圆的`叠加区`。 **标准数据格式:** ```javascript var MultiPolygon = { "type": "Feature", "properties": { name: "多面" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ //多区 第一个部分 [ //这和数据是逆时针,但是照样可以绘制出来,因为这个第一部分只是一个简单区 [ 114.3233871459961, 30.57438091562706 ], [ 114.33368682861328, 30.57851909346139 ], [ 114.34587478637695, 30.586647142912706 ], [ 114.33162689208984, 30.596695425524466 ], [ 114.31617736816406, 30.57955361032709 ], [ 114.3233871459961, 30.57438091562706 ] ] ], //多区 第一个部分 带动区结束 [ //多区 第二个部分 普通区开始 [ [ 114.33454513549805, 30.479893814659587 ], [ 114.38587188720703, 30.479893814659587 ], [ 114.38587188720703, 30.504596494227247 ], [ 114.33454513549805, 30.504596494227247 ], [ 114.33454513549805, 30.479893814659587 ] ] ] //多区 第二个部分 普通区结束 ] } } ``` --- # 常见问题 ## 怎么查看文档和示例 - 快速入门 [https://vjmap.com/guide/quickstart.html](https://vjmap.com/guide/quickstart.html) - 简单开发示例(vue,react,html) [https://vjmap.com/guide/downloaddemo.html](https://vjmap.com/guide/downloaddemo.html) - 工程化开发封装复杂交互参考源码 [https://vjmap.com/app/cloud](https://vjmap.com/app/cloud) - 各种应用场景和效果demo看 [https://vjmap.com/demo](https://vjmap.com/demo) - [唯杰地图3d](https://vjmap.com/map3d/) ## 唯杰地图系统架构 唯杰地图系统架构为BS架构。 服务端主要核心功能如解析打开DWG文件、保存成空间数据、生成栅格矢量瓦片渲染数据等功能都在于服务端; 服务端可跨平台私有化部署(推荐linux系统docker部署),支持内网无网络环境部署; 官网的免费试用版本功能与正式版本一样,但有水印;如需去水印,用于商用私有化部署请联系客服; 和地图有关的服务端功能一般无需用户二次开发;用户自定义的数据获取和保存功能,用户可用任意语言开发自己的服务端功能。 前端sdk库,可直接在html中引入样式` `和脚本文件 `` ;或安装npm包 `npm install vjmap`来进行开发。前端需要用户利用sdk库开发自己的功能融合进自己的系统中。可原生js开发,或React、vue开发(示例:[点击下载](https://vjmap.com/download/vjmap_demo.zip) )。支持移动端,如果是原生开发,请用webview基于h5开发。 ## 唯杰地图是免费的吗? 唯杰地图是可以`免费试用`的,但试用版本`有水印`,上传文件`大小有限制`,上传的数据会`不定时清空`,功能与正式版一致。如需`去水印`,私有化`部署到自己服务器上面`是`收费`的(支持内网无网络环境部署)。具体费用请联系客服; 客服微信: ![wx](https://vjmap.com/wx.png) ## 怎么打开CAD图形 打开CAD图形有以下四种方式 - 1. 通过sdk的`Service`的`uploadMap`方法上传dwg图形;[示例](https://vjmap.com/demo/#/demo/map/service/06uploadmap) - 2. 通过一个能访问的http网址路径直接打开图形;[示例](https://vjmap.com/demo/#/demo/map/service/05openMapFromHttp) - 3. 如果CAD与服务在同一个服务器,可直接通过本地路径来打开CAD图形;[示例](https://vjmap.com/demo/#/demo/map/service/05openMapFromLocal) - 4. 如果是新建图形,可以传入新建图形的json数据来创建图形;[示例](https://vjmap.com/demo/#/demo/map/service/05newmap) ## 支持移动端吗? 唯杰地图完全兼容移动端查看。在每个示例中和云平台管理中都增加了手机扫一扫的功能,可直接扫描二维码在手机端查看效果。
## 是否支持第三方开源的GIS前端库进行开发? 唯杰地图全面支持用`openlayers`、`leaflet`、`maptalks`来加载CAD图 - 用`openlayers`开发,请参考示例 [https://vjmap.com/demo/#/demo/map/openlayers/01olraster](https://vjmap.com/demo/#/demo/map/openlayers/01olraster) - 用`leaflet`开发,请参考示例 [https://vjmap.com/demo/#/demo/map/leaflet/01leafletraster](https://vjmap.com/demo/#/demo/map/leaflet/01leafletraster) - 用`maptalks`开发,请参考示例 [https://vjmap.com/demo/#/demo/map/maptalks/01maptalksraster](https://vjmap.com/demo/#/demo/map/maptalks/01maptalksraster) ## 是否支持对CAD图的编辑? 唯杰地图支持对CAD图的编辑,不过唯杰地图主要是对CAD图GIS展示和效果为主,编辑功能相对较弱。 完整的能在cad图上进行删除、修改,新增,导出CAD图的示例源码。可参考 [https://vjmap.com/app/visual/#/draw?mapid=p5ff2aa38c0f&version=v1&mapopenway=GeomRender&vector=true&theme=dark](https://vjmap.com/app/visual/#/draw?mapid=p5ff2aa38c0f&version=v1&mapopenway=GeomRender&vector=true&theme=dark) [https://vjmap.com/demo/#/demo/map/comprehensive/05drawcadedit](https://vjmap.com/demo/#/demo/map/comprehensive/05drawcadedit) 实现对CAD图的编辑,原理如下: - 对cad原图进行修改。可对CAD图中的实体进行新增、修改、删除等操作。修改保存后,后台会新成一个新的版本号。示例 [https://vjmap.com/demo/#/demo/map/service/05zfrommap](https://vjmap.com/demo/#/demo/map/service/05zfrommap) - 利用修改样式对cad图的显示进行修改,不会修改原图数据,只会改前端显示结果。性能高些。示例代码 [https://vjmap.com/demo/#/demo/map/service/05zgmodifymap](https://vjmap.com/demo/#/demo/map/service/05zgmodifymap) `利用修改样式对cad图的显示进行修改`主要原理是: (1)后台通过样式去控制绘制。可利用我们的表达式语法,对指定的图层,实体等进行显示或隐藏,和颜色的修改等。这样会在后台创建一个新的样式名称。服务后台打开图的时候指定此样式名称即可,这样生成的瓦片数据就是您个性化后的数据。 (2)前端可以加一个绘制图层,可以绘制点,线,面等名种形状。 通过后台样式控制和前端的绘制来达到您想要的修改后的效果。 如果是自已上传的图形,可以在`唯杰地图云平台中`打开,点击上面`工具栏`中的`更多绘图功能`进入。 ## 如何让不同的用户或用户组只能看到自己或自己所有组的图? 首先唯杰地图只是一个地图开发平台,只会提供了和地图有关的服务,不会对业务中的用户和密码进行封装。和地图服务有关的权限配置请参考博文 [唯杰地图之前端CAD图GIS数据访问权限配置](https://www.cnblogs.com/vjmap/p/16720980.html)。 实现`让不同的用户或用户组只能看到自己或自己所有组的图`需要在您`自己的业务系统`中与`唯杰地图`进行融合。 具体思路为: - (1) 在业务系统中进行用户登录 - (2) 登录成功后,此用户上传的图名称与此用户及用户组相关联。 举例如下: 用户组 `admin` 中的用户 `user1` 上传了 图名称为 `ns_map1`, `ns_map2`的CAD图。 用户组 `admin` 中的用户 `user2` 上传了 图名称为 `ns_cad3`, `ns_cad4`的CAD图。 那在业务数据库中可以用一个表记录下如下信息 表结构如下 (上传时间、上传文件名、缩略图地址、打开方式可通过上传打开完成后接口获取) |图id|所属用户组|上传用户|上传时间|上传文件名|缩略图地址|打开方式| |:---|:---|:---|:---|:---|:---|:---| |ns_map1|admin|user1| |ns_map1|admin|user1| |ns_cad3|admin|user2| |ns_cad4|admin|user2| - (3) 通过上面的关联关系, 当 用户 `user1` 查看图纸时,在业务系统的后台表中,查询出 `user1` 有 `ns_map1`, `ns_map2`的CAD图,这时候只显示出这两张图即可。 - (4) 如果 `允许此用户看此用户组下面的所有图` 的话,可以知道此用户组为`admin`,在业务系统的后台表中,查询出 `admin`用户组 有 `ns_map1`, `ns_map2`, `ns_cad3`, `ns_cad4` 的CAD图,这时候只显示出这四张图即可。 - (5) 如`防止`用户通过在`唯杰地图管理平台`或通过`vjmap.Service.listMaps`获取到不是此用户的图,还需要把用户上传的图设置为`隐藏`,这样通过唯杰的默认接口查询时,不会查询到此图。设置图形`隐藏`的方法为,图名称为`ns_`开头即可。如果要自定义隐藏图形名称的前缀,需要在后台`config.json`的`map`配置下, 加上`map_list_exclude_prefix`设置,允许多个时以分号隔开,如设置为`map_list_exclude_prefix`为 `ts_;ns_;test_`表示以`ts_`、`ns_`、`test_`开头的图名称都将自动隐藏。 - (6) 可直接通过 `vjmap.Service.openMap` 传入图名称打开图即可, 图形设置了隐藏的也是一样的打开方式。图设置为了隐藏与不隐藏的唯一区别就是通过`vjmap.Service.listMaps`获取所有图时,不能获取到隐藏了的图形id。 ::: tip 提示 如果只是需要不同用户组的数据之间隔离,可以利用工作区的机制。不同的用户组,创建不同的工作区。工作区之间数据是隔离的。工作区示例 [https://vjmap.com/demo/#/demo/map/service/07mapWorkspace](https://vjmap.com/demo/#/demo/map/service/07mapWorkspace) ::: ## CAD图中自定义字体会不会显示乱码或问号? 唯杰地图后台服务会安装一些常用的字体, 如果用户CAD图中自定义字体不在这些常用的字体中,我们会用默认字体替代这些字体,这样就不会显示问号或乱码了。但有时候显示可能会与原字体显示有差别,如宽高大小不一样。要想显示完全一样,需要把自定义的字体拷贝到唯杰地图后台服务字体目录`data/fonts`即可。如果需要在线替换字体,可采用以下步骤: - (1) 在cad中打开有问号字体的图形,查看字体样式中有问号的字体样式名称是什么字体,例如为"tssdeng.shx" - (2) 在唯杰中打开有问号字体的图形,在openMap返回的字段findFonts为系统查找的字体替换规则,例如为 {"tssdeng.shx_1":"tssdeng.shx"} - (3) 删除之前上传的图形,在 云端管理平台上传页面重新上传之前的图形 - (4) 上传完后,在更多设置中 `字体替换规则` 中输入要替换的字体规则,如{"tssdeng.shx_1": "_default_.ttc"} - (5) 为了防止缓存,可以对图形id重命名下,打开图形即可。 如遇乱码上述方法操作麻烦的情况下,万能解决办法,打开图形的时候,在更多设置里`字体替换规则` 中输入 `{"*":"_default_.ttc"}` , 这表示将图中的所有字体都用默认字体来代替(缺点是图中所有字体都是同一个字体了)。 ### 在服务端怎么上传和打开图形 在服务端,可以通过调用唯杰Restful的请求来上传和打开图形。具体请求接口的参数,可以在前端F12打开调试控制台网络请求中可以查看。 下面为node.js为例,实现服务端怎么上传和打开图形的demo ```js // app.js const fs = require('fs') const FormData = require('form-data') const axios = require('axios') const prompt = require('prompt'); (async ()=>{ const defaultServiceUrl = 'https://vjmap.com/server/api/v1'; const defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MiwiVXNlcm5hbWUiOiJhZG1pbjEiLCJOaWNrTmFtZSI6ImFkbWluMSIsIkF1dGhvcml0eUlkIjoiYWRtaW4iLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjo0ODEzMjY3NjM3LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTY1OTY2NjYzN30.cDXCH2ElTzU2sQU36SNHWoTYTAc4wEkVIXmBAIzWh6M'; let formData = new FormData(); let { filepath } = await prompt.get('filepath'); // 请输入要上传的文件路径 if (!filepath) return; let file = fs.createReadStream(filepath) formData.append('fileToUpload', file); let len = await new Promise((resolve, reject) => { return formData.getLength((err, length) => (err ? reject(err) : resolve(length))); }); let res = await axios({ url: `${defaultServiceUrl}/map/uploads?token=${defaultAccessToken}`, method: 'POST', data: formData, headers: { ...formData.getHeaders(), 'Content-Length': len, }, }); /* { fileid: 'c036d8ca23eb', mapid: 'c036d8ca23eb', // mapid是建议的mapid,如果之前上传打开过,则为之前的mapid uploadname: 'base1.dwg' } */ console.log(res.data); // 上传只是把图上传到了服务器, 您新建地图的时候的,第一次的时候openMap要传上传的fileid,然后给新建的图取个mapid打开就可以了,以后就可以直接通过mapid打开图了 let { mapid } = await prompt.get('mapid'); // 请输入要上传的文件路径 if (!mapid) { // 如果没有输入mapid,则用上传的时候返回的默认mapid mapid = res.data.mapid; } let result = await axios({ url: `${defaultServiceUrl}/map/openmap/${res.data.mapid}?version=&layer=&geom=true&fileid=${res.data.fileid}&uploadname=${res.data.uploadname}&mapfrom=&mapdependencies=&subfrom=&subdependencies=&stylename=&layeron=&layeroff=&clipbounds=&bkcolor=0&lineweight=&expression=`, method: 'GET', headers: { token: defaultAccessToken, }, }); console.log(result) // 如要判断图上传已创建处理完成,可用定时器如3s轮询判断处理状态 let result2 = await axios({ url: `${defaultServiceUrl}/map/cmd/listmaps/${result.data.mapid}/${result.data.version}, method: 'GET', headers: { token: defaultAccessToken, }, }); console.log(result2) // result2.status "working" 正在处理; "error" 表示有错误; "finish" 处理完成 })(); ``` ## 图形打开显示一个小点的解决办法 ### 在唯杰云端管理平台处理办法 - 1. 进入唯杰云端管理平台 https://vjmap.com/app/cloud - 2. 打开显示小点的地图后,进入上面工具栏中的“更多功能”-> "选择裁剪范围生成新的图形" - 3. 拉框选择要显示的区域,然后在新打开的页面中,给新的图形重命名即可。 - 4. 可以删除以前的图形.(可选) ### 在CAD里面处理办法 - 1.在CAD双击鼠标中键,使CAD图全图显示 - 2.如果这时候发现全图显示后,只看见几个小点,说明图其他区域很远处有一些其他东西,需要删除掉。 - 3.这时候再按 Ctrl+A全选所有图形。按住Shift拉框,把真正要显示的图选中,排除到选择集中,按delete键,删除多余的图形。(如果无法删除,可能是图层冻结或锁定了,需把无法删除的图层取消冻结或锁定) - 4.再 双击鼠标中键,使CAD图全图显示,这时候就正常了。再保存图形 ## 如何把另外一个坐标系的数据叠加到当前CAD图上 - 如果另外一个坐标系和当前CAD图是同一个坐标系,那数据无需转换直接用就可以的 - 如果不是同一个坐标系,如果不知道这两个坐标系对应关系的话,可以通过四参数来对应解决(误差由给定的公共点决定。公共点越准确,误差越小)。示例代码如: ```js // cad上面的点坐标 let cadPoints = [ vjmap.geoPoint([39760.07407, 237424.52134]), vjmap.geoPoint([39006.60468, 237808.49494749]), vjmap.geoPoint([38823.92918, 235003.98994]), vjmap.geoPoint([37885.55424, 235452.80893]) ]; // 另外坐标系上拾取的与上面的点一一对应的坐标 let otherPoints = [ vjmap.geoPoint([113.292983, 23.206979]), vjmap.geoPoint([113.285631, 23.210304]), vjmap.geoPoint([113.283897, 23.185047]), vjmap.geoPoint([113.274716, 23.188962]) ] // 通过坐标参数求出四参数 let fourparam = vjmap.coordTransfromGetFourParamter(otherPoints, cadPoints, false); // 把另外的坐标系上面的公共点转成cad坐标,与原始cad坐标比较,看得到的四参数误差有多大,如果误差比较小,说明公共点是可以用的 for(let i = 0; i< otherPoints.length; i++) { const cadpt = vjmap.coordTransfromByFourParamter(otherPoints[i], fourparam); console.log("原始点:" + cadPoints[i], "计算结果:" + cadpt, "误差:" +cadPoints[i].distanceTo(cadpt)) } // 例如把另外坐标系的点[113.292983, 23.206979] 转成cad坐标 const cadpt = vjmap.coordTransfromByFourParamter(vjmap.geoPoint([113.292983, 23.206979]), fourparam); console.log(cadpt) // {x: 39792.49478817663, y: 237432.04509891104} // 通过cad的点反算出另外坐标系的点 const otherpt = vjmap.coordTransfromByInvFourParamter(cadpt, fourparam); // 通过四参数反算 console.log(otherpt) // {x: 113.29298300000002, y: 23.206979 // 注:四参数只用应用于有平移旋转缩放的情况, 如有水平或垂直翻转请先转换完再用四参数。 ``` --- # 搜索文档 ## 唯杰地图开发智能助手 [唯杰地图文档MCP深度搜索](https://vjmap.com/server/_mapviewer?aidoc=true) https://vjmap.com/server/_mapviewer?aidoc=true ![image-20250804094631194](https://vjmap.com/blogimages/202508040946412.png) [唯杰地图文档AI搜索](https://vjmap.com/server/help/#/) https://vjmap.com/server/help/#/ ![image-20250804094730851](https://vjmap.com/blogimages/202508040947933.png) ## 网站文档搜索
因为网站文档是基于`Algolia`做的检索,不是全文搜索,有些函数可能不一定能搜索到。这时可搜索类型定义文件 ## 搜索类型定义文件 [打开类型定义文件](https://vjmap.com/guide/typescript.html),输入 `Ctrl+F` 进行搜索
## demo示例里搜索代码 在demo示例里左上角有搜索代码和说明功能, 学会用调试功能,在demo示例里加上debugger,按F12,点击代码栏右侧的执行可调试代码
## 搜索本地示例代码库 把唯杰地图开源的git示例代码下载到本地,在本地中搜索相关函数的用法或者对示例进行调试
--- # 示例源码 ## 直接获取的源码 [唯杰地图vjmap示例的所有源代码](https://github.com/vjmap)
[唯杰三维地图vjmap3d示例的所有源代码](vjmap3d-playgroundvjmap3d-playground)
[vjmap-common唯杰地图常用的功能封装](https://github.com/vjmap/vjmap-common) ## 添加客服微信免费获取的源码 添加`客服微信` ![wx](https://vjmap.com/wx.png) 可`免费无条件`获取以下源码 ::: tip 提示 获取以下源码只需简单的一步: 添加上面`客服微信`,备注`示例源码`,通过后即可免费无条件获取下面所有示例源码。 ::: ### 唯杰地图云端图纸管理平台源码 (vue3 + ts + ElementPlus) [唯杰地图云端图纸管理平台在线体验地址](https://vjmap.com/app/cloud/#/)
[唯杰地图可视化平台在线体验地址](https://vjmap.com/app/visual/#/edit?key=sys_be517afb&isLocal=false) ### 唯杰地图可视化平台 (vue3 + ts + naiveui)
### 唯杰地图3D应用案例效果源码 (vue3 + ts) [唯杰地图3D应用案例在线体验地址](https://vjmap.com/map3d/example.html)
--- # 地图MCP协议 传统的 CAD 地图应用都是由人来操作的,用户需要学习复杂的 CAD 操作命令和地图管理流程,操作成本相对较高。如果能让 AI 来帮我们操作地图,是不是就非常方便? 如果所有 CAD 地图应用都能做智能化改造,让 AI 能够理解和操作地图数据,每个人都拥有一个虚拟的"地图智能助理",我们只需要聊天一样跟地图智能助理"聊天",用自然语言提出我们的地图需求,智能助理会自动帮我们操作地图应用,实现我们的需求,这将大大提升人们在地图处理、CAD 图纸分析、空间数据管理等方面的工作效率和体验! `唯杰地图`基于 MCP 协议实现了 AI 代替人操作 CAD 地图应用这项技术,并应用到我们的唯杰地图引擎中,实现了唯杰地图系统的智能化改造。使用 MCP协议工具,天然地支持被 AI 识别和操控,包括地图浏览、CAD 图纸查询、空间数据提取、图形绘制等操作。同时还提供了一套前端 SDK,支持现有地图业务快速接入 AI、实现智能化,目前支持 Vue、React、Angular 等前端框架。 由于是基于标准的 MCP 协议实现的,具备通用性和广泛的适用性,可以通过各种不同类型的 MCP Host 来控制地图应用,比如可以通过网页上的 AI 对话框来控制地图应用,也可以通过`Tace`、`Cursor` 等 IDE 工具,或者通过 `Dify`、`Coze` 等智能体平台来操控,甚至可以通过手机App、微信小程序等方式远程遥控你的地图应用。我们可以与 AI 对话,让 AI 帮我们操作各类地图应用,实现我们的地图相关需求。 ## 唯杰地图后台服务MCP地址 官网唯杰地图服务MCP地址 ```json "vjmapsvr": { "url": "https://vjmap.com/server/mcp?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MiwiVXNlcm5hbWUiOiJhZG1pbjEiLCJOaWNrTmFtZSI6ImFkbWluMSIsIkF1dGhvcml0eUlkIjoiYWRtaW4iLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjo0ODEzMjY3NjM3LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTY1OTY2NjYzN30.cDXCH2ElTzU2sQU36SNHWoTYTAc4wEkVIXmBAIzWh6M" } ``` ### MCP 服务使用说明 把本服务的图纸管理、查询、导出等能力,以 MCP 工具提供给 AI编辑器,如Trae、Qoder 、Cursor、Claude Desktop 等 AI 客户端。传输方式为 Streamable HTTP。 #### 如何指定工作区 连接时绑定(推荐): http://https://vjmap.com/server/mcp?workspace=share?token=xxxxxx 不配 workspace 就走默认工作区。调用时也可再传 workspace 覆盖 URL;要强制回默认工作区,传 workspace=__default__。 需要先列出工作区时,这个分类默认不加载,URL 要显式打开: http://https://vjmap.com/server/mcp?tools=workspace?token=xxxxxx 再调用 cad_list_workspaces。 #### 怎么用 鉴权:请求头 Authorization: Bearer ,或头 token,或 URL?token=。用户图纸权限仍然生效。 接入示例: { "mcpServers": { "vjmapsvr": { "url": "http://https://vjmap.com/server/mcp?workspace=share", "headers": { "Authorization": "Bearer <你的token>" } } } } 只要查询和图纸列表时,可再加 ?tools=query,map。 默认加载:map、query、export、admin。 按需加载(配置或 URL 的 tools 里写分类/工具名才出现):draw、geom、symbol、workspace。 写操作工具还要 enable_write: true。生产环境建议 disable_tools 排除 admin。 ## 唯杰地图前端MCP地址 后端MCP地址只能调用后端图纸能力。前端MCP地址可以与页面相结合,对页面中的地图进行操作,如移动或加数据展示效果。 ### 效果展示: ![chatmcp.gif](https://vjmap.com/blogimages/chatmcp.gif) [唯杰地图云端管理平台](https://vjmap.com/app/cloud/#/) https://vjmap.com/app/cloud/#/ ![image-20250803183709135](https://vjmap.com/blogimages/202508031837207.png) ![image-20250803180303071](https://vjmap.com/blogimages/202508031803174.png) 点击界面左上角的MCP地址,可查看此次会话的MCP地址 ![image-20250803175643751](https://vjmap.com/blogimages/202508031756861.png) **可复制当前会话的`MCP地址` 到其他`MCP客户端`如`cursor`、`trae`或`cherry studio`中对当前地图进行提问** 如在`cursor`中通过MCP地址调用地图相关操作 ![mcpcursor.gif](https://vjmap.com/blogimages/mcpcursor.gif) ### MCP架构原理与流程 唯杰地图MCP系统采用服务端-前端分离架构,通过MCP(Model Context Protocol)协议实现双向通信。系统支持服务端工具和前端工具的混合调用,实现复杂的地图操作和交互功能。 #### 整体架构 ![image-20250803182907607](https://vjmap.com/blogimages/202508031829665.png) ### 后端MCP工具 | 工具名称 | 分类 | 类型 | 工具描述 | 主要参数 | | ---------------------- | ------- | ---- | ------------------------------------------------------------ | ------------------------------------------------------------ | | **listmaps** | map | 后端 | 获取地图列表信息,支持获取所有地图或指定地图的版本信息 | `mapid`(地图ID)、`version`(版本号)、`mapIds`(地图ID数组)、`workspace`(工作区)、`pagination`(分页)、`curPage`(当前页)、`pageCount`(每页数量) | | **createMap** | drawing | 后端 | 新建地图工具,支持两种创建方式:1、通过fileid和mapid创建地图;2、通过组合成新地图API创建 | `createType`(创建类型)、`mapid`(地图ID)、`fileid`(文件ID)、`uploadname`(文件名)、`geom`(几何渲染)、`sourceMapid`(源地图ID)、`sourceVersion`(源版本)、`sourceClipbounds`(裁剪范围)、`fourParameter`(四参数)、`layers`(图层列表)、`workspace`(工作区) | | **deletemap** | delete | 后端 | 删除地图功能,支持删除指定版本或所有版本的地图。重要:删除操作不可逆,请谨慎使用! | `mapid`(地图ID)、`version`(版本号)、`retainVersionMaxCount`(保留版本数)、`workspace`(工作区) | | **getMapImage** | map | 后端 | 根据范围和要显示的图层列表获取地图图像,返回二进制图片PNG格式。支持指定图层显示/隐藏,自动计算图片尺寸比例 | `mapid`(地图ID)、`version`(版本号)、`bounds`(范围)、`pictureWidth`(图片宽度)、`layerOn`(显示图层)、`layerOff`(隐藏图层)、`backgroundColor`(背景色)、`transparent`(透明背景)、`styleName`(样式名)、`workspace`(工作区)、`returnBase64`(返回base64) | | **fullSearch** | query | 后端 | CAD图全文搜索文档,支持在CAD图纸中搜索文字内容、图层、块名称等信息 | `query`(搜索关键词)、`map_ver`(地图版本)、`workspace`(工作区)、`type`(文档类型)、`bounding_box`(范围)、`time_range`(时间范围)、`limit`(结果数量)、`offset`(偏移量)、`fields`(返回字段)、`facet`(聚合信息) | | **queryFeatures** | query | 后端 | 查询地图实体,支持条件查询、矩形范围查询、点查询和表达式查询四种查询类型 | `mapid`(地图ID)、`version`(版本号)、`queryType`(查询类型)、`sql`(SQL语句)、`bounds`(范围)、`point`(点坐标)、`radius`(半径)、`expression`(表达式)、`workspace`(工作区) | | **extractTable** | query | 后端 | 自动提取CAD图纸中的表格数据,支持指定坐标范围或全图提取。当需要查询图纸相关信息时,应优先调用此工具提取表格数据进行分析,可获取图纸中相关结构化数据信息 | `mapid`(地图ID)、`version`(版本号)、`bounds`(提取范围)、`workspace`(工作区) | | **sqlDocHelper** | query | 后端 | 获取查询地图实体的SQL编写文档和表结构说明,为AI生成SQL语句提供参考 | 无参数 | | **createDwgDocHelper** | drawing | 后端 | 获取创建图文档的参数说明和实体类型定义,为AI生成图形实体JSON数组提供参考 | 无参数 | ### 前端工具 | 工具名称 | 分类 | 类型 | 工具描述 | 主要参数 | | ----------------------------- | ------- | ---- | ------------------------------------------------------------ | ------------------------------------------------------------ | | **map_get_info** | map | 前端 | 获取当前地图的详细信息,包括缩放级别、中心点、旋转角度、倾斜角度、显示范围、地图参数、图层信息和工作区名称、缩略图地址等。返回的坐标为CAD坐标系统。 | 无参数 | | **map_open_or_switch** | map | 前端 | 打开或切换图功能 - 根据mapid和version参数打开指定的图。打开成功后会自动更新地图上下文数据到后台。 | `mapid`(地图ID)、`version`(版本号)、`isKeepOldLayers`(保留旧图层)、`isVectorStyle`(矢量样式)、`isSetCenter`(设置中心)、`isFitBounds`(适应范围)、`layeron`(开启图层)、`layeroff`(关闭图层)、`backcolor`(背景色) | | **map_view_control** | map | 前端 | 地图视图控制工具 - 支持缩放、平移、旋转、俯仰、缩放至地图范围等操作。所有坐标参数使用CAD坐标(x,y) | `operation`(操作类型)、`zoom`(缩放级别)、`center`(中心点)、`bearing`(旋转角度)、`pitch`(俯仰角度)、`bounds`(范围)、`duration`(动画时长) | | **map_execute_code** | runcode | 前端 | 执行自定义JavaScript代码,用于复杂的地图操作和自定义功能实现(需要唯杰地图帮助文档MCP一起使用) | `code`(JavaScript代码)、`description`(代码描述) | | **map_draw_geojson** | draw | 前端 | 根据GeoJSON数据绘制要素,支持所有标准GeoJSON几何类型。坐标使用CAD坐标系,样式属性在properties中配置 | `geojsonData`(GeoJSON数据,包含几何对象和样式属性) | | **map_delete_drawn_features** | draw | 前端 | 删除绘制的要素,支持删除指定实例或清空所有绘制图层 | `instanceId`(实例ID)、`clearAll`(清空所有) | | **map_create_markers** | draw | 前端 | 根据GeoJSON数据创建一个或多个标记,每个标记的样式配置从其properties中读取,支持自定义样式、拖拽、弹窗等功能 | `geojsonData`(GeoJSON数据,包含Point几何和标记配置属性) | | **map_delete_markers** | draw | 前端 | 删除地图上的标记,支持删除指定标记或清空所有标记 | `markerId`(标记ID)、`clearAll`(清空所有) | **用户可通过SDK自定义业务方面的MCP工具** ### MCP URL地址组成 VJMap MCP系统的URL地址支持工具过滤功能,可以通过URL参数来控制可用的工具分类和具体工具。 #### URL格式 ``` /ai/mcp?sessionId={sessionId}&include_categories={categories}&exclude_categories={categories}&include_tools={tools}&exclude_tools={tools}&token={token} ``` 官方默认mcp地址为 ``` https://vjmap.com/server/ai/mcp?sessionId={前端每次会话的sessionID}&include_categories=query,map,draw,custom&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MiwiVXNlcm5hbWUiOiJhZG1pbjEiLCJOaWNrTmFtZSI6ImFkbWluMSIsIkF1dGhvcml0eUlkIjoiYWRtaW4iLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjo0ODEzMjY3NjM3LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTY1OTY2NjYzN30.cDXCH2ElTzU2sQU36SNHWoTYTAc4wEkVIXmBAIzWh6M ``` 如果只需要后端MCP工具,无需与前端交互sessionId参数可不填写。如 ``` https://vjmap.com/server/ai/mcp?include_categories=query,map&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MiwiVXNlcm5hbWUiOiJhZG1pbjEiLCJOaWNrTmFtZSI6ImFkbWluMSIsIkF1dGhvcml0eUlkIjoiYWRtaW4iLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjo0ODEzMjY3NjM3LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTY1OTY2NjYzN30.cDXCH2ElTzU2sQU36SNHWoTYTAc4wEkVIXmBAIzWh6M ``` #### 参数说明 | 参数名 | 类型 | 必填 | 描述 | 示例值 | | -------------------- | ------ | ---- | -------------------------------------- | ----------------------------------------- | | `sessionId` | string | 是 | 会话ID,用于标识当前会话 | `session_123456` | | `token` | string | 是 | 访问令牌,用于身份验证 | `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...` | | `include_categories` | string | 否 | 包含的工具分类,多个分类用逗号分隔 | `query,map,drawing` | | `exclude_categories` | string | 否 | 排除的工具分类,多个分类用逗号分隔 | `delete,utility` | | `include_tools` | string | 否 | 包含的具体工具名称,多个工具用逗号分隔 | `listmaps,fullSearch` | | `exclude_tools` | string | 否 | 排除的具体工具名称,多个工具用逗号分隔 | `deletemap` | #### 工具分类 系统支持以下工具分类: | 分类名称 | 分类值 | 描述 | 包含的工具 | | -------------- | --------- | ------------------ | ------------------------------------------------------------ | | 地图管理 | `map` | 地图的基本CRUD操作 | `listmaps`, `getMapImage`, `map_get_info`, `map_open_or_switch`, `map_view_control` | | 数据查询 | `query` | 数据查询和搜索相关 | `fullSearch`, `queryFeatures`, `extractTable`, `sqlDocHelper` | | 图形处理 | `drawing` | 图形创建和处理相关 | `createMap`, `createDwgDocHelper`, `map_draw_geojson`, `map_create_markers` | | 删除工具 | `delete` | 删除地图相关 | `deletemap` | | 绘图工具 | `draw` | 前端绘图相关 | `map_draw_geojson`, `map_delete_drawn_features`, `map_create_markers`, `map_delete_markers` | | 运行代码 | `runcode` | 代码执行相关 | `map_execute_code` | | 前端自定义分类 | `custom` | 前端自定义分类 | | #### 使用示例 ##### 示例1:只包含查询和地图工具 ``` /ai/mcp?sessionId=session_123456&include_categories=query,map&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` **说明:** 只启用查询类工具(`fullSearch`, `queryFeatures`, `extractTable`, `sqlDocHelper`)和地图类工具(`listmaps`, `getMapImage`, `map_get_info`, `map_open_or_switch`, `map_view_control`) ##### 示例2:排除删除工具 ``` /ai/mcp?sessionId=session_123456&exclude_categories=delete&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` **说明:** 启用除删除类工具外的所有工具,确保用户无法执行删除操作 ##### 示例3:只包含特定工具 ``` /ai/mcp?sessionId=session_123456&include_tools=listmaps,fullSearch,map_get_info&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` **说明:** 只启用三个特定工具:`listmaps`(获取地图列表)、`fullSearch`(全文搜索)、`map_get_info`(获取地图信息) ##### 示例4:排除特定工具 ``` /ai/mcp?sessionId=session_123456&exclude_tools=deletemap,map_execute_code&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` **说明:** 启用所有工具,但排除`deletemap`(删除地图)和`map_execute_code`(执行代码)这两个可能有安全风险的工具 ##### 示例5:组合使用包含和排除 ``` /ai/mcp?sessionId=session_123456&include_categories=query,map&exclude_tools=deletemap&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` **说明:** 只启用查询和地图类工具,但进一步排除`deletemap`工具,提供更精细的控制 #### 注意事项 1. **优先级规则:** `include_tools` 和 `exclude_tools` 的优先级高于 `include_categories` 和 `exclude_categories` 2. **参数冲突:** 如果同一个工具既在 `include_tools` 中又在 `exclude_tools` 中,`exclude_tools` 优先 3. **空值处理:** 如果某个参数为空或未提供,系统会使用默认行为(启用所有工具) 4. **安全性:** 建议在生产环境中使用 `exclude_categories=delete` 或 `exclude_tools=deletemap` 来防止意外删除操作 5. **URL编码:** 如果参数值包含特殊字符,请进行URL编码 在`cursor`中加入MCP地址进行问答,效果如下 ![image-20250803191926787](https://vjmap.com/blogimages/202508031919853.png) ```json { "mcpServers": { "vjmap": { "url": "https://vjmap.com/server/ai/mcp?include_categories=query,map&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MiwiVXNlcm5hbWUiOiJhZG1pbjEiLCJOaWNrTmFtZSI6ImFkbWluMSIsIkF1dGhvcml0eUlkIjoiYWRtaW4iLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjo0ODEzMjY3NjM3LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTY1OTY2NjYzN30.cDXCH2ElTzU2sQU36SNHWoTYTAc4wEkVIXmBAIzWh6M" } } } ``` 如果需要与前端交互时,可在AI对话框中MCP工具中复制MCP地址。 ![image-20250803192608314](https://vjmap.com/blogimages/202508031926432.png) ```json { "mcpServers": { "vjmap": { "url": "https://vjmap.com/server/ai/mcp?sessionId=session-6323558e&include_categories=query,map,draw,custom&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MSwiVXNlcm5hbWUiOiJyb290MSIsIk5pY2tOYW1lIjoicm9vdDEiLCJBdXRob3JpdHlJZCI6InJvb3QiLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjoxOTQyMzg5NTc0LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTYyNzAyODU3NH0.l1pP9FXu6ARDaaa-6ma0lp7ftbIk2t6rgmSmTqXry10" } } } ``` 注:每次会话的sessionId是不同的,上面的地址应该根据当前的会话来生成 ### MCP 前端SDK及以自定义工具示例 ```html vjmap demo
``` 上述代码中`ai-chat-lib.umd.js`是ai 会话的`ui`库。 上述代码中`vjmap-mcp-sdk.umd.js`是vjmap mcp 的`sdk`库。主要是用来于服务端通信, 同时提供接口让用户自定义工具, 这库也可以用`npm`进行安装。用法如下 ### 唯杰地图MCP SDK 安装 ```bash npm install vjmcpsdk ``` ### 唯杰地图MCP SDK 用法 ```js import vjmap from vjmap import { initializeMCP, createTool, z } from vjmcpsdk let apiUrl = map.getService().baseUrl(); let token = map.getService().accessToken; // 初始化MCP连接,使用新的简化API console.log('初始化MCP连接...') let mcpInstance = await initializeMCP({ apiBase: apiUrl, autoConnect: true, mapInstance: map }) const sessionId = mcpInstance.state.sessionId // 注册地图相关工具 await mcpInstance.registerMapTools() // 注册自定义工具示例 (如果要自定义工具,则用下面的示例) await registerCustomTool(mcpInstance, z) // flashPos 函数实现 const flashPos = (bounds) => { map.fitMapBounds(vjmap.GeoBounds.fromArray(bounds), { padding: 300 }) return new Promise((resolve) => { const routePath = vjmap.GeoBounds.fromArray(bounds).toPointArray(); routePath.push(routePath[0]) let geoLineDatas = []; geoLineDatas.push({ points: map.toLngLat(routePath), properties: { opacity: 1.0 } }) let polylines = new vjmap.Polyline({ data: geoLineDatas, lineColor: 'yellow', lineWidth: 3, lineOpacity: ['get', 'opacity'], isHoverPointer: false, isHoverFeatureState: false }); polylines.addTo(map); vjmap.createAnimation({ from: 1, to: 10, duration: 1000, onUpdate: (e) => { const data = polylines.getData(); if (data && data.features && data.features[0]) { data.features[0].properties.opacity = parseInt(e.toString()) % 2 ? 1.0 : 0; polylines.setData(data); } }, onStop: () => { polylines.remove() resolve({}) }, onComplete: () => { polylines.remove() resolve({}) } }) }) } // 注册自定义工具示例 async function registerCustomTool(mcpInstance, z) { if (!mcpInstance) return // 使用 zod 定义工具输入 schema const inputSchema = z.object({ bounds: z.array(z.number()).length(4).describe('边界坐标数组 [minX, minY, maxX, maxY]') }) // 创建工具定义 const tool = createTool( 'map_flash_position', '在地图上闪烁显示指定边界区域', inputSchema, 'custom' ) // 创建工具处理函数 const handler = async (args) => { try { const validatedArgs = inputSchema.parse(args) const { bounds } = validatedArgs // 调用 flashPos 函数 await flashPos(bounds) return { content: [{ type: 'text', text: `已在地图上闪烁显示边界区域: [${bounds.join(', ')}]` }] } } catch (error) { return { content: [{ type: 'text', text: `工具执行失败: ${error instanceof Error ? error.message : String(error)}` }], isError: true } } } // 注册工具 mcpInstance.registerTool(tool, handler, "custom") // 重要:注册工具到服务器,这样服务端才能获取到自定义工具 try { const serviceManager = mcpInstance.getServiceManager() if (serviceManager) { await serviceManager.registerToolsToServer() console.log('自定义工具已成功注册到服务器') } } catch (error) { console.error('注册自定义工具到服务器失败:', error) } } ``` ### 后台设置 在后台数据目录的`data/ai/aisvr_config.yaml`中进行配置 ![image-20250803190902021](https://vjmap.com/blogimages/202508031909069.png) --- # 地图AI对话 ## 使用 进入[唯杰地图云端管理平台](https://vjmap.com/app/cloud),点击每个图上面的`AI`图标,可与此地图进行`AI`对话;或者点击打开地图后,点击右下角的`AI` 对话图标。 ![image-20250211091239128](https://vjmap.com/blogimages/202502110912181.png) [点击打开在线体验地址](https://vjmap.com/server/_mapviewer?render=1&mapid=sys_zp&version=v1&light=false&title=sys_zp-v1&mobile=false&enableClickHighlight=false&enableAiChat=true&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MiwiVXNlcm5hbWUiOiJhZG1pbjEiLCJOaWNrTmFtZSI6ImFkbWluMSIsIkF1dGhvcml0eUlkIjoiYWRtaW4iLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjo0ODEzMjY3NjM3LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTY1OTY2NjYzN30.cDXCH2ElTzU2sQU36SNHWoTYTAc4wEkVIXmBAIzWh6M) ![image-20250211143145348](https://vjmap.com/blogimages/202502111431393.png) ![](https://vjmap.com/blogimages/aichat1.gif) [点击查看视频教程 https://www.bilibili.com/video/BV1dGNreCEA8/](https://www.bilibili.com/video/BV1dGNreCEA8/) [用AI自动生成CAD气温曲线图 https://www.bilibili.com/video/BV1JtKjenEhF](https://www.bilibili.com/video/BV1JtKjenEhF) [博文](https://blog.csdn.net/vjmap/article/details/145599203) ## 安装 通过动态引入script的方式引入`vjchat` (推荐,可页面初始加载时减少加载脚本体积) ```js // 加载vjchat库 // @ts-ignore if (typeof vjchat !== "object") { // 因为vjchat.umd.js依赖vjmap, 请先 import vjmap from "vjmap", 再把vjmap做为全局对象 window.vjmap = vjmap; // 如果没有环境 await vjmap.addScript([{ src: "https://vjmap.com/server/_cloud/lib/vjchat.umd.js" }]) } // @ts-ignore let aiChat = vjchat.attachMap(map, vjmap, { title: "唯杰地图AI对话", // 标题 context: context, // 替换默认的上下文函数 theme: { mode: "dark" }, silent: false, hiddenUI: false, resizable: true, minimizePosition: "right", position: { right: 10, top: 100 }, size: { width: 420, height: 500 }, buttonControl: { position: "top-left", left: -40, top: 5 }, opacity: 0.8, // 设置透明度 onClose: () => { // 关闭时回调; } }) // 销毁 // aiChat.destroy(); ``` 或者通过 `npm instal vjchat` 引入 `vjchat` ```js import { attachMap } from "vjchat" attachMap(map, vjmap, { title: "唯杰地图AI对话", // 标题 ... // 内容同上 }) ``` ## 原理 ![image-20250211145933029](https://vjmap.com/blogimages/202502111459063.png) - 用户输入问题或相关需求 - 根据用户问题去知识库检索出与问题相关的知识给大模型 - 大模型根据用户问题和获取的知识库生成相应的代码 - 前端解析相关代码并执行输出效果展示 ## 提问技巧 因为大模型需要根据问题和匹配到的知识库来自动生成代码,所以能不能根据问题自动匹配到相关知识库是能否回答正确的关键前提。(建议打开设置中 知识库搜索结果确认 ,在每次回答前对自动匹配到的知识库进行确认) 可以`AI`对话工具栏对话中点击`导出工程模板` [点击下载工程代码](https://vjmap.com/download/vjmap-app-vue3.zip) , 在`src\chat\sdk.ts` 查看全部代码知识库。 ![image-20250213092136220](https://vjmap.com/blogimages/202502130921324.png) 提问技巧就是`一定要尽量命中文档中的函数说明`, 并且`同一句话涉及到多个需求时`,要用`标点符号`如逗号来隔开。 下面是一些`错误提问`和`正确提问`的相关示例说明: - [✗] 中间添加一个蓝色标记 - [✓] 在地图范围中间,加一个蓝色点标记 解释:要在地图中间加一个点标记,涉及到知识库中的两个函数,一个是`获取地图范围`getMapGeoBounds,一个是`增加一个点标记Marker` addMarker, 所以提问时要涉及到要地图范围才能获取到中间的点位置,同时这句话有两个需求,同时尽量用逗号分开。 - [✗] 创建红色的圆 - [✓] 通过json数据创建新的地图, 创建中心点为[10,10],半径为5,颜色为红色的圆 解释:创建圆有可能是前端通过圆符号创建圆,也可能是后台通过json创建CAD图形对象的圆,所以要明确需求,需求需在文档知识库中能查到。同时绘制的参数如位置信息要明确。 - [✗] 采集点坐标输入传感器名称和数值,创建Marker点击能查看相关属性。 - [✓] 1、采集一个点坐标,获取坐标point。 2、弹出输入对话框,输入传感器名称和数值,获取返回值data。 3、在point位置上创建一个Marker, 可拖动,数据内容为data, 弹窗内容为data,以表单形式展示。 解释:如需求比较复杂时,尽量分步骤说明,同时可用相关变量进行说明。 ## 教程 ![image-20250211150518327](https://vjmap.com/blogimages/202502111505364.png) ### 图形交互与对话模式 图形交互会检索知识库并自动执行大模型生成的代码并对地图进行操作。 对话模式只会与大模型进行对话(不会检索知识库),不会执行代码操作。 ### 不立即执行代码 勾选后,代码不会立即执行,需要手动点击`执行代码`,或点击`编辑代码`再执行 ![image-20250211150941350](https://vjmap.com/blogimages/202502111509415.png) ### 知识库搜索结果确认 根据用户问题去知识库检索出与问题相关的知识给大模型,有时会出现与问题无关的知识库或没有搜索到相关的知识,这时候用户可以选中 `知识库搜索结果确认` 来手动选择相关知识库。 ![image-20250211151254424](https://vjmap.com/blogimages/202502111512469.png) 如输入"增加一个功能按钮,按钮名为放大一级,功能为将地图缩放级别放大一级"时,系统会自动把相关的两个函数知识库选中,这时候用户可以手动再选择相关的进行确认后,再交给大模型处理。 ### 知识库搜索结果相关性排序 对知识库相关性进行后排序(建议选中) ### 知识库最大返回条数 对知识库进行相关性排序前从数据库中获取的知识库最大返回条数 ### 关联历史对话数据 **默认不关联历史对话**,也就是上次的输入和输出对下次输入输出没有影响 ,没有上下文数据。如有必要,可进行设置。 ### 大模型设置 ![image-20250211152318677](https://vjmap.com/blogimages/202502111523723.png) 默认情况下,会调用唯杰地图后端的大模型服务接口,如私有化部署时没有部署唯杰地图后端的大模型服务和想自定义自己申请或已部署的大模型,也可以在前端自定义设置大模型接口地址。 目前支持 `ollama`和`openAI`的接口的大模型。配置示例如下 如设置qwen2.5的`openAI`模型 ![image-20250211153239987](https://vjmap.com/blogimages/202502111532024.png) 使用`XAI`的`openAI`模型 ![image-20250211153400305](https://vjmap.com/blogimages/202502111534334.png) 使用本地部署的`ollama`模型 ![image-20250211153609054](https://vjmap.com/blogimages/202502111536081.png) ### 粘贴上次用户输入 ![image-20250211154433208](https://vjmap.com/blogimages/202502111544231.png) 可用户想在上次输入的基础上,进行修改后再提问,可点击“粘贴上次用户输入” ### 清空会话内容 清空会话中的所有内容。(清空后可在历史记录中查看历史数据) ### 撤销上次操作 ![image-20250211154825934](https://vjmap.com/blogimages/202502111548957.png) 可点击`撤销上次操作`对上次执行的代码结果进行撤销操作(注:有些步骤无法撤销) ### 清空地图除底图外的所有数据 点击后会清空地图上除底图外的所有数据 ### 清空地图上绘图数据 点击后会清空地图上绘制图层draw上面的所有数据 ### 清空地图上所有点标记 点击后会清空地图上所有点标记Marker ### 导出工程模板 可导出为`Vue3`的工程模板,方便调试和查看`全部SDK代码`。 在工程中也可以加入`用户业务代码`与AI对话。 ![image-20250211160442919](https://vjmap.com/blogimages/202502111604955.png) ### 执行代码 可加载历史对话代码或加载上一次代码,可编辑相关代码,点击执行,可以代码中加入 `debugger` 进行调试 ![image-20250211160531600](https://vjmap.com/blogimages/202502111605638.png) ### 用户数据集 用户数据集是指提前在此界面中输入用户的GeoJson数据。当在AI对话框中输入"获取用户数据"时将获取此数据。 比如用户有一组geojson数据,需要将geojson中的点用Marker在图中进行标注。可提前把geojson拷贝到“用户数据集”窗口。 ![image-20250211161238256](https://vjmap.com/blogimages/202502111612295.png) 输入"获取用户数据中的所有点类型的数据,在相应位置添加点标记, 弹窗内容为数据的属性值" 进行提问,执行结果为: ![image-20250211161526454](https://vjmap.com/blogimages/202502111615500.png) ### 常见提问词 ![image-20250211161759092](https://vjmap.com/blogimages/202502111617118.png) 点击常见提问词,可点击某项的“对话”进行直接对话 ![image-20250211162029067](https://vjmap.com/blogimages/202502111620103.png) ### 地图图像资源管理 在创建符号`symbol`时,需要用到图像资源。需提前在地图图像资源管理中对图像进行定义。 默认的图像名称有`_markerIcon_`、`_scatterDot_`、`_pulsingDot_` 如自定义图像,可选择本地图像或输入svg内容进行自定义。 ![image-20250211162716181](https://vjmap.com/blogimages/202502111627212.png) 如提前上传一个girl.png的图片名称改为girl, 输入问题 “在地图的绘制范围内随机生成5个点, 根据这些位置分别绘制点符号,符号图标为girl” 执行绘制结果如下 ![image-20250211162932763](https://vjmap.com/blogimages/202502111629819.png) ### 查看知识库 点击搜索知识库,点击要搜索的相关内容 ,在搜索结果中点击查看 ![image-20250211163240464](https://vjmap.com/blogimages/202502111632506.png) ![image-20250211163305440](https://vjmap.com/blogimages/202502111633469.png) ![image-20250211163329720](https://vjmap.com/blogimages/202502111633745.png) ### 历史会话记录 ![image-20250211163436361](https://vjmap.com/blogimages/202502111634405.png) ## 如何用在线AI网站免费生成代码 用在线API接口会消耗token需要花`Money`, 通过这办法可用在线AI网站`免费生成代码` - 1、选中设置中“只生成问题模板,不自动生成代码” ![image-20250213171532518](https://vjmap.com/blogimages/202502131715689.png) - 2、在提问区输入要提问的问题,如 ``` 获取北京市最近一个月的气温json数组。通过此天气的json数据创建新的地图, 要求绘制一个气温变化曲线图,绘制最高温度曲线(红色)和最低温度曲线(绿色),同时把天气情况写在最高温度曲线对应上的点上面。X轴为日期(要有刻度),y轴为气温(要有刻度, 刻度最小值为-20,最大值为40, 每个刻度的单位为5) ``` - 3、点击发送开始提问,在之后弹出的`AI提问问题`对话框中选择`复制`,把问题复制下来。 ![image-20250213174811969](https://vjmap.com/blogimages/202502131748041.png) - 4、打开在线AI网站如 [deepseek](https://chat.deepseek.com/), 复制刚粘贴的内容 - 5、复制AI生成的代码 ![image-20250213180109112](https://vjmap.com/blogimages/202502131801169.png) - 6、在`AI提问问题`对话框中点击`执行代码`或者点击工具栏菜单中的`执行代码`,粘贴刚复制的代码,点击执行 ![image-20250213193012076](https://vjmap.com/blogimages/202502131930136.png) - 7、等结果执行完成,就可以查看执行效果了 ![image-20250213193218984](https://vjmap.com/blogimages/202502131932051.png) ## 接口定义 attachMap函数相关参数定义 ```ts export type ThemeMode = 'light' | 'dark' export interface ThemeConfig { mode?: ThemeMode primaryColor?: string borderColor?: string backgroundColor?: string containerBackground?: string textColor?: string codeBgColor?: string } export interface BaseChatOptions { type?: ModelType temperature?: number streamCallback?: (content: string) => void interactiveSystemPrompt?: string // 添加图形交互提示词 chatSystemPrompt?: string rankPrompt?: string; historyChatNum?: number topK?: number // 添加对话提示词 scoreThreshold?: number diableAutoExecCode?: boolean knowledgeConfirm?: boolean knowledgeRank?: boolean rankScoreThreshold?: number similarityScoreTopK?: number welcomePrompt?: string } export interface OllamaChatOptions extends BaseChatOptions { type: 'ollama' model: string baseUrl: string } export interface OpenAIChatOptions extends BaseChatOptions { type: 'openai' model: string apiKey: string baseUrl?: string } export type ChatOptions = OllamaChatOptions | OpenAIChatOptions export interface ChatMapOptions { theme?: ThemeConfig; chat: ChatOptions; resizable?: boolean; silent?: boolean; hiddenUI?: boolean; userVectorDocs?: string; size?: { width: number; height: number; }; position?: { left?: number; top?: number; right?: number; bottom?: number; }; opacity?: number; minimizePosition?: 'left' | 'right'; title?: string; historyChatNum?: number; topK?: number; scoreThreshold?: number; onClose?: () => void; } const defaultThemes: Record = { light: { primaryColor: '#409eff', borderColor: '#c0c4cc', backgroundColor: '#f5f7fa', containerBackground: '#ffffff', textColor: '#303133', codeBgColor: '#f6f8fa' }, dark: { primaryColor: '#507F80', borderColor: '#666668', backgroundColor: '#2d2d2d', containerBackground: '#1e1e1e', textColor: '#e5eaf3', codeBgColor: '#282c34' } } // 默认配置 const defaultOptions: Partial = { size: { width: 600, height: 500 }, opacity: 0.8, minimizePosition: 'right' } ``` ## 知识库SDK文档 [点击查看知识库SDK文档](/guide/aisdk.html) --- # AI绘CAD图 ## # 用Trae、Cursor免费生成CAD图纸 ### 示例效果 Trae 编辑器 ![](http://vjmap.com/blogimages/trae.gif) Cursor 编辑器 ![](http://vjmap.com/blogimages/cursor.gif) ### 使用步骤 - 1、在github下载项目源码 [https://github.com/vjmap/aidraw](https://github.com/vjmap/aidraw) ​ 或者直接点击 [https://vjmap.com/download/aidraw.zip](https://vjmap.com/download/aidraw.zip) 进行下载 - 2、用`Trae`或`Cursor` 编辑器打开此项目源码 - 3、在插件市场搜索`LiveServer`进行安装 (可选,如果有运行html的服务程序也可以不需要此步骤, 或者`直接双击index.html`文件在浏览器里面打开[此方案`不支持`获取本地文件]) - 4、选中`index.html`文件,右键,在菜单中选择`Open with Live Server` - 5、在 `trae` 编辑器中进入`Builder` 模式, 或 `cursor` 中进入`Composer`或`Edit`模式,输入要绘制的内容进行提问 - 6、生成完成后,可以打开 `draw.js`查看生成的内容,在浏览器中刷新页面查看生成的效果。 - 7、在生成的页面中点击右上角的`下载`按钮,可下载生成的`autocad`支持的`dwg`图形文件。 ### 教程视频 [https://www.bilibili.com/video/BV1sRRYY2EBo/](https://www.bilibili.com/video/BV1sRRYY2EBo/) ### 相关示例 ``` 绘制常用三角函数的示例图,用于教学用,要求有刻度、坐标轴和格网,x坐标范围-10到10 ``` ![image-20250309195159868](https://vjmap.com/blogimages/202503091952067.png) ``` 绘制一只可爱的小猫,细节要丰富 ``` ![image-20250309195734749](https://vjmap.com/blogimages/202503091957823.png) ```js data目录中的china.json是中国地图的geojson数据, 通过这数据,绘制出中国地图,每个省需要填充颜色(颜色随机且符合地图制图规范),同时标注名称,字体颜色为青色, 最后绘制整个图的图框 ``` ![image-20250309200219874](https://vjmap.com/blogimages/202503092002957.png) ``` 画一个长80宽60厚2的板,要求周围带四个8mm孔,中心带一个16mm孔,周围圆角R5,并进行长度标注 ``` ![image-20250309200527023](https://vjmap.com/blogimages/202503092005107.png) ``` 以下是北京市一周的气温情况,要求绘制一个气温变化曲线图,绘制最高温度曲线(红色)和最低温度曲线(绿色),每个值上面绘制一个小圆,同时把天气情况写在最高温度曲线对应上的点上面。X轴为日期y轴为气温,需要绘制刻度值和坐标格网 [ {"date": "2025-02-01", "weather": "晴", "max_temp": 5, "min_temp": -4}, {"date": "2025-02-02", "weather": "阴", "max_temp": 9, "min_temp": 0}, {"date": "2025-02-03", "weather": "晴", "max_temp": 11, "min_temp": 8}, {"date": "2025-02-04", "weather": "多云", "max_temp": -2, "min_temp": -6}, {"date": "2025-02-05", "weather": "大雪", "max_temp": -4, "min_temp": -7}, {"date": "2025-02-06", "weather": "晴", "max_temp": 6, "min_temp": -2}, {"date": "2025-02-07", "weather": "多云", "max_temp": 6, "min_temp": -1} ] ``` ![image-20250309200817897](https://vjmap.com/blogimages/202503092008015.png) 生成的图点击`下载`,可直接下载`dwg`文件的图,用`autocad`直接打开查看 ![image-20250309202030768](https://vjmap.com/blogimages/202503092020854.png) ## 唯杰AI绘CAD图使用 进入[唯杰地图云端管理平台](https://vjmap.com/app/cloud),点击右边的`AI绘制CAD图`进入 ![image-20250303133120273](https://vjmap.com/blogimages/202503031331297.png) ![image-20250303133149480](https://vjmap.com/blogimages/202503031331512.png) [点击查看视频教程 https://www.bilibili.com/video/BV1g19BYNECf/](https://www.bilibili.com/video/BV1g19BYNECf/) ## 绘制图形 ### 1、根据问题描述绘制图形 - 在输入框中输入要绘制的内容描述,点击发送 - AI根据描述内容,自动生成相应代码 - 自动执行(或可选择手动执行)代码,生成CAD图形 - 可点击直接下载生成的DWG图 比如输入: ``` 绘制一只可爱的小猫,细节要丰富 ``` ![image-20250303135551282](https://vjmap.com/blogimages/202503031355337.png) 注:在设置中设置大模型地址和大模型名称,不同的大模型生成的图效果不一样,此次示例生成的图用的大模型为`claude 3.7 sonnet` ### 2、直接指定数据绘制图形 如果`数据少`时,可以在提问中直接把数据写上进行提问 比如输入: ``` 下面有几条线的坐标,用不同颜色绘制出来。 [[0, 0],[4, 0]] [[4, 0],[2, 5],[3, 7]] [[2, 3],[0, 0]] ``` ![image-20250303140212862](https://vjmap.com/blogimages/202503031402902.png) ### 3、根据用户数据绘制图形 如果用户数据量多时,因为大模型上下文`字数限制`,并且大模型是按字的`token`进行收费的,所以可以先点击`用户数据`, 把数据提前输入在`用户数据`中,让代码在运行时获取数据生成图形。需要注意的是:因为大模型`不知道用户数据格式`,所以需要在问题中`把数据格式描述清楚`。 比如绘制中国地图,可先进入[阿里数据可视化平台](https://datav.aliyun.com/portal/school/atlas/area_selector), 选择省数据,点击`复制geojson` ![image-20250303142412632](https://vjmap.com/blogimages/202503031424704.png) ![image-20250303142742809](https://vjmap.com/blogimages/202503031427869.png) ``` 用户数据格式是中国地图的geojson数据, 通过这数据,绘制出中国地图,每个省需要填充颜色(颜色随机且符合地图制图规范),同时标注名称,字体颜色为青色, 最后绘制整个图的图框 ``` ![image-20250303143121222](https://vjmap.com/blogimages/202503031431273.png) 注: 此次示例生成的图用的大模型为`claude 3.7 sonnet` 上面的`geojson`是通用数据,只需要描述中说明用户数据是geojson格式即可。但对于其他数据,需对数据格式进行描述才可。比如一个cvs的数据 ``` 1,Item1,3.5,7.2,A 2,Item2,1.2,4.8,B 3,Item3,5.6,9.1,C 4,Item4,2.3,6.7,A ``` 提问时应该把每个字段描述清楚,同时把需求说清楚。 ``` 用户数据是一组csv格式的字符串数据,每项的内容为“序号,名称,x,y,类型”。获取数据中每行的x,y数据绘制一个小圆,颜色为类型来决定。小圆下面写上名称。(圆半径和字体高度根据所有数据的范围来确定) ``` ![image-20250303143743767](https://vjmap.com/blogimages/202503031437814.png) ![image-20250303144343835](https://vjmap.com/blogimages/202503031443895.png) 注: 此次示例生成的图用的大模型为`qwen-coder-plus-latest` ### 4、引用其他CAD图的实体对象绘制 对于一些复杂的符号或符号在之前的图中已经存在,可以通过引用CAD图中的实体对象来进行绘制。 首先在[唯杰地图云端管理平台](https://vjmap.com/app/cloud)上传这个CAD图模板图(命名一个mapid),通过`内存直接打开`的方式,点击要引用的实体对象,在右侧属性面板中获取此对象的`objectid` ![image-20250303145548835](https://vjmap.com/blogimages/202503031455883.png) 示例: ``` 绘制一个停车场示例示例图(图纸宽300,高100),2排9列,每个车位置都需要绘制出车的矩形范围、标注出编号和绘制车符号[块参照来源于克隆图形car ,克隆实体id为331, 块原来范围为[587613582,3103823616,587618261,3103825364],基点在范围中点]。矩形范围画大点包含编号和车符号(占矩形1/2大小),编号在上面,车符号在下面,两个不要重叠。 ``` ![image-20250303150028471](https://vjmap.com/blogimages/202503031500520.png) ### 5、引用其他CAD图或图层数据绘制 也可以引用其他CAD整个图,或CAD图中的几个图层数据,比如`sys_world` 中`网络线`和`经纬度标注`的内容如下 ![image-20250303151049961](https://vjmap.com/blogimages/202503031510017.png) ``` 基于图sys_world,选择图中的"网格线"和"经纬度标注"图层,图纸绘制范围为{"min":{"x":1756,"y":2537},"max":{"x":27713,"y":17957}}。请随机绘制50个颜色不同的小圆,小圆下面标注当前序号。 ``` ![image-20250303151442124](https://vjmap.com/blogimages/202503031514172.png) ## 生成图的方式 ### 1、重新生成新的图形 ​ 默认情况下每次创建会新建一个图形并自动打开 ​ ![image-20250303152233475](https://vjmap.com/blogimages/202503031522508.png) ### 2、直接加至当前图形中 如果要基于当前的图形上再加数据,可以选择`把创建的实体自动直接加至当前地图中` ![image-20250303153502053](https://vjmap.com/blogimages/202503031535129.png) ### 3、交互式手动加至当前图形中 ![image-20250303153906649](https://vjmap.com/blogimages/202503031539718.png) ## 下载生成的CAD图 生成图形后,点击工具栏上面的`下载CAD图`,即可把生成的`dwg`图下到本地。用`autocad`打开即可查看。 私有化部署后,因为数据安全考虑,需要后台配置下才可以正常下载。 打开`data`目录中的`config.json`文件, 在`system`键下面设置要映射的目录地址的下载路径,再在`usercfg`通过设置`shareWorkspace`(绘图所在工作区名称,为空时为默认工作区)、`shareDownload`(token_router_static中设置的下载路径key), 示例如下: ```json { "system" : { "token_router_static": "_download_cad::./data/workspace/share/mapfiles" }, "usercfg": { "shareWorkspace": "share", "shareDownload": "_download_cad" } } ``` ## 相关设置 ### 清空会话内容 因为聊天时,会根据设置把历史记录(默认五条)做为上下文传入,如果要绘制的内容发生变化时,`一定要记得清空会话内容`(相当于新建会话) ### 在线大模型API设置 大模型可以在后台进行配置,也可以在前端由用户直接设置。 ![image-20250303160016995](https://vjmap.com/blogimages/202503031600044.png) 更多设置可参考文档 [https://vjmap.com/guide/aichat.html](https://vjmap.com/guide/aichat.html) ## 如何用在线的大模型如deepseek免费生成图形 用在线API接口会消耗token需要花`Money`, 通过这办法可用在线AI网站`免费生成代码` - 1、选中设置中“只生成问题模板,不自动生成代码” ![image-20250213171532518](https://vjmap.com/blogimages/202502131715689.png) - 2、在提问区输入要提问的问题,如 ``` 获取北京市最近一个月的气温json数组。通过此天气的json数据创建新的地图, 要求绘制一个气温变化曲线图,绘制最高温度曲线(红色)和最低温度曲线(绿色),同时把天气情况写在最高温度曲线对应上的点上面。X轴为日期(要有刻度),y轴为气温(要有刻度, 刻度最小值为-20,最大值为40, 每个刻度的单位为5) ``` - 3、点击发送开始提问,在之后弹出的`AI提问问题`对话框中选择`复制`,把问题复制下来。 ![image-20250213174811969](https://vjmap.com/blogimages/202502131748041.png) - 4、打开在线AI网站如 [deepseek](https://chat.deepseek.com/), 复制刚粘贴的内容 - 5、复制AI生成的代码 ![image-20250213180109112](https://vjmap.com/blogimages/202502131801169.png) - 6、在`AI提问问题`对话框中点击`执行代码`或者点击工具栏菜单中的`执行代码`,粘贴刚复制的代码,点击执行 ![image-20250213193012076](https://vjmap.com/blogimages/202502131930136.png) - 7、等结果执行完成,就可以查看执行效果了 ![image-20250213193218984](https://vjmap.com/blogimages/202502131932051.png) --- # 上传创建地图 ## 上传创建过程介绍 - 1、上传图形 `uploadMap(file: File)` (1) 获取文件md5值,根据文件的md5去服务端判断文件是否已上传,如果已上传返回文件id(此步非必须) (2) 上传文件至服务端后台,返回文件id(`fileid`)和`建议`的一个`mapid`值 - 2、创建图形 `OpenMap` 根据上传图形返回的文件id(`fileid`), 用户可设置创建图形的`mapid`值(也可以使用上传图形返回的建议的mapid,防止mapid重复)去创建图形。 如果`mapid`值在后台不存在,第一次调用 `OpenMap` 时,会根据文件id(`fileid`)去创建对应的`mapid`地图。 如果`mapid`在后台已存在(如第二次调用`OpenMap` 时),会直接打开此地图。 ## 通过唯杰地图云端管理平台上传图形 打开 [唯杰地图云端管理平台](https://vjmap.com/app/cloud/#/) 点击右边的上传图形选择一个图进行上传 ![](https://vjmap.com/blogimages/uploadmap.webp) ## 通过前端SDK上传图形 ```js // 创建服务对象 let svc = new vjmap.Service(env.serviceUrl, env.accessToken) // 选择文件获取file对象(略) // 上传图形 let res = await svc.uploadMap(file); // res.mapid = 'xxx'; // 可修改返回建议`mapid`值 // 创建图形 let data = await svc.openMap(res); ``` 具体实现可查看示例 [上传新图形](https://vjmap.com/demo/#/demo/map/service/06uploadmap) [上传新图形(基于ElementUI实现)](https://vjmap.com/demo/#/demo/map/service/06uploadmapelementui) ## 通过URL地址直接打开图形 通过这种方式无需上传图形,后台会根据URL地址自动下载图形,并创建打开地图。 ```js // --通过网络路径打开地图--传入DWG路径的http路径打开DWG图 // js代码 // 新建地图服务对象,传入服务地址和token let svc = new vjmap.Service(env.serviceUrl, env.accessToken) // 打开地图 // 地图ID (如果换成了您自定义的url地址,记得修改此名称id,改成您想要的唯一的名称即可) const mapid = "gym"; // 地图url地址,注意:url地址后四个字母必须为.dwg。如果实际中的地址四4位不是.dwg可以随便加个参数如 url?param=xxx&filetype=.dwg const httpDwgUrl = "https://vjmap.com/static/assets/data/gym.dwg" ; let res = await svc.openMap({ // (第一次打开此地图ID时,会根据fileid去获取dwg路径打开,之后会读取缓存打开) mapid: mapid, // 地图ID fileid: httpDwgUrl, // httpUsePost: true, // 如果是复杂的url地址,建议用post请求,请把httpUsePost设置为true, 默认false mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开 style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式 }) ``` 具体示例可查看 [通过网络路径打开地图](https://vjmap.com/demo/#/demo/map/service/05openMapFromHttp) ## 通过后端REST请求上传图形 具体可查看文档 [地图服务Rest接口](/guide/restinterface.html#上传图形) ### 上传图形 | | | |:-----|:--------------------| | 服务地址 | /api/v1/map/uploads | | 请求类型 | Post | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | fileToUpload | 文件二进制内容 || ### 创建或打开图形 | | | |:-----|:--------------------| | 服务地址 | /api/v1/map/openmap/{mapid} | | 请求类型 | Get或Post | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | version | 文件二进制内容 | | | layer | 样式 | | | geom | 以几何渲染方式打开 | true | | fileid | 文件唯一ID. 地图ID第一次打开时,需传递fileid | cfbe8dc085fb | | imageLeft | 图像左上角坐标x 第一次打开图像类型时有效 | undefined | | imageTop | 图像左上角坐标y 第一次打开图像类型时有效 | | | imageResolution | 图像分辨率 第一次打开图像类型时有效 | | | uploadname | 上传时的文件名 | zp.dwg | | mapfrom | 地图来源参数(协同图形有效) | | | mapdependencies | 地图依赖项(协同图形有效) | | | subfrom | 地图来源参数(子项的设置)(协同图形有效) | | | subdependencies | 地图依赖项(子项的设置)(协同图形有效) | | | stylename | 样式图层名称 | | | layeron | 要开的图层索引列表,格式如[0,1,3] | | | layeroff | 要关的图层索引列表,格式如[2,4] | | | clipbounds | 地图裁剪范围,范围如[x1,y1,x2,y2] | | | bkcolor | 背景颜色 | 0 | | lineweight | 线宽,格式如[1,1,1,1,0] | | | expression | 样式表达式 | | | secretKey | 如第一次创建图时要对图加密可设置此key | | | accessKey | 如图设置了密码需访问key | | 注:第一次上传图形时,如需设置密码,需设置`secretKey`值。计算方式为对密码明文四次md5加密,取加密后的字符串前16位。 如用 `nodejs` 在后台上传图形并创建打开图示例 ```js // app.js const fs = require('fs') const FormData = require('form-data') const axios = require('axios') const prompt = require('prompt'); (async ()=>{ const defaultServiceUrl = 'https://vjmap.com/server/api/v1'; const defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MiwiVXNlcm5hbWUiOiJhZG1pbjEiLCJOaWNrTmFtZSI6ImFkbWluMSIsIkF1dGhvcml0eUlkIjoiYWRtaW4iLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjo0ODEzMjY3NjM3LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTY1OTY2NjYzN30.cDXCH2ElTzU2sQU36SNHWoTYTAc4wEkVIXmBAIzWh6M'; let formData = new FormData(); let { filepath } = await prompt.get('filepath'); // 请输入要上传的文件路径 if (!filepath) return; let file = fs.createReadStream(filepath) formData.append('fileToUpload', file); let len = await new Promise((resolve, reject) => { return formData.getLength((err, length) => (err ? reject(err) : resolve(length))); }); let res = await axios({ url: `${defaultServiceUrl}/map/uploads?token=${defaultAccessToken}`, method: 'POST', data: formData, headers: { ...formData.getHeaders(), 'Content-Length': len, }, }); /* { fileid: 'c036d8ca23eb', mapid: 'c036d8ca23eb', // mapid是建议的mapid,如果之前上传打开过,则为之前的mapid uploadname: 'base1.dwg' } */ console.log(res.data); // 上传只是把图上传到了服务器, 您新建地图的时候的,第一次的时候openMap要传上传的fileid,然后给新建的图取个mapid打开就可以了,以后就可以直接通过mapid打开图了 let { mapid } = await prompt.get('mapid'); // 请输入要上传的文件路径 if (!mapid) { // 如果没有输入mapid,则用上传的时候返回的默认mapid mapid = res.data.mapid; } let url = `${defaultServiceUrl}/map/openmap/${res.data.mapid}?version=&layer=&geom=true&fileid=${res.data.fileid}&uploadname=${res.data.uploadname}&mapfrom=&mapdependencies=&subfrom=&subdependencies=&stylename=&layeron=&layeroff=&clipbounds=&bkcolor=0&lineweight=&expression=`; // 如需设置密码,如密码是123456 // md5四次加密 strMd5是字符串md5值计算方法,请自行实现 // let secretKey = strMd5(strMd5(strMd5(strMd5("123456")))).substr(0, 16); // url += "&secretKey=" + secretKey let result = await axios({ url: url, method: 'GET', headers: { token: defaultAccessToken, }, }); console.log(result) })(); ``` --- # 地图打开方式 |打开方式|说明|优点|缺点|应用场景| |:---|:---|:---|:---|:---| |内存模式(栅格)|请求新瓦片数据时,会在后台重新打开图形,在内存中渲染瓦片数据|第一次请求数据时速度快|以后请求新数据时速度慢(有可能要重新打开图);占用内存;不支持矢量瓦片|图形的临时查看| |几何渲染(栅格)|第一次请求数据时,会在后台打开图形,然后把图形数据保存至空间数据库,之后请求都会在空间数据库里查询数据进行几何渲染输出瓦片|之后请求数据快(有新瓦片请求时无需重新打开图)|第一次请求数据时会慢(因为把图形数据保存至空间数据库);会占用一部分空间|经常需查看的图形(前端不需要自定义样式)| |几何渲染(矢量)|同上|同上;以矢量瓦片形式渲染,可在前端自定义样式|同上;如果图大的话,级别小时矢量瓦片会比较大|经常需查看的图形(图形文件不大)| ::: tip 提示 内存模式: 第一次打开快,但不能使用空间查询,只能用表达式查询。每次打开时(如没有命中缓存数据)都需要在后台加载CAD图,以后打开速度不能秒开。适应于图形的临时查看。(会保留CAD图原来的实体结构) 几何渲染(栅格或矢量): 第一次打开时因为会把图形数据保存至空间数据库会慢点。可进行空间数据库查询,以后每次打开时都直接从空间数据库中获取数据生成瓦片数据,能实现秒开加载。适合于经常需查看的图形。(会把CAD图块实体进行打散方便空间查询,关联关系通过objectid进行关联判断) ::: ## 打开方式之间转换 ## 内存直接打开 -> 几何渲染 如果第一次是以 `内存直接打开`打开的。以后想以`几何渲染` 打开,可以在[唯杰地图云端管理平台](https://vjmap.com/app/cloud/)相应的图纸下,选择`栅格`或`矢量`, 再点击图形打开,这样就会把图形转换成空间数据库(需要花点时间)然后以`栅格`或`矢量`进行打开显示。 ![image-20240924104130384](https://vjmap.com/blogimages/202409241041541.png) ## 几何渲染 -> 内存直接打开 如果第一次是以 `几何渲染`打开的。以后想以`内存直接打开` 打开,可以在[唯杰地图云端管理平台](https://vjmap.com/app/cloud/)相应的图纸下,选择`内存`, 再点击图形打开。如果需要删除之前保存的空间数据库的内容,可点击旁边的操作按钮,点击`清空缓存数据` ![image-20240924104450544](https://vjmap.com/blogimages/202409241044567.png) ## 内存模式 ### 流程示例图
### 代码 ```js // 打开地图 let res = await svc.openMap({ mapopenway: vjmap.MapOpenWay.Memory, // 后台以内存方式打开已上传的图 ... }) // 新建地图对象 let map = new vjmap.Map({ style: svc.rasterStyle(), // 栅格瓦片样式 ... }); ``` ### 效果

## 几何渲染方式 ### 流程示例图
### 几何渲染(栅格) #### 代码 ```js // 打开地图 let res = await svc.openMap({ mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开 ... }) // 新建地图对象 let map = new vjmap.Map({ style: svc.rasterStyle(), // 栅格瓦片样式 ... }); ``` #### 效果

### 几何渲染(矢量) #### 代码 ```js // 打开地图 let res = await svc.openMap({ mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开 ... }) // 新建地图对象 let map = new vjmap.Map({ style: svc.vectorStyle(), // 矢量瓦片样式 ... }); ``` #### 效果 --- # 新建修改CAD图形 ## 支持的CAD实体类型 |类名称|说明| |:---|:---| |DbLine|直线| |DbCurve|曲线| |Db2dPolyline|二维折线| |Db3dPolyline|三维多段线| |DbPolyline|多段线| |BlockReference|块参照| |DbArc|圆弧| |DbCircle|圆| |DbEllipse|椭圆| |DbHatch|填充| |Text|单行文本| |DbMText|多行文本| |RasterImage|栅格图片| |DbShape|型实体| |Spline|样条曲线| |Wipeout|遮罩实体| |Dimension|标注| |Db2LineAngularDimension|角度标注[两条线]| |Db3PointAngularDimension| 角度标注[三点]| |DbAlignedDimension|对齐标注| |DbArcDimension|圆弧标注| |DbDiametricDimension|直径标注| |DbOrdinateDimension|坐标标注| |DbRadialDimension|半径标注| |DbRadialDimensionLarge|半径折线标注| |DbRotatedDimension|转角标注| |DbAttributeDefinition |属性注记| |DbTable |表格| |DbMLeader |引线标注| |DbSolid |实线实体| |DbLayer|图层| |DbTextStyle|文字样式| |DbDimStyle|标注样式| |DbLinetypeStyle|线型样式| |DbBlock|块定义| |DbDocument|数据库文档| ## 新建图形示例

## 新建图形代码 ```js (async () => { // --新建地图--在后台新建CAD图,然后在前端打开 // js代码 let svc = new vjmap.Service(env.serviceUrl, env.accessToken) let doc = new vjmap.DbDocument(); let entitys = []; let line1 = new vjmap.DbLine(); line1.start = [0, 0] line1.end = [0, 15] entitys.push(line1) let line2 = new vjmap.DbLine(); line2.start = [0, 14.1] line2.end = [2.99, 14.1] entitys.push(line2) let line3 = new vjmap.DbLine(); line3.start = [0, 0.9] line3.end = [2.99, 0.9] entitys.push(line3) let line4 = new vjmap.DbLine(); line4.start = [0, 9.95] line4.end = [5.8, 9.95] entitys.push(line4) let line5 = new vjmap.DbLine(); line5.start = [0, 5.05] line5.end = [5.8, 5.05] let hatch = new vjmap.DbHatch(); hatch.pattern = "SOLID"; hatch.color = 0xB43F32; hatch.points = [line4.start, line4.end, line5.end, line5.start]; entitys.push(hatch); entitys.push(line4) entitys.push(line5) let line6 = new vjmap.DbLine(); line6.start = [5.8, 5.05] line6.end = [5.8, 9.95] entitys.push(line6) let arc1 = new vjmap.DbArc(); arc1.center = [5.7963, 7.504]; arc1.radius = 1.8014; arc1.startAngle = 270 * Math.PI / 180.0; arc1.endAngle = 90 * Math.PI / 180.0; entitys.push(arc1) let arc2 = new vjmap.DbArc(); arc2.center = [5.7963, 7.504]; arc2.radius = 1.8014; arc2.startAngle = 90 * Math.PI / 180.0; arc2.endAngle = 270 * Math.PI / 180.0; //arc2.linetype = "DASHED" entitys.push(arc2) let arc3 = new vjmap.DbArc(); arc3.center = [1.575, 7.5]; arc3.radius = 6.75; arc3.startAngle = 282 * Math.PI / 180.0; arc3.endAngle = 78 * Math.PI / 180.0; entitys.push(arc3) let block = new vjmap.DbBlock(); block.name = "ball"; block.origin = [0, 0] block.entitys = entitys; doc.appendBlock(block); let blockRef1 = new vjmap.DbBlockReference(); blockRef1.blockname = "ball"; blockRef1.position = [0, 0]; doc.appendEntity(blockRef1); let blockRef2 = new vjmap.DbBlockReference(); blockRef2.blockname = "ball"; blockRef2.position = [28, 15]; blockRef2.rotation = Math.PI; doc.appendEntity(blockRef2); let otherEnts = [ new vjmap.DbLine({ start: [0, 15], end: [28, 15] }), new vjmap.DbLine({ start: [0, 0], end: [28, 0] }), new vjmap.DbLine({ start: [14, 0], end: [14, 15], colorIndex: 1 }), new vjmap.DbCircle({ center:[14, 7.5], radius: 1.83, color: 0xFF0000 }), new vjmap.DbText({ position: [14, 16], contents: "篮球场示意图", colorIndex: 1, horizontalMode: 4, height: 1, }) ] doc.appendEntity(otherEnts); // js代码 let res = await svc.updateMap({ mapid: "basketballCourt", filedoc: JSON.stringify(doc), mapopenway: vjmap.MapOpenWay.Memory, style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式 }) if (res.error) { message.error(res.error) } let mapExtent = vjmap.GeoBounds.fromString(res.bounds); let prj = new vjmap.GeoProjection(mapExtent); var map = new vjmap.Map({ container: 'map', // container ID style: svc.rasterStyle(), center: prj.toLngLat(mapExtent.center()), zoom: 2, renderWorldCopies: false }); map.attach(svc, prj); map.fitMapBounds(); map.addControl(new vjmap.NavigationControl()); map.addControl(new vjmap.MousePositionControl({showZoom: true})); map.enableLayerClickHighlight(svc, e => { e && message.info(`type: ${e.name}, objectid: ${e.objectid}, layer: ${e.layerindex}`); }) })(); ``` ## 在现有图形上修改或新增删除 ```js let doc = new vjmap.DbDocument(); /** 来源于哪个图,会在此图的上面进行修改或新增删除,格式如 形式为 mapid/version,如 exam/v1 . */ doc.from = "basketballCourt/v1"; // 修改或删除实体是通过传递 `objectid` 实体句柄,如果没有 `objectid` 则表示新增 let modifyEnts = [ /*修改*/ new vjmap.DbCircle({ objectid: "71",// 实体句柄,如传了实体句柄,是表示修改或删除此实体. colorIndex: 2 }), /*删除*/ new vjmap.DbText({ objectid: "73",// 实体句柄,如传了实体句柄,是表示修改或删除此实体. delete: true // 表示删除 }), /*新增(没有传 objectid )*/ new vjmap.DbMText({ position: [14, -2], contents: "我是多行文本", colorIndex: 3, attachment: 2, height: 1, }) ] doc.appendEntity(modifyEnts); // js代码 let res = await svc.updateMap({ mapid: "newBasketballCourt", filedoc: JSON.stringify(doc), mapopenway: vjmap.MapOpenWay.Memory, style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式 }) ```

### `objectid` 的获取 进入[唯杰地图云端管理平台](https://vjmap.com/app/cloud/#/),以`内存方式`打开图形 ![image-20240924205039979](https://vjmap.com/blogimages/202409242050012.png) 点击所要查看实体,在右侧属性栏中获取此实体的`objectid` ![image-20240924205124588](https://vjmap.com/blogimages/202409242051641.png) ## 后台通过rest请求创建图形 具体文档可查看 [地图服务REST接口创建或打开图形](/guide/restinterface.html#%E5%88%9B%E5%BB%BA%E6%88%96%E6%89%93%E5%BC%80%E5%9B%BE%E5%BD%A2) ## 通过几何模型创建复杂图形 [几何模型介绍](/guide/geomodel.html) 如通过几何模型创建网络拓扑图示例 [网络拓扑图](https://vjmap.com/demo/#/demo/map/models/08networktop) ![image-20240924204745998](https://vjmap.com/blogimages/202409242047203.png) ## JSON格式创建、修改、删除图形 **用法示例** ```js var data = { "from": "", /** 来源于哪个图,会在此图的上面进行修改或新增删除,格式如 形式为 mapid/version,如 exam/v1 . */ "layers": [ { "name": "test", "color": 1 } ], "textStyles": [ { "name": "mytext", "isShapeFile": false, "textSize": 0.0, "xScale": 1.0, "priorSize": 22, "obliquingAngle": 0.0, "fileName": "VERDANA.TTF", "typeFace": "Verdana", "bold": false, "italic": false, "charset": 0, "pitchAndFamily": 34 } ], "dimStyles": [ { "name": "mydimstyle", "textStyle": "mytext", "dimsah": true, "dimblk1": "_OBLIQUE", "dimblk2": "_DOT" } ], "linetypes": [ { "name": "mylinetype", "comments": "--|----", "style": [ { "method": "numDashes", "parameter": 6 }, { "method": "patternLength", "parameter": 1.8 }, { "method": "dashLengthAt", "parameter": [0, 0.5] }, { "method": "dashLengthAt", "parameter": [1, -0.2] },{ "method": "dashLengthAt", "parameter": [2, -0.2] }, { "method": "dashLengthAt", "parameter": [3, 0.5] }, { "method": "dashLengthAt", "parameter": [4, -0.2] }, { "method": "dashLengthAt", "parameter": [5, -0.2] }, { "method": "shapeStyleAt", "parameter": [1, "Standard"] }, { "method": "shapeOffsetAt", "parameter": [1, [-0.1,-0.05]] }, { "method": "textAt", "parameter": [1, "HW"] }, { "method": "shapeScaleAt", "parameter": [1, 0.5] },{ "method": "shapeStyleAt", "parameter": [4, "Standard"] }, { "method": "shapeOffsetAt", "parameter": [4, [-0.1,-0.05]] }, { "method": "shapeNumberAt", "parameter": [4, 131] }, { "method": "shapeScaleAt", "parameter": [4, 0.2] }, { "method": "shapeRotationAt", "parameter": [4, 3.1415926] } ] } ], "blocks": [ { "name": "myblock", "origin": [10, 10], "entitys": [ { "typename": "DbCircle", "center": [10, 10], "radius": 20, "colorIndex": 1 }, { "typename": "DbSpline", "fitPoints": [[0,0],[20,10],[30, 0]], "colorIndex": 2 } ] } ], "entitys": [ { "typename": "DbLine", "start": [10, 5], "end": [2000,3000], "matrix": [ { "op": "rotate", "angle": 1.5, "origin": [2000, 3000] }, { "op": "scale", "scale": 20, "origin": [2000, 3000] }, { "op": "translation", "vector": [2000, 3000] } ], "layer": "test", "linetype": "mylinetype" }, { "objectid": "340", // 表示修改 "colorIndex": 1, "layer": "test" }, { "objectid": "33F", "delete": true // 表示删除 }, { "typename": "DbBlockReference", "blockname": "myblock", "position": [200,300], "rotation": 3.14, "scaleFactors": [2,3] }, { "typename": "DbBlockReference", "ref": "c43eacd46047/v1", "position": [500, 1000], "rotation": 1.7, "scaleFactors": [1,1] }, { "typename": "DbText", "position": [1000,1000], "contents": "Text", "height": 100, "horizontalMode": 4 }, { "typename": "Db3PointAngularDimension", "dimStyle": "mydimstyle", "arcPoint": [1200,1200], "centerPoint": [1000,1000], "xLine1Point": [500,700], "xLine2Point": [800,1500] }, { "typename": "DbRasterImage", "sourceHttpUrl": "http://xxx/xx.jpg", "position": [200, 500], "pixelWidth": 440, "pixelHeight": 220, "width": 300, "height": 150 } ] } let doc = new vjmap.DbDocument(); let fileDoc = doc.toDoc(data); // js代码 let res = await svc.updateMap({ mapid: "your_map_id", filedoc: fileDoc, mapopenway: vjmap.MapOpenWay.Memory, style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式 }) ``` --- # 创建图形示例 ## 前端根据数据生成CAD工程剖面图 ### 效果 ![datatodwg.gif](https://vjmap.com/blogimages/datatodwg.gif) ### 它支持哪些功能? - 支持CAD的27种实体类型的创建,如线、文字、填充等 - 支持对DWG图中实体进行修改、克隆、删除等操作 - 支持创建CAD图层、线型、块定义、文字样式 - 支持从外部图形中拷贝实体到当前创建的CAD图中 - 支持块属性文字的创建和设置 - 对创建好的CAD图形数据能以GeoJson的格式在前端直接展示,同时能选中移动等操作 - 对创建好的CAD图形能在前端展示,同时能点击弹出实体类型等属性 - 能导出成DWG图形 ### 实现原理 ![image-20230303202058333](https://vjmap.com/blogimages/image-20230303202058333.png) (1) 对剖面图中不变的元素如图例做成模板。创建图时直接拷贝这些实体即可。对于图签可以外部图形插入,同时图签中需要修改的文字内容如制图人或日期等字段,可以块属性文字的方式在创建时以属性赋值的方式来进行创建。如上面生成的剖面图的模板来源于下面这两个模板图形。 剖面图模板:![image-20230303202523238](https://vjmap.com/blogimages/image-20230303202523238.png) [在线查看剖面图模板](https://vjmap.com/app/cloud/#/map/template_sect?version=v1&mapopenway=Memory&vector=false) 图签模板:(其中单位和日期是块属性文字,支持插入的时候输入属性值进行修改) ![image-20230303202615419](https://vjmap.com/blogimages/image-20230303202615419.png) [在线查看图签模板](https://vjmap.com/app/cloud/#/map/template_tk?version=v1&mapopenway=Memory&vector=false) (2) 获取要创建的绘图数据,示例中对数据进行了模拟生成。 (3) 根据唯杰地图[https://vjmap.com/](https://vjmap.com/) SDK中提供创建CAD实体类型的方法创建相关实体。 唯杰地图SDK支持的实体类型有DbLine直线、DbCurve曲线、Db2dPolyline二维折线、Db3dPolyline三维多段线、DbPolyline多段线、BlockReference块参照、DbArc圆弧、DbCircle圆、DbEllipse椭圆、DbHatch填充、Text单行文本、DbMText多行文本、RasterImage栅格图片、DbShape型实体、Spline样条曲线、Wipeout遮罩实体、Dimension标注、Db2LineAngularDimension角度标注[两条线]、Db3PointAngularDimension角度标注[三点]、DbAlignedDimension对齐标注、DbArcDimension圆弧标注、DbDiametricDimension直径标注、DbOrdinateDimension坐标标注、DbRadialDimension半径标注、DbRadialDimensionLarge半径折线标注、DbRotatedDimension转角标注、AcDbAttributeDefinition属性注记、AcDbAttribute块属性、DbLayer图层、DbTextStyle文字样式、DbDimStyle标注样式、DbLinetypeStyle线型样式、DbBlock块定义、DbDocument数据库文档。 如何减少代码量可以用如下方法: - 技巧一:可以直接拷贝模板中的实体,对实体的属性进行修改。这样能少赋值参数,减少代码量。 - 技巧二:对于重复的对象,可以创建块,变化的文字,以块属性文字定义。再重复创建块参照,修改属性文字。 (4) 把创建的数据生成一个JSON对象,调用唯杰地图服务,后台创建DWG图形。 (5) 把后台创建的DWG图形数据以GeoJson数据或GIS瓦片的格式返回给前端进行展示。对于图不大的情况,可用GeoJson数据进行展示。如果图大时,GeoJson数据量大,数据返回慢,渲染也会受影响,这时建议用GIS栅格瓦片或矢量瓦片的时候进行绘制。 ### 在线体验地址 [https://vjmap.com/demo/#/demo/map/comprehensive/03datatodwgmap](https://vjmap.com/demo/#/demo/map/comprehensive/03datatodwgmap) ## 基于模板数据自动成果 [根据一个cad图模板,用数据自动生成导出dwg图形](https://vjmap.com/demo/#/demo/map/service/05ztpldatanewmap) CAD图模板如下 [在线查看CAD图模板](https://vjmap.com/app/cloud/#/map/sys_drawtemplate?version=v1&mapopenway=Memory&vector=false) ![image-20240924210431612](https://vjmap.com/blogimages/202409242104659.png)
// --基于模板数据自动成图--根据一个cad图模板,用数据自动生成导出dwg图形
// 图模板来源地图id和版本
let templateId = "sys_drawtemplate";
let templateSecVersion = "v1";
let svc = new vjmap.Service(env.serviceUrl, env.accessToken);
// 注:以下所的有objectid来源方法为:
// 在唯杰云端管理平台里面以内存方式打开模板图,然后点击相应实体,在属性面板中获取object值
// 或者以几何渲染方式打开模板图,点击相应实体,在属性面板中获取object值,如果是块实体(objectid中有多个_),取第一个_前面的字符串
let symbols = {
    "symbol1": "8F9",//符号一objectid
    "symbol2": "92A" ,//符号二objectid
}
let lineObjectId = "905"; //线objectid
let arrowObjectId = "909"; //箭头objectid
let textObjectId = "8FA"; //文字objectid
// 模拟数据
const mockData = (num) => {
    num = num || 5;
    let lines = []
    for(let i = 0; i <  num; i++) {
        let items = {
            points: [],
            textColor: vjmap.randomColor(),
            lineColor: vjmap.randomColor(),
            arrowColor: vjmap.randomColor()
        }
        for(let k = 0; k <  3; k++) {
            items.points.push({
                symbol: (k % 2 == 0) ? "symbol1" : "symbol2", // 模拟用,偶数点用符号二,奇数点用符号二
                text: "CY" + vjmap.randInt(0, 1000), // 随机生成一个文本值
                position: [vjmap.randInt(-50, 50), vjmap.randInt(-50, 50)],
                symbolColor: vjmap.randomColor()
            })
        }
        lines.push(items);
    }
    return lines
}
// 创建图
const createDoc = async (Data) => {
    // 获取要绘制的数据
    let drawData = Data;
    let doc = new vjmap.DbDocument();
    // 数据来源
    doc.from = `${templateId}/${templateSecVersion}`;
    // 把来源图的数据最后都清空,(这里的模板不需要清空,直接用了)
    doc.isClearFromDb = true;
    let entitys = [];
    // 所有线
    for(let ln = 0; ln <  drawData.length; ln++) {
        let lineItem = drawData[ln];
        let textColor = lineItem.textColor; // 文本颜色
        let lineColor = lineItem.lineColor; // 线颜色
        let arrowColor = lineItem.arrowColor; // 箭头颜色
        // 线上所有点
        for(let pt = 0; pt <  lineItem.points.length; pt++) {
            let point = lineItem.points[pt];
            entitys.push(new vjmap.DbBlockReference({
                cloneObjectId: symbols[point.symbol],
                position: point.position,
                color: vjmap.htmlColorToEntColor(point.symbolColor)
            }))
            entitys.push(new vjmap.DbText({
                cloneObjectId: textObjectId,
                position: [point.position[0], point.position[1]-0.5],
                horizontalMode: vjmap.DbTextHorzMode.kTextCenter, // kTextCenter
                verticalMode: vjmap.DbTextVertMode.kTextTop, // kTextVertMid,
                color: vjmap.htmlColorToEntColor(textColor)
            }))
            if (pt != 0) {
                // 创建两点之间的线
                let startPt = lineItem.points[pt - 1].position;
                let endPt = lineItem.points[pt].position;
                entitys.push(new vjmap.Db2dPolyline({
                    cloneObjectId: lineObjectId,
                    points: [startPt, endPt],
                    color: vjmap.htmlColorToEntColor(lineColor)
                }))
                // 在中点创建一个箭头
                let midPt = [(startPt[0] + endPt[0]) / 2.0, (startPt[1] + endPt[1]) / 2.0];
                entitys.push(new vjmap.DbBlockReference({
                    cloneObjectId: arrowObjectId,
                    position: midPt,
                    // 旋转
                    matrix: [
                        {
                            op: "rotate", // 旋转
                            angle: vjmap.geoPoint(startPt).angleTo(vjmap.geoPoint(endPt)), // 弧度
                            origin: midPt // 旋转基点
                        }
                    ],
                    color: vjmap.htmlColorToEntColor(arrowColor)
                }))
                let arrowTextPoint = [midPt[0], midPt[1] + 1];
                let arrowTextAngle = vjmap.geoPoint(startPt).angleTo(vjmap.geoPoint(endPt));
                if (arrowTextAngle > Math.PI / 2 && arrowTextAngle <  Math.PI * 3 / 2 ) {
                    // 使文字不倒立
                    arrowTextAngle += Math.PI
                }
                // 箭头上方的文本
                entitys.push(new vjmap.DbText({
                    cloneObjectId: textObjectId,
                    position: arrowTextPoint,
                    horizontalMode: vjmap.DbTextHorzMode.kTextCenter, // kTextCenter
                    verticalMode: vjmap.DbTextVertMode.kTextVertMid, // kTextVertMid,
                    color: vjmap.htmlColorToEntColor(arrowColor),
                    // 旋转
                    matrix: [
                        {
                            op: "rotate", // 旋转
                            angle: arrowTextAngle, // 弧度
                            origin: arrowTextPoint // 旋转基点
                        }
                    ],
                }))
            }
        }
    }
    doc.entitys = entitys;
    return doc;
}
// 先得设置一个要图形的所有范围,这个范围是随便都没有有关系的。最后导出dwg时,会根据实体的所有坐标去自动计算真实的范围。
let mapBounds = '[-10000,-10000,10000,10000]'
let mapExtent = vjmap.GeoBounds.fromString(mapBounds);
mapExtent = mapExtent.square(); // 要转成正方形
svc.setCurrentMapParam({
    darkMode: true, // 由于没有打开过图,所以主动设置黑色模式
    bounds: mapExtent.toString()
})
// 建立坐标系
let prj = new vjmap.GeoProjection(mapExtent);
// 新建地图对象
let map = new vjmap.Map({
    container: 'map', // container ID
    style: {
        version: svc.styleVersion(),
        glyphs: svc.glyphsUrl(),
        sources: {},
        layers: []
    },// 矢量瓦片样式
    center: [0,0], // 中心点
    zoom: 2,
    renderWorldCopies: false
});
// 地图关联服务对象和坐标系
map.attach(svc, prj);
// 使地图全部可见
map.fitMapBounds();
await map.onLoad();
// 创建一个几何对象
const createGeomData = async (map, doc) => {
    let svc = map.getService();
    let res = await svc.cmdCreateEntitiesGeomData({
        filedoc: JSON.stringify(doc)
    });
    if (res.error) {
        message.error(res.error);
        return {
            type: "FeatureCollection",
            features: []
        };
    }
    if (res.metadata && res.metadata.mapBounds) {
        // 如果返回的元数据里面有当前地图的范围,则更新当前地图的坐标范围
        map.updateMapExtent(res.metadata.mapBounds);
    }
    const features = [];
    if (res && res.result && res.result.length > 0) {
        for (let ent of res.result) {
            if (ent.geom && ent.geom.geometries) {
                let clr = map.entColorToHtmlColor(ent.color); // 实体颜色转html颜色
                let featureAttr = {};
                // 因为要组合成一个组合实体,所以线和多边形的颜色得区分
                if (ent.isPolygon) {
                    featureAttr.color = clr; // 填充色,只对多边形有效
                    featureAttr.noneOutline = true; // 不显示多边形边框,只对多边形有效
                } else {
                    featureAttr.color = clr; // 颜色
                    featureAttr.line_width = ent.lineWidth; // 线宽
                }
                for(let g = 0; g <  ent.geom.geometries.length; g++) {
                    if (ent.geom.geometries[g].type == "Point") {
                        // 改成起始和终点一样的线
                        ent.geom.geometries[g].type = "LineString";
                        ent.geom.geometries[g].coordinates = [
                            ent.geom.geometries[g].coordinates, // 始点
                            ent.geom.geometries[g].coordinates // 终点
                        ]
                    }
                }
                let ft = {
                    id: vjmap.RandomID(10),
                    type: "Feature",
                    properties: {
                        objectid: ent.objectid,
                        opacity: ent.alpha / 255,
                        ...featureAttr,
                    }
                }
                if (ent.geom.geometries.length == 1) {
                    features.push({
                        ...ft,
                        geometry: ent.geom.geometries[0],
                    });
                } else {
                    features.push({
                        ...ft,
                        geometry: {
                            geometries: ent.geom.geometries,
                            type: "GeometryCollection"
                        },
                    });
                }
            }
        }
    }
    return {
        type: "FeatureCollection",
        features: features,
    };
};
// 清空之前的地图数据
const clearMapData = () => {
    svc.setCurrentMapParam({
        darkMode: true, // 由于没有打开过图,所以主动设置黑色模式
        bounds: mapExtent.toString()
    })
    map.disableLayerClickHighlight();
    map.removeDrawLayer();
    let sources = map.getStyle().sources;
    for(let source in sources) {
        map.removeSourceEx(source);
    }
}
// 创建一个有数据的地图
const createDataMap = async (doc) => {
    clearMapData();
    let geojson = await createGeomData(map, doc);
    map.getDrawLayer().set(geojson);
}
// 创建一个dwg的地图
const createDwgMap = async (doc) => {
    // 先清空之前绘制的
    clearMapData();
    // js代码
    let res = await svc.updateMap({
        // 获取一个临时的图id(临时图形只会用临时查看,过期会自动删除)
        mapid: vjmap.getTempMapId(1), // 临时图形不浏览情况下过期自动删除时间,单位分钟。默认30
        filedoc: JSON.stringify(doc),
        mapopenway: vjmap.MapOpenWay.Memory,
        style: {
            backcolor: 0 // 如果div背景色是浅色,则设置为oxFFFFFF
        }
    })
    if (res.error) {
        message.error(res.error)
    }
    await map.switchMap(res);
}
let curDoc;
const exportDwgOpen = async () => {
    if (!curDoc) return;
    const mapid = 'exportdwgmap';
    let res = await svc.updateMap({
        mapid: mapid,
        filedoc: JSON.stringify(curDoc),
        mapopenway: vjmap.MapOpenWay.Memory,
        style: {
            backcolor: 0 // 如果div背景色是浅色,则设置为oxFFFFFF
        }
    })
    if (res.error) {
        message.error(res.error)
    } else{
        window.open(`https://vjmap.com/app/cloud/#/map/${res.mapid}?version=${res.version}&mapopenway=Memory&vector=false`)
    }
}
const creatDataMap = async () => {
    let Data = mockData(5);
    const doc = await createDoc(Data);
    await createDataMap(doc);
    map.fitMapBounds();
    curDoc = doc;
}
const creatDwgMap = async () => {
    let Data = mockData(15);
    const doc = await createDoc(Data);
    await createDwgMap(doc);
    map.fitMapBounds();
    // 点击有高亮状态(鼠标点击地图元素上时,会高亮)
    map.enableLayerClickHighlight(svc, e => {
        if (!e) return;
        let msg = {
            content: `type: ${e.name}, id: ${e.objectid}, layer: ${e.layerindex}`,
            key: "layerclick",
            duration: 5
        }
        e && message.info(msg);
    })
    curDoc = doc;
}
// 随机生成一个图
creatDataMap();
const mousePositionControl = new vjmap.MousePositionControl();
map.addControl(mousePositionControl, "bottom-left");


--- # 编辑图形 ::: tip 提示 唯杰地图支持对CAD图的编辑,支持对CAD原图进行新增实体、修改实体、删除实体、复制实体等,不过唯杰地图主要是对CAD图GIS展示和效果为主,编辑功能相对较弱。 ::: ## 原理 完整的能在cad图上进行删除、修改,新增,导出CAD图的示例源码。可参考 [https://vjmap.com/app/visual/#/draw?mapid=p5ff2aa38c0f&version=v1&mapopenway=GeomRender&vector=true&theme=dark](https://vjmap.com/app/visual/#/draw?mapid=p5ff2aa38c0f&version=v1&mapopenway=GeomRender&vector=true&theme=dark) [https://vjmap.com/demo/#/demo/map/comprehensive/05drawcadedit](https://vjmap.com/demo/#/demo/map/comprehensive/05drawcadedit) 实现对CAD图的编辑,原理如下: - 对cad原图进行修改。可对CAD图中的实体进行新增、修改、删除等操作。修改保存后,后台会新成一个新的版本号。示例 [https://vjmap.com/demo/#/demo/map/service/05zfrommap](https://vjmap.com/demo/#/demo/map/service/05zfrommap) - 利用修改样式对cad图的显示进行修改,不会修改原图数据,只会改前端显示结果。性能高些。示例代码 [https://vjmap.com/demo/#/demo/map/service/05zgmodifymap](https://vjmap.com/demo/#/demo/map/service/05zgmodifymap) `利用修改样式对cad图的显示进行修改`主要原理是: (1)后台通过样式去控制绘制。可利用我们的表达式语法,对指定的图层,实体等进行显示或隐藏,和颜色的修改等。这样会在后台创建一个新的样式名称。服务后台打开图的时候指定此样式名称即可,这样生成的瓦片数据就是您个性化后的数据。 (2)前端可以加一个绘制图层,可以绘制点,线,面等名种形状。 通过后台样式控制和前端的绘制来达到您想要的修改后的效果。 ## 操作视频教程 ![](https://vjmap.com/blogimages/editcadmap.webp) [在线体验地址](https://vjmap.com/app/visual/#/draw?mapid=p5ff2aa38c0f&version=v1&mapopenway=GeomRender&vector=true&theme=dark) :::tip 提示 只是由于数据安全的问题,试用版本不能下载生成的dwg原图。只能在线打开查看生成的图。正式版本可以后台设置下载 ::: --- # 图形处理 处理图形一般通过两种方式来实现 - 方法一: 组合成新地图 composeNewMap ```js /** * 组合成新地图 * @param param 组合参数 * @return {Promise} */ composeNewMap(param: IComposeNewMap | IComposeNewMap[]): Promise; /** * 组合新地图参数 */ export interface IComposeNewMap { /** 地图ID. */ mapid: string; /** 地图版本(为空时采用当前打开的地图版本). */ version?: string; /** 地图裁剪范围,范围如[x1,y1,x2,y2], 为空的话,表示不裁剪 */ clipbounds?: [number, number, number, number]; /** 选择是包含还是相交(默认false表示包含,true相交) */ selByCrossing?: boolean; /** 四参数(x偏移,y偏移,缩放,旋转弧度),可选,对坐标最后进行修正*/ fourParameter?: [number, number, number, number]; /** 是否用上面的四参数进行反算,默认false*/ isInverseFourParamter?: boolean; /** 要显示的图层名称,为空的时候,表示全部图层 */ layers?: string[]; /** 生新成图的图层名称前缀,如果设置为null将不会有默认的图层前缀。如果没有前缀,不同的图有图层重名时,如果设置了随层属性有可能会导致绘制的不一样 */ layerPrefix?: string; /** 生新成图的图层名称后缀 */ layerSuffix?: string; /** 保存的文件名称,为空的时候,自根据参数自动生成 */ savefilename?: string; /** 生成后清理图形数据以减少dwg文件大小 */ purge?: boolean; /** 来源于其他图的实体时,如果块定义重复,则自动重命名 ,默认true */ isRenameBlockNameIfExist?: boolean; /** 是不是创建完缩放至全图 默认false (如果有多个图合并,只需设置第一个图的) */ isZoomExtents?: boolean; /** 导出的CAD版本号。如果为*表示为当前CAD图的版本 */ cadVersion?: string | "*" | "cad2000" | "cad2004" | "cad2007" | "cad2010" | "cad2013" | "cad2018"; } ``` 如提取一个CAD图几个图层的数据保存为新图 ```js let svc = new vjmap.Service(env.serviceUrl, env.accessToken) // js代码 let res = await svc.composeNewMap({ mapid: "sys_zp", vesion: "v1", layers: ["红线", "道路"] }) ``` - 方法二: 基于模板图创建新图 ```js export class DbDocument { /** 来源于哪个图,会在此图的上面进行修改或新增删除,格式如 形式为 mapid/version,如 exam/v1 . */ from?: string; /** 来源于哪个图时有效,表示从此图中选择指定的图层,不在指定的图层将不会显示 */ pickLayers?: string[]; /** 来源于哪个图时有效,表示从此图中选择指定的实体ID,不在指定的实体ID将不会显示 */ pickEntitys?: string[]; /** 来源于哪个图时有效,使用表达式,表示从此图中选择指定的实体ID,不在指定的实体ID将不会显示 结果与pickEntitys的取并 */ pickExpr?: string; isClearFromDb?: boolean; /** 文档环境,用于设置是否显示线宽等设置, 设置线宽为 LWDISPLAY ,true显示或 false不显示线宽*/ environment?: Record; /** 实体集. */ entitys?: IDbEntity[]; /** 图层集. */ layers?: IDbLayer[]; /** 文字样式. */ textStyles?: IDbTextStyle[]; /** 标注样式. */ dimStyles?: IDbDimStyle[]; /** 线型. */ linetypes?: IDbLinetype[]; /** 块定义. */ blocks?: IDbBlock[]; /** 来源于其他图的实体时,如果块定义重复,则自动重命名 ,默认true */ isRenameBlockNameIfExist?: boolean; /** 是不是创建完缩放至全图 默认false */ isZoomExtents?: boolean; //... 其他方法省略 } ``` 如提取一个CAD图几个图层的数据保存为新图 ```js let svc = new vjmap.Service(env.serviceUrl, env.accessToken) let doc = new vjmap.DbDocument(); doc.from = "sys_zp/v1"; doc.pickLayers = ["红线", "道路"] // js代码 let res = await svc.updateMap({ mapid: "newsyszplayers", filedoc: JSON.stringify(doc), mapopenway: vjmap.MapOpenWay.Memory, style: { backcolor: 0 // 如果div背景色是浅色,则设置为oxFFFFFF } }) ``` ## 裁剪图形 ```js let svc = new vjmap.Service(env.serviceUrl, env.accessToken) // js代码 let res = await svc.composeNewMap({ mapid: "sys_zp", vesion: "v1", /** 地图裁剪范围,范围如[x1,y1,x2,y2], 为空的话,表示不裁剪 */ clipbounds?: [x1,y1,x2,y2]; }) ``` ![](https://vjmap.com/blogimages/mapclip.webp) ## 坐标转换 ```js let svc = new vjmap.Service(env.serviceUrl, env.accessToken) // js代码 let res = await svc.composeNewMap({ mapid: "sys_zp", vesion: "v1", /** 四参数(x偏移,y偏移,缩放,旋转弧度),可选,对坐标最后进行修正*/ fourParameter?: [number, number, number, number]; }) ``` ![](https://vjmap.com/blogimages/mapfourparam.webp) ## 合并图形 ```js // --图形处理合成新的图形--通过将图形列表处理后,再合成一个新的图形 let svc = new vjmap.Service(env.serviceUrl, env.accessToken) // 组合成新的图,将sys_world图进行一定的处理后,再与sys_hello进行合成,生成新的地图文件名 let rsp = await svc.composeNewMap([ { mapid: "sys_world", // 地图id // 下面的参数可以根据实际需要来设置,可以对图层,范围,坐标转换来进行处理 //layers: ["经纬度标注","COUNTRY"], // 要显示的图层名称列表 //clipbounds: [10201.981489534268, 9040.030491346213, 26501.267379, 4445.465999], // 要显示的范围 //fourParameter: [0,0,2,0] // 对地图进行四参数转换计算 }, { mapid: "sys_hello" } ]) if (!rsp.status) { message.error(rsp.error) } // 返回结果为 /* { fileid: "pefbe7459b06", mapdependencies: "sys_world||sys_hello", mapfrom: "sys_world&&v1&&&&0&&&&&&&&&&&&10||sys_hello&&v1&&&&0&&&&&&&&&&&&2", status: true } */ ```
// --图形处理合成新的图形--通过将图形列表处理后,再合成一个新的图形
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 组合成新的图,将sys_world图进行一定的处理后,再与sys_hello进行合成,生成新的地图文件名
let rsp = await svc.composeNewMap([
    {
        mapid: "sys_world", // 地图id
        // 下面的参数可以根据实际需要来设置,可以对图层,范围,坐标转换来进行处理
        //layers: ["经纬度标注","COUNTRY"], // 要显示的图层名称列表
        //clipbounds: [10201.981489534268, 9040.030491346213, 26501.267379,  4445.465999], // 要显示的范围
        //fourParameter: [0,0,2,0] // 对地图进行四参数转换计算
    },
    {
        mapid: "sys_hello"
    }
])
if (!rsp.status) {
    message.error(rsp.error)
}
// 返回结果为
/*
{
    fileid: "pefbe7459b06",
    mapdependencies: "sys_world||sys_hello",
    mapfrom: "sys_world&&v1&&&&0&&&&&&&&&&&&10||sys_hello&&v1&&&&0&&&&&&&&&&&&2",
    status: true
}
*/
let res = await svc.openMap({
    mapid: "composeHelloWord", // 取个新的地图名称
    fileid: rsp.fileid,
    //mapdependencies: rsp.mapdependencies, // 把返回值做为参数传递打开, 如果需要创建协同图形,请把此项打开
    //mapfrom: rsp.mapfrom, // 把返回值做为参数传递打开, 如果需要创建协同图形,请把此项打开
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style:  vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    message.error(res.error)
}
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
let prj = new vjmap.GeoProjection(mapExtent);
let center = mapExtent.center();
let map = new vjmap.Map({
    container: 'map', // container ID
    style: svc.rasterStyle(),
    center: prj.toLngLat(center),
    zoom: 2,
    renderWorldCopies: false
});
map.attach(svc, prj);
map.fitMapBounds();
await map.onLoad();


[图形排列组合成新图形示例](https://vjmap.com/demo/#/demo/map/service/08zArrangeCompose) ## 提取图层 使用`composeNewMap`的`layers`也可以提取图层,这里使用`DbDocument`做为示例
// --新建地图[直接生成DWG图打开]--在后台新建CAD图,然后在前端打开
// js代码
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
let doc = new vjmap.DbDocument();
doc.from = "sys_zp/v1";
doc.pickLayers = ["红线", "道路"]
// js代码
let res = await svc.updateMap({
    mapid: vjmap.getTempMapId(),// 临时图形不浏览情况下过期自动删除时间,单位分钟。默认30
    filedoc: JSON.stringify(doc),
    mapopenway: vjmap.MapOpenWay.Memory,
    style: {
        backcolor: 0 // 如果div背景色是浅色,则设置为oxFFFFFF
    }
})
if (res.error) {
    message.error(res.error)
}
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
let prj = new vjmap.GeoProjection(mapExtent);
var map = new vjmap.Map({
    container: 'map', // container ID
    style: svc.rasterStyle(),
    center: prj.toLngLat(mapExtent.center()),
    zoom: 2,
    renderWorldCopies: false
});
map.attach(svc, prj);
map.fitMapBounds();
map.addControl(new vjmap.NavigationControl());
map.addControl(new vjmap.MousePositionControl({showZoom: true}));


--- # 图形管理 ## 地图创建 ```js let svc = new vjmap.Service(env.serviceUrl, env.accessToken) // 创建地图 let res = await svc.openMap({ mapid: "要取名的mapid", // 地图ID,(创建的时候确保此ID不存在) fileid: "上传获取到的文件fileid", // 创建地图必须传fileid mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开 style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式 }) ``` ## 地图打开 ```js // 打开地图 let res = await svc.openMap({ mapid: "要打开的mapid", // 地图ID,(创建的时候确保此ID已存在),打开地图不需要fileid了 mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开 style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式 }) ``` ## 地图更新 地图更新会在原来的`mapid`上面多加一个`版本号`,如原来的版本号为 `v1`, 更新后,版本号将变为 `v2` ```js let res = await svc.updateMap({ mapid: "要更新的mapid", fileid: "上传获取到的文件fileid", // 创建地图必须传fileid mapopenway: vjmap.MapOpenWay.GeomRender, style: { backcolor: 0 // 如果div背景色是浅色,则设置为oxFFFFFF } }) ``` [地图管理相关示例](https://vjmap.com/demo/#/demo/map/service/07mapManager) ## 地图版本管理 打开指定版本的地图 ```js // 打开地图 let res = await svc.openMap({ mapid: env.exampleMapId, // 地图ID,(请确保此ID已存在,可上传新图形新建ID) version: "v1", // 指定版本号,不删除的话默认为最新版本 mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开 style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式 }) ``` 删除指定版本的地图 ```js await svc.cmdDeleteMap(mapid, "v1"); // 指定版本进行删除 ``` ## 删除地图 ```js await svc.cmdDeleteMap(mapid); // 如果只选择了图,没有选择版本,则删除所有的 ``` ## 工作区管理 工作区相当于数据隔离,不同的工作区数据保存位置不同。权限也不同。 ```js // 切换到此工作区下面,以后服务svc调用的方法都是在此工作区下面进行了 svc.switchWorkspace('工作区名称'); // 如果名称为空,则为默认工作区 ``` 具体用法可参考 [地图工作区管理](https://vjmap.com/demo/#/demo/map/service/07mapWorkspace) ## 地图密码保护 ### 设置密码保护 ```js let res = await svc.openMap({ mapid: "要取名的mapid", // 地图ID,(创建的时候确保此ID不存在) fileid: "上传获取到的文件fileid", // 创建地图必须传fileid // 如果要密码访问的话,设置秘钥值 secretKey: svc.pwdToSecretKey('password') , mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开 style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式 }) ``` 具体示例可参考 [上传新图形](https://vjmap.com/demo/#/demo/map/service/06uploadmapelementui) ### 打开有密码保护的图 ```js // --打开有密码保护的图--需要输入密码或AccessKey才能访问有密码保护的图 // 新建地图服务对象,传入服务地址和token let svc = new vjmap.Service(env.serviceUrl, env.accessToken); // 如果有此图的密码,可以在打开之前进行设置,这样就不会弹出密码输入框了 // secretKey权限上图的最多权限,不要轻易把secretKey暴露 // let secretKey = 'xxxxxxx'; // xxxxxxx的值可以通过在控制台输出 svc.pwdToSecretKey("your password"); 的值复制过来。这样就不会在代码中暴露密码明文了 //svc.addSecretKey(secretKey); // 或者如果有此图的accessKey,可以在打开之前进行设置,这样就不会弹出密码输入框了, // accessKey可以访问图形,但权限比较小,不能删除图形等。可以把accessKey发给需要访问此图的人 //svc.addAccessKey("akxxxxxxxxxxxxxxxx") // 打开地图 let res = await svc.openMap({ mapid: "sys_zp", // 请改成有密码保护的图id, mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开 style: vjmap.openMapDarkStyle(), // div为深色背景颜色时,这里也传深色背景样式 // 如果密码输入回调不传的话,则用默认的prompt弹框为输入密码 cbInputPassword: async (param) => { if (param.tryPasswordCount > 3) return '';// 如果密码重试次数超过三次 // 如果是有密码保护的图,在这里自定义密码输入框,可能用自己的UI库。这里用了vjgui只是做了一个示例说明 return new Promise((resolve) => { vjgui.init(); vjgui.prompt(`请输入密码或AccessKey`, e=>{ resolve(e) }, { value: "", title: `图 ${param.mapid}`, width: "200px", height: "150px" }) }) } }) ``` 具体示例可参考 [打开有密码保护的图](https://vjmap.com/demo/#/demo/map/service/08openMapNeedPassword) ### 取消密码 可在`唯杰地图云端管理平台`相应图的`操作`菜单中`设置密码`为空进行取消 ![image-20240925165226985](https://vjmap.com/blogimages/202409251652183.png) ## 地图隐藏 `mapid`以 `ns_`开头的图将在云端管理平台中不可见。只有`root`权限的token才可见。 ## 临时地图 `mapid`以 `ns_temp_30`开头的图将为临时图,其中`30`为30分钟后自动删除, 可自定义其他时间。 ```js /** * 获取一个临时的图id(临时图形只会用临时查看,过期会自动删除) * @param expireTime 临时图形不浏览情况下过期自动删除时间,单位分钟。默认30 * @param isVisible 是否可见,(可见的话,将可通过ListMaps获取,否则为隐藏图)。默认不可见 * @return */ export function getTempMapId(expireTime?: number, isVisible?: boolean): string; ``` --- # 地图个性化 ## 后端个性化地图 通过自定义样式表达式使后端渲染不同的数据样式,优点:能适用于栅格瓦片或矢量瓦片,但效果会稍有延迟。 ```ts /** * 获取样式图层名 * @param style 样式参数 * @param mapid 地图ID * @param version 版本号,为空,则获取最新的; * @param isGeomLayer 几何渲染图层优先(默认true) * @return {Promise} */ createStyle(style: { /** 样式名称. */ name?: string; /** 要开的图层索引列表,格式如[0,1,3]. */ layeron?: string | number[]; /** 要关的图层索引列表,格式如[2,4]. layeron与layeroff只要输入一个即可*/ layeroff?: string | number[]; /** 地图裁剪范围,范围如[x1,y1,x2,y2].如果只需入了数值的话,表示是缩放倍数 */ clipbounds?: [number, number, number, number] | number; /** 颜色. */ backcolor?: number; /** 线宽,格式如[1,1,1,1,0].表式第1,2,3,4级线宽开,第5级线宽关,大于第5级的,以最后设置的级别状态为主,所以也是关。如为空,则和原图线宽显示状态相同 */ lineweight?: string | number[]; /** 表达式. */ expression?: string; }, mapid?: string, version?: string, isGeomLayer?: boolean): Promise; ``` `expression`的语法可参考文档 [服务端渲染表达式语法](/guide/expr.html) 和 [服务端条件查询和表达式查询](/guide/svrStyleVar.html) ### 创建一个`高科技蓝`的表达式写法为 ```js var color := gFilterCustomTheme(gInColorRed, gInColorGreen, gInColorBlue, 200, 200, 0.1); gOutColorRed[0] := gRed(color); gOutColorGreen[0] := gGreen(color); gOutColorBlue[0] := gBlue(color); ```
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 再打开图
let res = await svc.openMap({
    mapid: "sys_world",
    mapopenway: vjmap.MapOpenWay.GeomRender,
    style :{
        name: "style2",
        clipbounds: "",
        backcolor: 0,
        expression: "var color := gFilterCustomTheme(gInColorRed, gInColorGreen, gInColorBlue, 200, 200, 0.1);gOutColorRed[0] := gRed(color);gOutColorGreen[0] := gGreen(color);gOutColorBlue[0] := gBlue(color);"
    }
})
if (res.error) {
    console.error(res.error)
}
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
let prj = new vjmap.GeoProjection(mapExtent);
let map = new vjmap.Map({
    container: 'map', // container ID
    style: svc.vectorStyle(),
    center: prj.toLngLat(mapExtent.center()),
    zoom: 2,
    renderWorldCopies: false
});
map.attach(svc, prj);
map.fitMapBounds();
更多[个性化地图样式示例](https://vjmap.com/demo/#/demo/map/service/24setMapStyle) ### 图签图层文本改成红色 表达式写法: ```js if((gInFeatureType=='AcDbText' or gInFeatureType=='AcDbMText' or gInFeatureType=='AcDbAttributeDefinition' or gInFeatureType=='AcDbAttribute') and (gInLayerName=='WZ')) { gOutColorRed:=255; gOutColorGreen:=0; gOutColorBlue:=0; } ``` ### 隐藏指北针(3级后才可见) 表达式写法: ```js if(gInObjectId in '10D_10F__111_#||10D_10F__110_#') { gOutVisible[2]:=0; gOutVisible[4]:=1; } ``` ### 黄色的线变色透明加粗 表达式写法: ```js if((gInColorRed==255 and gInColorGreen==255 and gInColorBlue==0)) { gOutLineWidth:=5; gOutColorRed:=0; gOutColorGreen:=255; gOutColorBlue:=255; gOutColorAlpha:=100; } ``` ### 指定半径的圆改成红色 表达式写法: ```js if(((abs(gInExtendMaxX-gInExtendMinX-4000)<20))) { gOutColorRed:=255; gOutColorGreen:=0; gOutColorBlue:=0; } ``` ### 不同级别显示不同的图层 通过设置样式中的 `layeron` 实现 ```js let style = { // 不同级别下 layeroff 控制图层索引是否关闭图层, layeron 来控制图层索引是否打开图层 layeron:`{ "*": "()", "1": "(19,24)", "2": "(19,24,5)", "3-4": "(1,19,24,5,22)", "5": "(1,19,24,5,22,15)", "6-7": "(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,19,22,24)" }` } ``` [在线查看示例效果](https://vjmap.com/demo/#/demo/map/service/12mapStyleBackend) ![image-20240925205929159](https://vjmap.com/blogimages/202409252059230.png) ## 前端个性化地图(矢量瓦片) 通过修改矢量瓦片的样式在前端修改渲染数据,优点:速度快,但只能用于矢量瓦片 [前端样式表达式语法文档查看](/guide/expression.html) ### 图签图层文本改成红色 ```js const customColorCaseExpr = [ [ "all", [ "any", [ "==", [ "get", "type" ], 13 ], [ "==", [ "get", "type" ], 12 ], [ "==", [ "get", "type" ], 26 ], [ "==", [ "get", "type" ], 27 ] ], [ "match", [ "get", "layer" ], [ 24 ], true, false ] ], "#ff0000" ] let newStyle = svc.vectorStyle({ customColorCaseExpr }); map.setStyle(newStyle); ``` ### 黄色的线变色透明加粗 ```js const customColorCaseExpr = [ [ "any", [ "==", [ "get", "color" ], "#ffff00" ] ], "#00ffff" ] let newStyle = svc.vectorStyle({ customColorCaseExpr }); map.setStyle(newStyle); ``` [在线查看示例效果](https://vjmap.com/demo/#/demo/map/service/12mapStyleFront) ![image-20240925212508501](https://vjmap.com/blogimages/202409252125560.png) ## 前端个性化地图(栅格瓦片) 对于栅格瓦片图层,可在前端设置图层的地图样式、是否反色、饱和度、对比度 [设置图层自定义样式在线示例](https://vjmap.com/demo/#/demo/map/map/setLayerStyle) ```js let styleStyle = svc.rasterStyle(); // 初始状态设置样式值 styleStyle.layers[0].paint = { ...styleStyle.layers[0].paint, // 自定义地图样式,用深色来代替默认的样式 "raster-inverse": 1, // 0不反色 1 反色 "raster-monochrome": "#4586b6", // // 修改地图样式,用纯色代替 "raster-saturation": 0, // 饱和度 取值 -1 到 1 "raster-contrast": 0, // 对比度 取值 -1 到 1 } // 地图对象 let map = new vjmap.Map({ container: 'map', // DIV容器ID style: styleStyle, // 样式,这里是栅格样式 center: prj.toLngLat(prj.getMapExtent().center()), // 设置地图中心点 zoom: 2, // 设置地图缩放级别 renderWorldCopies: false // 不显示多屏地图 }); // 关联服务对象和投影对象 map.attach(svc, prj); await map.onLoad() let layerId = "raster-layer" // 反色 //map.setRasterInverse(layerId, 1) // 0不反色 1 反色 // 纯色 //map.setRasterMonochrome(layerId, "#4586b6") // 饱和度 // map.setRasterSaturation(layerId, 0) // 取值 -1 到 1 // 对比度 // map.setRasterContrast(layerId, 0) // 取值 -1 到 1 // 明亮度最大值 // map.setRasterBrightnessMin(layerId, 0.5) // 取值 0 到 1 // 明亮度最大值 // map.setRasterBrightnessMax(layerId, 1) // 取值 0 到 1 ```
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: 'sys_zp', // 地图ID
	mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
	style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
	// 如果打开出错
	message.error(res.error)
}
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(res.bounds);
let styleStyle = svc.rasterStyle();
// 初始状态设置样式值
styleStyle.layers[0].paint = {
	...styleStyle.layers[0].paint,
	// 自定义地图样式,用深色来代替默认的样式
	"raster-inverse": 1, //  0不反色 1 反色
	"raster-monochrome": "#4586b6", // // 修改地图样式,用纯色代替
	"raster-saturation": 0, // 饱和度  取值 -1 到 1
	"raster-contrast": 0, // 对比度   取值 -1 到 1
}
// 地图对象
let map = new vjmap.Map({
	container: 'map', // DIV容器ID
	style: styleStyle, // 样式,这里是栅格样式
	center: prj.toLngLat(prj.getMapExtent().center()), // 设置地图中心点
	zoom: 2, // 设置地图缩放级别
	renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad()
let layerId = "raster-layer"
// 反色
//map.setRasterInverse(layerId, 1) // 0不反色 1 反色
// 纯色
//map.setRasterMonochrome(layerId, "#4586b6")
// 饱和度
// map.setRasterSaturation(layerId, 0) // 取值 -1 到 1
// 对比度
// map.setRasterContrast(layerId, 0) // 取值 -1 到 1
// 明亮度最大值
//  map.setRasterBrightnessMin(layerId, 0.5) // 取值 0 到 1
// 明亮度最大值
//  map.setRasterBrightnessMax(layerId, 1) // 取值 0 到 1
--- # 地图叠加 ## 地图叠加的两种方式 ### 合并CAD图形进行叠加 此方式是通过`composeMap`合成图形,在后台根据参数把不同的CAD图合成一个新的组合图形。看查合并图时,查看的是组合后的图形。 ```js // --图形处理合成新的图形--通过将图形列表处理后,再合成一个新的图形 let svc = new vjmap.Service(env.serviceUrl, env.accessToken) // 组合成新的图,将sys_world图进行一定的处理后,再与sys_hello进行合成,生成新的地图文件名 let rsp = await svc.composeNewMap([ { mapid: "sys_world", // 地图id // 下面的参数可以根据实际需要来设置,可以对图层,范围,坐标转换来进行处理 //layers: ["经纬度标注","COUNTRY"], // 要显示的图层名称列表 //clipbounds: [10201.981489534268, 9040.030491346213, 26501.267379, 4445.465999], // 要显示的范围 //fourParameter: [0,0,2,0] // 对地图进行四参数转换计算 }, { mapid: "sys_hello" } ]) if (!rsp.status) { message.error(rsp.error) } ``` ### WMS服务组合图形 Web 地图服务 (WMS) 是用于在 Web 上显示 GIS 数据的 最广泛使用和最简单的形式,他能将不同的图形瓦片根据坐标叠加对应到一起。此方式是通过WMS方式,把要叠加显示的图通过WMS叠加到一起,把叠加后的瓦片数据返回给前端。 ```js // 基于底图,通过wms服务叠加其他图形 // 先获取地图元数据来获取图层样式 let cadMapId = "sys_world"; // 要叠加的图形id let style = await svc.createStyle({ backcolor: 0xFFFFFF // 浅色主题 }, cadMapId) let wmsurl = svc.wmsTileUrl({ mapid: cadMapId, version:"v1", layers: style.stylename, mapbounds: res.bounds, fourParameter: "0,0,1,0" // 参数为(平移x,平移y,缩放k,旋转弧度r) 因为坐标与底图一样,所以不用偏移缩放和旋转 }) map.addSource('wms-test-source', { 'type': 'raster', 'tiles': [ wmsurl ], 'tileSize': 256 }); map.addLayer({ 'id': 'wms-test-layer', 'type': 'raster', 'source': 'wms-test-source', 'paint': { "raster-opacity": 1 } } ); ``` ### 两者的优缺点比较如下: WMS服务组合图形: 叠加显示过程中速度快;叠加图形太多时效率低;后期查询数据时不方便(涉及到多个图查询) 合并CAD图形创建叠加图形: 叠加显示过程速度较慢(在后台要重新创建新图形); 创建完成后和组合图形多少没关系了;后期查询方便 以下面的示例以 `WMS服务叠加` 以例。如需要合并CAD图形处理的方法请参考文档 (图形处理)[guide/processMap] ## CAD图根据坐标直接叠加 相关示例 [WMS叠加栅格图形--通过wms服务,把两个图形通过栅格瓦片叠加到一起](https://vjmap.com/demo/#/demo/map/service/27wmsRaster) [WMS叠加多个图形(坐标系自动变化)--通过wms服务,把多个图形通过栅格瓦片叠加到一起](https://vjmap.com/demo/#/demo/map/service/28wmsZMultimap) [WMS叠加多个图形(坐标系不变)--通过wms服务,把多个图形通过栅格瓦片叠加到一起](https://vjmap.com/demo/#/demo/map/service/28wmsZMultimap2) [WMTS叠加多个图形(坐标系自动变化)--通过wmts服务,把多个图形通过栅格瓦片叠加到一起](https://vjmap.com/demo/#/demo/map/service/28wmtsMultimap) [WMTS叠加多个图形(先有底图坐标系自动变化)--通过wmts服务,把多个图形通过栅格瓦片叠加到一起](https://vjmap.com/demo/#/demo/map/service/28wmtszMultimap) [WMTS叠加多个图形(先有底图坐标系不变)--通过wmts服务,把多个图形通过栅格瓦片叠加到一起](https://vjmap.com/demo/#/demo/map/service/28wmtsztMultimap)
// --WMS叠加栅格图形--通过wms服务,把两个图形通过栅格瓦片叠加到一起
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
let res = await svc.openMap({
    mapid: "sys_hello",
    mapopenway: vjmap.MapOpenWay.GeomRender // 以几何数据渲染方式打开
})
if (res.error) {
    message.error(res.error)
}
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
let prj = new vjmap.GeoProjection(mapExtent);
let center = mapExtent.center();
let map = new vjmap.Map({
    container: 'map', // container ID
    style: svc.rasterStyle(),
    center: prj.toLngLat(center),
    zoom: 2,
    renderWorldCopies: false
});
map.attach(svc, prj);
map.fitMapBounds();
await map.onLoad();
map.addControl(new vjmap.NavigationControl());
map.addControl(new vjmap.MousePositionControl({showZoom: true}));
// 基于上面的底图,通过wms服务叠加其他图形
// 先获取地图元数据来获取图层样式
let cadMapId = "sys_world"; // 要叠加的图形id
let style = await svc.createStyle({
    backcolor: 0xFFFFFF // 浅色主题
}, cadMapId)
let wmsurl = svc.wmsTileUrl({
    mapid: cadMapId,
    version:"v1",
    layers: style.stylename,
    mapbounds: res.bounds,
    fourParameter: "0,0,1,0" // 参数为(平移x,平移y,缩放k,旋转弧度r) 因为坐标与底图一样,所以不用偏移缩放和旋转
})
map.addSource('wms-test-source', {
    'type': 'raster',
    'tiles': [
        wmsurl
    ],
    'tileSize': 256
});
map.addLayer({
        'id': 'wms-test-layer',
        'type': 'raster',
        'source': 'wms-test-source',
        'paint': { "raster-opacity": 1 }
    }
);


## CAD图根据四参数校准叠加 示例 [WMS图形排列组合--将多个图通过WMS排列组合](https://vjmap.com/demo/#/demo/map/service/28wmsZZArrange)
// --WMS图形排列组合--将多个图通过WMS排列组合
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
svc.setCurrentMapParam({
    darkMode: true // 由于没有打开过图,所以主动设置黑色模式
})
// 将下面四个图按2行2列的排列方式,组合成新图
let mapIds = ['sys_zp', 'sys_world', 'sys_hello', "sys_topo"];
// 新的地图范围假设设置为0,0,20000,20000
const newMapLength = 20000;
// 先获取四个图的元数据,获取坐标范围
const col = 2;
const cellMapColLength = newMapLength / col;
const cellMapRowLength = newMapLength / Math.ceil( mapIds.length / col);
let allMapParams = [];// 所有的地图参数
let mapFourParam = {};//记录下每个地图的四参数,用于把地图的数据加进新地图中时的坐标转换
let addMarkers = []; // 要增加的每个地图的中心点位置
for(let i = 0; i < mapIds.length; i++) {
    let metadata = await svc.metadata(mapIds[i]);
    let bounds = vjmap.GeoBounds.fromString(metadata.bounds);
    let mapBounds = bounds; // 原来每个地图的地图范围
    let style = await svc.createStyle({
        backcolor: svc.currentMapParam().darkMode ? 0 : 0xFFFFFF
    }, mapIds[i]); // 原来每个地图的地图样式
    bounds = bounds.scale(+metadata.initViewScale * 1.1); // 比初始视图比例稍大点就可以
    // 四参数,找一个图的左上角各右下角坐标与新的坐标下面的左上角和右下解坐标对应起来,来求解四参数(四参数最少要两个点)
    let srcPoints = [
        bounds.min,
        bounds.max
    ];
    const curRow = Math.floor(i / col);
    const curCol = i % col;
    let destMin = [cellMapColLength * curCol, newMapLength - cellMapRowLength * (curRow + 1)];
    let destMax = [cellMapColLength * (curCol + 1), newMapLength - cellMapRowLength * curRow];
    // 求的新范围可能不是正方形,得求出这个矩形的中心点的最小正方形
    let newBounds = new vjmap.GeoBounds(vjmap.geoPoint(destMin), vjmap.geoPoint(destMax));
    const c = newBounds.center();
    let w = newBounds.width() < newBounds.height() ? newBounds.width() : newBounds.height();
    // 另外坐标系上拾取的与上面的点一一对应的坐标
    let destPoints = [
        vjmap.geoPoint([c.x - w / 2.0, c.y - w / 2.0]),
        vjmap.geoPoint([c.x + w / 2.0, c.y + w / 2.0])
    ]
    // 通过坐标参数求出四参数,不用考虑旋转
    // 因为wms叠加,是根据wms的范围去找图的范围,所以参数应设置成destPoints转srcPoints
    let fourparam = vjmap.coordTransfromGetFourParamter(destPoints, srcPoints, true);
    allMapParams.push({
        mapid: mapIds[i],
        fourParameter: [fourparam.dx, fourparam.dy, fourparam.scale, fourparam.rotate],  // 参数为(平移x,平移y,缩放k,旋转弧度r)
        layer: style.stylename,  // 原来每个地图的地图样式
        mapBounds: mapBounds // 原来每个地图的地图范围
    })
    // 下面记录下每个地图的中心点,然后到时在新的地图中,把之前每个点的中心点绘制出来,用于测试坐标转换
    mapFourParam[mapIds[i]] = fourparam;
    addMarkers.push({
        mapid: [mapIds[i]],
        point: bounds.center()
    })
}
let mapBounds = [0, 0, newMapLength, newMapLength];
let mapExtent = vjmap.GeoBounds.fromArray(mapBounds)
// 建立坐标系
let prj = new vjmap.GeoProjection(mapExtent);
// 新建地图对象
let map = new vjmap.Map({
    container: 'map', // container ID
    style: {
        version: svc.styleVersion(),
        glyphs: svc.glyphsUrl(),
        sources: {},
        layers: []
    },// 矢量瓦片样式
    center: [0,0], // 中心点
    zoom: 2,
    renderWorldCopies: false
});
// 地图关联服务对象和坐标系
map.attach(svc, prj);
await map.onLoad();
const addWmsLayer = async (maps) => {
    let mapIds = [];
    let layers = [];
    let fourParameters = [];
    for(let a = 0; a < maps.length; a++) {
        let m = maps[a];
        mapIds.push(m.mapid);
        fourParameters.push(m.fourParameter);
        layers.push(m.layer);
    }
    let wmsurl = svc.wmsTileUrl({
        mapid: mapIds,
        layers: layers,
        fourParameter: fourParameters,
        mapbounds: mapExtent.toString() // 全图的范围
    })
    map.addSource('wms-maps-source', {
        'type': 'raster',
        'tiles': [
            wmsurl
        ],
        'tileSize': 256
    });
    map.addLayer({
            'id': 'wms-maps-layer',
            'type': 'raster',
            'source': 'wms-maps-source',
            'paint': { "raster-opacity": 1 }
        }
    );
}
addWmsLayer(allMapParams);
// 在新的坐标系下,把之前每个地图的中心点绘制出来
for(let m of addMarkers) {
    let fourParam = mapFourParam[m.mapid];
    // 需要注意的是,这个地方因为是从原来的地图到wms坐标范围,所以需要反算 通过四参数反算进行坐标转换
    let newPoint = vjmap.coordTransfromByInvFourParamter(m.point, fourParam);
    new vjmap.Marker({color: vjmap.randomColor()})
        .setLngLat(map.toLngLat(newPoint))
        .addTo(map);
}
map.addControl(new vjmap.MousePositionControl({showZoom: true}));
map.setZoom(0);// 设置到级别最小


## 互联网地图与CAD图叠加(为互联网地图为底图) 此场景是以互联网地图为底图(以互联网地图为坐标系),通用调用唯杰地图CAD图的 `WMS` 服务适配互联网地图来进行叠加显示。 如果CAD图有地理坐标系,如本身是2000坐标系的图,则可以自动叠加。 如果CAD图本身没有坐标系,只能通过人为指定三个以上的公共点进行校准,计算出四参数(平移xy、旋转、缩放)来进行叠加。 具体原理可查看 [坐标转换](/guide/coordtransform.html) 文档。 此场景适合于业务数据大部分是互联网地图数据,CAD图只是叠加查看效果。
// --天地图与CAD图叠加[天地图为底图]--天地图与CAD图叠加,以天地图为坐标系
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 根据地图范围建立经纬度投影坐标系
let prj = new vjmap.LnglatProjection();
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: {
        version: svc.styleVersion(),
        sources: {
            tdt1: {
                type: 'raster',
                tiles: ["https://t3.tianditu.gov.cn/DataServer?T=vec_w&X={x}&Y={y}&L={z}&tk=7baeffb96bf61861b302d0f963cfda66"],
                // 要使用影像请用这个地址
                // tiles: ["https://t3.tianditu.gov.cn/DataServer?T=img_w&X={x}&Y={y}&L={z}&tk=7baeffb96bf61861b302d0f963cfda66"]
            },
            tdt2: {
                type: 'raster',
                tiles: ["https://t3.tianditu.gov.cn/DataServer?T=cva_w&X={x}&Y={y}&L={z}&tk=7baeffb96bf61861b302d0f963cfda66"],
            }
        },
        layers: [{
            id: 'tdt1',
            type: 'raster',
            source: 'tdt1',
        },{
            id: 'tdt2',
            type: 'raster',
            source: 'tdt2',
        }]
    },
    center: prj.toLngLat([117.21242940465396, 39.196783504641644]),
    zoom: 15,
    pitch: 0,
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
// 根据地图本身范围缩放地图至全图显示
//map.fitMapBounds();
await map.onLoad();
// 先获取地图元数据来获取图层样式
let cadMapId = "sys_cad2000"
let style = await svc.createStyle({
    backcolor: 0xFFFFFF // 浅色主题
}, cadMapId)
let wmsurl = svc.wmsTileUrl({
    mapid: cadMapId,
    version:"v1",
    layers: style.stylename,
    srs: "EPSG:3857", // 底图是天地图坐标系
    crs: "EPSG:4548", // cad图是2000坐标  可通过前两位获取 vjmap.transform.getEpsgParam(vjmap.transform.EpsgCrsTypes.CGCS2000, 39).epsg
    // fourParameter: "-39000000,0,1,0"// 此图坐标前面没有加带系,需要加上带系的偏移量,用四参数调节位置
})
map.addSource('wms-test-source', {
    'type': 'raster',
    'tiles': [
        wmsurl
    ],
    'tileSize': 256
});
map.addLayer({
        'id': 'wms-test-layer',
        'type': 'raster',
        'source': 'wms-test-source',
        'paint': { "raster-opacity": 1 }
    }
);
map.addControl(new vjmap.MousePositionControl({showZoom: true}));
## CAD图与互联网地图叠加(为CAD图为底图) 此场景是以CAD为底图(以CAD图为坐标系),通用调用唯杰地图提供的互联网地图的`WMS`服务来适配CAD地图来进行叠加显示。 如果CAD图有地理坐标系,如本身是2000坐标系的图,则可以自动叠加。 如果CAD图本身没有坐标系,只能通过人为指定三个以上的公共点进行校准,计算出四参数(平移xy、旋转、缩放)来进行叠加。 具体原理可查看 [坐标转换](/guide/coordtransform.html) 文档。 此场景适合于业务数据大部分是CAD图数据,互联网地图只是叠加查看效果。
// --CAD图与互联网图叠加--CAD图与互联网图叠加显示,以CAD图为坐标系
// 假设我们希望CAD全图显示是对应级别为8级,这样地图缩小后,互联网地图还会显示
let initZoom = 8;
let style = {
    backcolor: 0, // div为深色背景颜色时,这里也传深色背景样式
    clipbounds: Math.pow(2, initZoom) // 只传值,不传范围,表示之前的范围放大多少倍
}
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
let res = await svc.openMap({
    mapid: "sys_cad2000",
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style:  style
})
if (res.error) {
    consoloe.error(res.error)
}
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
let prj = new vjmap.GeoProjection(mapExtent);
let center = mapExtent.center();
let map = new vjmap.Map({
    container: 'map', // container ID
    style: svc.rasterStyle(),
    center: prj.toLngLat(center),
    zoom: 10,
    renderWorldCopies: false
});
map.attach(svc, prj);
await map.onLoad();
map.addControl(new vjmap.NavigationControl());
map.addControl(new vjmap.MousePositionControl({showZoom: true}));
// 如果x是8位,可通过前两位获取 如 vjmap.transform.getEpsgParam(vjmap.transform.EpsgCrsTypes.CGCS2000, 39).epsg
// 如果x是6位,是通过下面此工具 https://vjmap.com/demo/#/demo/map/web/03webzgetepsg,把地图移动至cad图位置的省市,点击获取当前地图中心经纬度, 计算EPSG,找一个与 CAD图距离相近的EPSG
let epsg = "EPSG:4509" // cad图的epsg
// 增加高德地图底图
const addGaodeMap = async (isRoadway) => {
    const tileUrl = svc.webMapUrl({
        tileCrs: "gcj02",
        tileUrl:  isRoadway ? [
                "https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}"
            ] :
            /* 如果用影像 */
            [
                "https://webst0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=6&x={x}&y={y}&z={z}",
                "https://webst0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}"
            ],
        tileSize: 256,
        tileRetina: 1,
        tileMaxZoom: 18,
        tileShards: "1,2,3,4",
        tileToken: "",
        tileFlipY: false,
        mapbounds: res.bounds,
        srs: epsg
    })
    map.addSource('web-gaode-source', {
        'type': 'raster',
        'tiles': [
            tileUrl
        ],
        'tileSize': 256
    });
    map.addLayer({
            'id': 'web-gaode-layer',
            'type': 'raster',
            'source': 'web-gaode-source',
            'paint': { "raster-opacity": 1 }
        }
    );
    let layers = map.getStyle().layers
    // 把这个图层放至所有图层的最下面
    map.moveLayer('web-gaode-layer', layers[0].id)
    // cad坐标与高德坐标相互转换示例
    let webCo = await cad2webCoordinate(center, false); // cad转高德
    let cadCo = await web2cadCoordinate(webCo, false); // 高德转cad
    console.log(center, webCo, cadCo)
}
// 增加天地图图底图
const addTiandituMap = async (isRoadway) => {
    const tileUrl = svc.webMapUrl({
        tileCrs: "wgs84",
        tileUrl: [
            isRoadway ? "https://t{s}.tianditu.gov.cn/DataServer?T=vec_w&X={x}&Y={y}&L={z}&tk={t}" :
                /* 如果用影像 */"https://t{s}.tianditu.gov.cn/DataServer?T=img_w&X={x}&Y={y}&L={z}&tk={t}",
            "https://t{s}.tianditu.gov.cn/DataServer?T=cva_w&X={x}&Y={y}&L={z}&tk={t}"
        ],
        tileSize: 256,
        tileRetina: 1,
        tileMaxZoom: 18,
        tileShards: "1,2,3,4,5,6",
        tileToken: [
            "6d53378dc5f7dbef8d84ffdd2b54139b", // 有可能有一个token会导致请求过多而返回失败,可以多写几个,失败时会尝试后面的token
            "69eb2fa0de3b2a668f1ef603a3f8bc73"
        ],
        tileFlipY: false,
        mapbounds: res.bounds,
        srs: epsg
    })
    map.addSource('web-tdt-source', {
        'type': 'raster',
        'tiles': [
            tileUrl
        ],
        'tileSize': 256
    });
    map.addLayer({
            'id': 'web-tdt-layer',
            'type': 'raster',
            'source': 'web-tdt-source',
            'paint': { "raster-opacity": 1 }
        }
    );
    let layers = map.getStyle().layers
    // 把这个图层放至所有图层的最下面
    map.moveLayer('web-tdt-layer', layers[0].id)
    // cad坐标与天地图坐标相互转换示例
    let webCo = await cad2webCoordinate(center, true); // cad转天地图
    let cadCo = await web2cadCoordinate(webCo, true); // 天地图转cad
    console.log(center, webCo, cadCo)
}
const removeMapLayer = ()=> {
    // 删除source及source下面的所有图层
    map.removeSourceEx(['web-gaode-source', 'web-tdt-source'])
}
// cad转web坐标,isWgs84是否为wgs84 4326坐标,如天地图;否的话为gcj02火星坐标,如高德地图
const cad2webCoordinate = async (pt, isWgs84) => {
    // 通过坐标转换把转成wgs84 4326坐标
    let res = await svc.cmdTransform(epsg,"EPSG:4326",pt); // 如果不想调服务转,也可以在前端用proj4库
    // 如果为火星坐标,还需要从4326转火星
    let webPt = res[0];// 返回的第1条结果
    if (isWgs84 === false) {
        webPt = vjmap.transform.convert(webPt, vjmap.transform.CRSTypes.EPSG4326, vjmap.transform.CRSTypes.AMap)
    }
    return webPt;
}
// web转cad坐标,isWgs84是否为wgs84 4326坐标,如天地图;否的话为gcj02火星坐标,如高德地图
const web2cadCoordinate = async (pt, isWgs84) => {
    // (上面计算过程的逆过程), 如果为火星坐标,还需要从火星转4326,再通过坐标转换把wgs84 4326转成cad图坐标
    if (isWgs84 === false) {
        pt = vjmap.transform.convert(pt, vjmap.transform.CRSTypes.AMap, vjmap.transform.CRSTypes.EPSG4326);
    }
    // 通过坐标转换把cad epsg转成wgs84 4326坐标
    let res = await svc.cmdTransform("EPSG:4326",epsg,vjmap.geoPoint(pt)); // 如果不想调服务转,也可以在前端用proj4库
    let cadPt = res[0];// 返回的第1条结果
    return cadPt
}
map.on("zoomend", async ()=> {
    let center = map.fromLngLat(map.getCenter());// 得到cad地图地理中心坐标点
    let gcj02Web = await cad2webCoordinate(center, false);// 转高德坐标
    let wgs84Web = await cad2webCoordinate(center, true);// 转天地图坐标
    let content = `当前地图中心点坐标: CAD坐标:${center.x.toFixed(4)},${center.y.toFixed(4)}; 高德坐标:${gcj02Web[0].toFixed(4)},${gcj02Web[1].toFixed(4)}; 天地图坐标:${wgs84Web[0].toFixed(4)},${wgs84Web[1].toFixed(4)}`;
    console.log({ content, key: "info", duration: 3});
})
addGaodeMap(true);
::: tip 提示 更多互联网地图叠加显示的介绍请查看 [互联网地图](/guide/webmap.html) 文档。 ::: --- # SQL查询 当CAD图纸以`几何渲染`方式打开时,后台会自动把CAD的数据转换为空间数据并保存至`空间数据库`。在前端可以通过`sql`对后台数据进行自定义查询。 ## 后台表结构 具体可查看文档[服务端条件查询和表达式查询](/guide/svrStyleVar.html#条件查询) 针对后台数据库表结构,使用sql语句条件部分进行数据查询。只需要写`where`后面的部分 表结构如下 |字段名|类型|说明| |:---|:---|:---| |id|integer|id| |objectid|text|实体id(块objectid命名规则:块id_引用的块定义id1_引用的块定义id2(可能有多个)_实体id; 组objectid命名规则:组id$实体id_实体内元素索引;表格命名规则:表格id@实体id_实体内元素索引,其余实体命名规则: id_实体内元素索引| |layerindex|integer|图层索引| |name|text|实体类型值,如"1"表示AcDbLine,具体值查看下面的“支持的CAD实体类型”| |color|int|颜色(根据rgb颜色计算此值,例如黑色为0x000000,加上透明度为0xFF000000, 变成无符号 0xff000000<<0结果为 -16777216)| |data|blob|几何数据| |s1|text|线型| |s2|text|扩展字典数据| |s3|text|坐标数据,不同实体表达不一样| |s4|text|字符型属性数据, 不同实体表达不一样| |s5|text|字符型属性数据, 不同实体表达不一样| |n1|float|颜色索引| |n2|float|线型比例| |n3|float|线宽| |n4|float|透明度| |n5|float|数值型属性数据, 不同实体表达不一样| |n6|float|数值型属性数据, 不同实体表达不一样| |n7|float|数值型属性数据, 不同实体表达不一样| |n8|float|数值型属性数据, 不同实体表达不一样| |n9|float|数值型属性数据, 不同实体表达不一样| |n10|float|数值型属性数据, 不同实体表达不一样| |geom|GEOMETRY|实体外包矩形| ## 表结构及每个字段对应的意义及说明 | id | objectid | layerindex | name | color | s3 | s4 | s5 | n5 | n6 | n7 | n8 | n9 | n10 | |:---------|:--------|:----|:----------|:----------|:-------------|:--------------|:----|:----|:----|----------|----------|:---------|:---------| | 数据库id 整型 | 实体id 字符型 | 图层索引 整型 | 类型值 字符型 | 颜色 整型 | 字符型 | 字符型 | 字符型 | 数值型 | 数值型 | 数值型 | 数值型 | 数值型 | 数值型 | | | | | 1 (AcDbLine 直线) | | points | | | thickness | length | | | | | | | | | 2 (AcDbPolyline 多段线 | | points | bulge | data3d | area | isclosed | linetypeScale | thickness | elevation | | | | | | 3 (AcDb2dPolyline 二维折线) | | points | | | area | isclosed | polyType | thickness | elevation | | | | | | 4 (AcDb3dPolyline 三维多段线) | |points | | | area | isclosed | polyType | | | | | | | | 5 (AcDbSpline 样条曲线) | | points | | | area | isclosed | isFit | fitTol |degree | | | | | | 6 (AcDbArc 圆弧) | | center | | | area | isclosed | radius | startAngle | endAngle | thickness | | | | | 7 (AcDbCircle 圆) | | center | | | area | isclosed | radius | thickness | | | | | | | 8 (AcDbEllipse 椭圆) | | center | majorAxis | | area | isclosed | radius | startAngle |endAngle | | | | | | 9 (AcDbCurve 曲线) | | | | | area | isclosed | | | | | | | | | 10 (AcDbBlockReference 块参照) | | positon | attributeDef | blockname | rotate | scaleX | scaleY | scaleZ | | | | | | | 11 (AcDbHatch 填充) | | points |patternName | | elevation | patternAngle | patternScale | | | | | | | | 12 (AcDbMText 多行文本) | | location | contents | text | height | width | rotate | textHeight | actualHeight | actualWidth | | | | | 13 (AcDbText 单行文本) | | location |text | | height | rotate | horzMode | | | | | | | | 14 (AcDbShape 型实体) | | positon | normal | | rotate | size | | | | | | | | | 15 (AcDbRasterImage 栅格图片) | | origin | | | | | | | | | | | | | 16 (AcDbWipeout 遮罩实体) | | 16 | | | | | | | | | | | | | 17 (AcDb2LineAngularDimension 角度标注[两条线]) | | | | | | | | | | | | | | | 18 (AcDb3PointAngularDimension 角度标注[三点]) | | | | | | | | | | | | | | | 19 (AcDbAlignedDimension 对齐标注) | | | | | | | | | | | | | | | 20 (AcDbArcDimension 圆弧标注) | | | | | | | | | | | | | | | 21 (AcDbDiametricDimension 直径标注) | | | | | | | | | | | | | | | 22 (AcDbOrdinateDimension 坐标标注) | | | | | | | | | | | | | | | 23 (AcDbRadialDimension 半径标注) | | | | | | | | | | | | | | | 24 (AcDbRadialDimensionLarge 半径折线标注) | | | | | | | | | | | | | | | 25 (AcDbRotatedDimension 转角标注) | | | | | | | | | | | | | | | 26 (AcDbAttributeDefinition 属性注记) | | location |text | contents | height | rotate | horzMode | | | | | | | | 27 (AcDbAttribute 块属性) | | location |text | contents | height | rotate | horzMode | | | | | | | | 28 (AcDbTable 表格) | | positon | attributeDef | contents | rotate | scaleX | scaleY | scaleZ | | | | | | | 29 (AcDbMLeader 引线标注) | | location | contents | text | height | width | rotate | textHeight |actualHeight | actualWidth | | | | | 30 (AcDbRegion 面域) | | points | indices | data3d | area | perimeter | | | | | | | | | 31 (AcDbPolyFaceMesh 多面网格实体) | | points | indices | data3d | | | | | | | | | | | 32 (AcDbPolygonMesh 多边形网格) | | points |indices | data3d | | | | | | | | | | | 33 (AcDbSurface 曲面) | | points | indices | data3d | | | | | | | | | | | 34 (AcDb3dSolid 三维实体) | | points | indices| data3d | | | | | | | | | | | 35 (AcDbFace 三维面) | | points | indices | data3d | | | | | | | ## SQL示例说明 **只需要写`where`后面的部分** 查询图中的`所有的直线` ```js name='1' ``` 查询图中`图层索引为0`的所有`三维多段线` ```js name='4' and layerindex=0 ``` 查询图中`红色`的`单行文本` ```js name='13' and color=-16776961 ``` 查询图中`objectid为`的`9BE`的实体 ```js objectid='9BE' ``` 查询图中所有文本(单行文本、多行文本、属性注记、块属性) ```js name='13' or name='12' or name='26' or name='27' ``` 查询图中`objectId`为`EA`、`10D`、`19A`开头的块实体 ```js objectid like 'EA_%' or objectid like '10D_%' or objectid like '19A_%' ``` 查询图中`长度为10的直线` ```js name='1' and n6 = 10 ``` 查询图中`半径为5的圆` ```js name='7' and n7 = 5 ``` ## 条件查询 ```ts /** * 条件查询实体 * @param param 参数 * @param cb 结果中每个点的处理回调。如果返回空的话,则用默认处理方法 */ conditionQueryFeature(param: IConditionQueryFeatures, cb?: (point: [number, number]) => [number, number] | null | undefined): Promise; /** * 条件查询实体参数 */ export interface IConditionQueryFeatures extends IQueryBaseFeatures { /** 条件. */ condition: string; /** 范围.或者点坐标数组 */ bounds?: [number, number, number, number] | [number, number][]; /** 是否获取交点 bounds 为点坐标数组时有效,默认false */ isGetIntersections?: boolean; /** 记录开始位置. */ beginpos?: number; /** 是否返回几何数据,为了性能问题,realgeom为false时,如果返回条数大于1.只会返回每个实体的外包矩形,如果条数为1的话,会返回此实体的真实geojson;realgeom为true时每条都会返回实体的geojson */ includegeom?: boolean; /** 是否返回真实实体几何geojson.与 includegeom参数,结合使用。参考includegeom的用法*/ realgeom?: boolean; /** 是否为包含关系, true为包含关系,false为相交关系,默认false. (传入了bounds进行范围查询时有效)*/ isContains?: boolean; } ``` 查询图中所有文字示例 ```js let query = await svc.conditionQueryFeature({ condition: `name='13' or name='12' or name='26' or name='27'`, // 只需要写sql语句where后面的条件内容,字段内容请参考文档"服务端条件查询和表达式查询" fields: "", geom: true, limit: 100000 //设置很大,相当于把所有的圆都查出来。不传的话,默认只能取100条 }) ```
// --获取图中所有文字并高亮--
// js代码
// 新建地图服务对象,传入服务地址和token
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: env.exampleMapId, // 地图ID,(请确保此ID已存在,可上传新图形新建ID)
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    message.error(res.error)
}
// 获取地图的范围
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
// 建立坐标系
let prj = new vjmap.GeoProjection(mapExtent);
// 新建地图对象
let map = new vjmap.Map({
    container: 'map', // container ID
    style: svc.rasterStyle(), // 栅格瓦片样式
    center: prj.toLngLat(mapExtent.center()), // 中心点
    zoom: 2,
    renderWorldCopies: false
});
// 地图关联服务对象和坐标系
map.attach(svc, prj);
// 使地图全部可见
map.fitMapBounds();
let polygonLayer;
const getAllTexts = async ()=> {
    if (polygonLayer) return; // 如果绘制过了,不绘制了
    // 实体类型ID和名称映射
    const { entTypeIdMap } = await svc.getConstData();
    const getTypeNameById = name => Object.keys(entTypeIdMap).find(e => entTypeIdMap[e] === name);
    let polygons = [];
    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: "",
        geom: true,
        limit: 100000 //设置很大,相当于把所有的圆都查出来。不传的话,默认只能取100条
    })
    if (query.error) {
        message.error(query.error);
        return;
    } else {
        if (query.recordCount > 0) {
            for(let i = 0; i < query.recordCount; i++) {
                let bounds = map.getEnvelopBounds(query.result[i].envelop);
                let clr = vjmap.entColorToHtmlColor(query.result[i].color); // 实体颜色转html颜色(
                polygons.push({
                    points: map.toLngLat(bounds.toPointArray()),
                    properties: {
                        name: query.result[i].objectid,
                        color: clr,
                        text: query.result[i].text
                    }
                });
            }
        }
    }
    polygonLayer = new vjmap.Polygon({
        data: polygons,
        fillColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'yellow', '#f0f'],
        fillOpacity: ['case', ['to-boolean', ['feature-state', 'hover']], 0.6, 0.4],
        fillOutlineColor: ['get', 'color'],
        isHoverPointer: true,
        isHoverFeatureState: true
    });
    polygonLayer.addTo(map);
    polygonLayer.hoverFeatureState(e => {
        if (e.features) {
            map.logInfo(`名称为 ${e.features[0].properties.name},值为 ${e.features[0].properties.text} 的文字`)
        }
    })
}
getAllTexts()


## 点查询 ```ts /** * 点查询实体 * @param param 参数 * @param cb 结果中每个点的处理回调。如果返回空的话,则用默认处理方法 */ pointQueryFeature(param: IPointQueryFeatures, cb?: (point: [number, number]) => [number, number] | null | undefined): Promise; /** * 点查询实体参数 */ export interface IPointQueryFeatures extends IQueryBaseFeatures { /** 查询的坐标X. */ x: number; /** 查询的坐标Y. */ y: number; /** 像素大小. */ pixelsize?: number; /** 条件. */ condition?: string; /** 返回最大的几何字节数. */ maxGeomBytesSize?: number; /** 当前一个像素表示多少几何长度,如果输入了此值,则为此值为主,否则,根据输入的zoom值后台自动计算. */ pixelToGeoLength?: number; } /** * 查询实体参数 */ export interface IQueryBaseFeatures { /** 当前级别. */ zoom?: number; /** 地图ID(为空时采用当前打开的mapid). */ mapid?: string; /** 地图版本(为空时采用当前打开的地图版本). */ version?: string; /** 图层名称(为空时采用当前打开的地图图层名称). */ layer?: string; /** 返回最多的记录条数. */ limit?: number; /** 是返回的字段列表,多个之是用逗号,分开,如. "name,objectid" */ fields?: string; /** 是否为几何图形查询. */ geom?: boolean; /** GeoJSON几何数据简化墨托卡距离,默认为零,不简化。例如允许10级别以上一个像素级别的误差,可用 map.pixelToGeoLength(1, 10) * vjmap.Projection.EQUATORIAL_SEMIPERIMETER * 2 / map.getGeoBounds(1.0).width() */ simplifyTolerance?: boolean; /** 启动cache(内存打开的图形有效). */ useCache?: boolean; /** 查询返回的坐标默认为墨卡托坐标,返回会程序会自动转化为cad坐标,如果要直接返回cad地图坐标,需要把toMapCoordinate设置为true. */ toMapCoordinate?: boolean; } ``` 示例 [叠加图形上点击选择CAD实体](叠加图形上点击选择CAD实体) ## 矩形查询 ```ts /** * 矩形查询实体 * @param param 参数 * @param cb 结果中每个点的处理回调。如果返回空的话,则用默认处理方法 */ rectQueryFeature(param: IRectQueryFeatures, cb?: (point: [number, number]) => [number, number] | null | undefined): Promise; /** * 矩形查询实体参数 */ export interface IRectQueryFeatures extends IQueryBaseFeatures { /** 查询的坐标X1. (如果x1,y1,x2,y2同时不输的话,表示是查询整个图的范围 */ x1?: number; /** 查询的坐标Y1. */ y1?: number; /** 查询的坐标X2. */ x2?: number; /** 查询的坐标Y2. */ y2?: number; /** 条件. */ condition?: string; /** 返回最大的几何字节数. */ maxGeomBytesSize?: number; } /** * 查询实体参数 */ export interface IQueryBaseFeatures { /** 当前级别. */ zoom?: number; /** 地图ID(为空时采用当前打开的mapid). */ mapid?: string; /** 地图版本(为空时采用当前打开的地图版本). */ version?: string; /** 图层名称(为空时采用当前打开的地图图层名称). */ layer?: string; /** 返回最多的记录条数. */ limit?: number; /** 是返回的字段列表,多个之是用逗号,分开,如. "name,objectid" */ fields?: string; /** 是否为几何图形查询. */ geom?: boolean; /** GeoJSON几何数据简化墨托卡距离,默认为零,不简化。例如允许10级别以上一个像素级别的误差,可用 map.pixelToGeoLength(1, 10) * vjmap.Projection.EQUATORIAL_SEMIPERIMETER * 2 / map.getGeoBounds(1.0).width() */ simplifyTolerance?: boolean; /** 启动cache(内存打开的图形有效). */ useCache?: boolean; /** 查询返回的坐标默认为墨卡托坐标,返回会程序会自动转化为cad坐标,如果要直接返回cad地图坐标,需要把toMapCoordinate设置为true. */ toMapCoordinate?: boolean; } ``` 示例 [数据自动生成CAD工程剖面图](https://vjmap.com/demo/#/demo/map/comprehensive/03datatodwgmap?lineNumber=496&column=25&endColumn=40) `矩形查询`也可以使用`条件查询` `conditionQueryFeature`,查询条件`bounds`为一个`矩形范围`[number, number, number, number]。 ## 多边形查询 `多边形查询` 可以使用`条件查询` `conditionQueryFeature`,查询条件`bounds`为一个`坐标数组` Array<[number, number]>。 示例 [多边形选择实体](https://vjmap.com/demo/#/demo/map/service/22hightselentitypoly) ## 分页查询 如果数据很多时,可以采用分页查询 如查询图中所有直线段(直线,二三维多段线) ```js // 在图中查找所有的直线段 const getMapHVLines = async () => { // 查找图中所有的直线,二三维多段线 let queryEntTypes = ['AcDbLine', 'AcDbPolyline', 'AcDb2dPolyline', 'AcDb3dPolyline']; let cond = queryEntTypes.map(t => `name='${getTypeNameById(t)}'`).join(' or '); // sql条件 let result = []; let beginPos = 0; // 记录查询开始位置 // 有可能记录数会很多,这里用分页查询 while(true) { let query = await svc.conditionQueryFeature({ condition: cond, // 只需要写sql语句where后面的条件内容,字段内容请参考文档"服务端条件查询和表达式查询" fields: "objectid,points,envelop", // 只要id,坐标 beginpos: beginPos, // 记录开始位置 limit: 100000 // 每次查10万条 }); beginPos += query.result.length; // 开始位置位置挪动 result.push(...query.result || []); if (result.length >= query.recordCount) break; } result = result.filter(e => { let points = e.points.split(";"); if (points.length !=2 ) return false; e.geoStart = vjmap.GeoPoint.fromString(points[0]); delete e.geoStart.z;// 不考虑z值 e.geoEnd = vjmap.GeoPoint.fromString(points[1]); delete e.geoEnd.z;// 不考虑z值 if (e.geoStart.x > e.geoStart.x) { // 交换下 let temp = e.geoStart; e.geoStart = e.geoEnd; e.geoEnd = temp; } e.startPoint = e.geoStart.toString(); e.endPoint = e.geoEnd.toString(); if (e.startPoint == e.endPoint) { // 同一个点 return false; } let line = points.map(e=>vjmap.geoPoint(e.split(","))) let isVLine = vjmap.isZero(line[0].x - line[1].x);//竖线 let isHLine = vjmap.isZero(line[0].y - line[1].y);//横线 if (!(isVLine || isHLine)) return false; // 并且是横线或竖线 e.isHorzLine = isHLine; return true }) return result; } // 实体类型ID和名称映射 const { entTypeIdMap } = await svc.getConstData(); const getTypeNameById = name => { for(let id in entTypeIdMap) { if (entTypeIdMap[id] == name) { return id } } } ``` --- # 表达式查询 当CAD图纸以`内存模式`方式直接打开时,因为没有转`空间数据库`这个过程,所以在前端无法可以通过`sql`对后台数据进行自定义查询,只能通过`js脚本`进行查询或通过`表达式`进行查询。表达式语法具体可查看文档[服务端渲染表达式语法](/guide/expr.html)。 ::: tip 提示 `js脚本` 更好理解,容易上手,但性能稍慢 `表达式` 较难理解,上手较难,但性能好 推荐用`js脚本` ::: ## 内置变量 |变量|说明|类型|举例| |:---|:---|:---|:---| |gInLayerIndex|当前实体图层索引|(数值型)|0| |gInObjectId|当前实体对象ID|(字符串)|'283' 判断如 gInObjectId=='283' |gInFeatureType|当前实体类型|(字符串)|'AcDbPolyline' 判断如 gInFeatureType=='AcDbPolyline' |gInLayerName|当前实体图层名称|(字符串)|'图层1' 判断如 gInLayerName=='图层1' |gInColor|当前实体颜色|(数值型)|4278255615 |gInColorRed|当前实体颜色(红色分量)|(数值型)|255 |gInColorGreen|当前实体颜色(绿色分量)|(数值型)|255 |gInColorBlue|当前实体颜色(蓝色分量)|(数值型)|0 |gInColorAlpha|当前实体颜色(透明度分量)|(数值型)|0 |println|调试输出函数| |gInExtendMinX|当前实体图形范围(右下角X)|(数值型)|700.10 |gInExtendMinY|当前实体图形范围(右下角y)|(数值型)|10.01 |gInExtendMaxX|当前实体图形范围(左上角X)|(数值型)|800.20 |gInExtendMaxY|当前实体图形范围(左上角y)|(数值型)|20.02 |gInAttrs1|当前实体属性一值|(字符串) s1 线型| |gInAttrs2|当前实体属性二值|(字符串) s2 扩展字典数据| |gInAttrs3|当前实体属性三值|(字符串) s3 不同实体表达不一样| |gInAttrs4|当前实体属性四值|(字符串) s4 字符型属性数据, 不同实体表达不一样| |gInAttrs5|当前实体属性五值|(字符串) s5 字符型属性数据, 不同实体表达不一样| |gInAttrn6|当前实体属性六值|(数值型) n1 颜色索引| |gInAttrn7|当前实体属性七值|(数值型) n2 线型比例| |gInAttrn8|当前实体属性八值|(数值型) n3 线宽| |gInAttrn9|当前实体属性九值|(数值型) n4 透明度| |gInAttrn10|当前实体属性十值|(数值型) n5 数值型属性数据, 不同实体表达不一样| |gInAttrn11|当前实体属性11值|(数值型) n6 不同实体表达不一样| |gInAttrn12|当前实体属性12值|(数值型) n7 不同实体表达不一样| |gInAttrn13|当前实体属性13值|(数值型) n8 不同实体表达不一样| |gInAttrn14|当前实体属性14值|(数值型) n9 不同实体表达不一样| |gInAttrn15|当前实体属性15值|(数值型) n10 不同实体表达不一样| |gInCoordX|当前实体坐标X数组|(数值型数组)|[10,20...] gInCoordX[0]=10| |gInCoordY|当前实体坐标Y数组|(数值型数组)|[10,20...] gInCoordY[0]=10| |gInCoordZ|当前实体坐标Z数组|(数值型数组)|[10,20...] gInCoordZ[0]=10| |gInCoordCount|当前实体坐标个数|(数值型)|2| |gOutColorRed|返回的颜色(红色分量)(修改当前实体颜色)|(数值型)|255,如gOutColorRed := 255;表示所有级别红色分量为255; 也可以是数组表示,如gOutColorRed[0] := 122;gOutColorRed[5] := 200;表示1-4级别红色分量是122,5级别以上是红色分量200 |gOutColorGreen|返回的颜色(红色分量)(修改当前实体颜色)|(数值型)|255,用法同上 |gOutColorBlue|返回的颜色(红色分量)(修改当前实体颜色)|(数值型)|255,用法同上 |gOutColorAlpha|返回的颜色(透明度分量)(修改当前实体颜色)|(数值型)|255,用法同上 |gOutLineWidth|返回的线宽值(修改当前实体线宽值)|(数值型)|3,用法同上 |gOutVisible|返回是否可见(修改当前实体是否可见)|(数值型)|1或0,用法同上 |gOutReturn|0 不加入选择集;1加入选择集| ## 支持的CAD实体类型 | 类名称 |说明| 类型值 | |:---------------------------|:---|:----| | AcDbLine |直线| 1 | | AcDbPolyline |多段线| 2 | | AcDb2dPolyline |二维折线| 3 | | AcDb3dPolyline |三维多段线| 4 | | AcDbSpline |样条曲线| 5 | | AcDbArc |圆弧| 6 | | AcDbCircle |圆| 7 | | AcDbEllipse |椭圆| 8 | | AcDbCurve |曲线| 9 | | AcDbBlockReference |块参照| 10 | | AcDbHatch |填充| 11 | | AcDbMText |多行文本| 12 | | AcDbText |单行文本| 13 | | AcDbShape |型实体| 14 | | AcDbRasterImage |栅格图片| 15 | | AcDbWipeout |遮罩实体| 16 | | AcDb2LineAngularDimension |角度标注[两条线]| 17 | | AcDb3PointAngularDimension | 角度标注[三点]| 18 | | AcDbAlignedDimension |对齐标注| 19 | | AcDbArcDimension |圆弧标注| 20 | | AcDbDiametricDimension |直径标注| 21 | | AcDbOrdinateDimension |坐标标注| 22 | | AcDbRadialDimension |半径标注| 23 | | AcDbRadialDimensionLarge |半径折线标注| 24 | | AcDbRotatedDimension |转角标注| 25 | | AcDbAttributeDefinition |属性注记| 26 | | AcDbAttribute |块属性| 27 | | AcDbTable |表格| 28 | | AcDbMLeader |引线标注| 29 | | AcDbRegion |面域| 30 | | AcDbPolyFaceMesh |多面网格实体| 31 | | AcDbPolygonMesh |多边形网格| 32 | | AcDbSurface |曲面| 33 | | AcDb3dSolid |三维实体| 34 | | AcDbFace |三维面| 35 | | AcDbSolid |实线实体| 36 | ## 表达式示例说明 查询图中的`所有的直线` js ```js return gInFeatureType=='AcDbLine' ``` 查询图中`图层名称为图层1`的所有`三维多段线` ```js return (gInFeatureType=='AcDb3dPolyline' && gInLayerName=='图层1') ``` 查询图中`红色`的`单行文本` ```js return (gInFeatureType=='AcDbText' && gInColorRed==255 && gInColorGreen==0 && gInColorBlue==0) ``` 查询图中`objectid为`的`9BE`的实体 ```js return gInObjectId=='9BE' ``` 查询图中所有文本(单行文本、多行文本、属性注记、块属性) ```js return (gInFeatureType=='AcDbText' || gInFeatureType=='AcDbMText' || gInFeatureType=='AcDbAttributeDefinition' || gInFeatureType=='AcDbAttribute') ``` 查询图中所有块实体 ```js return gInFeatureType=='AcDbBlockReference' ``` 查询图中`长度为10的直线` ```js return (gInFeatureType=='AcDbLine' && gInAttrn11 == 10) ``` 查询图中`半径为5的圆` ```js return (gInFeatureType=='AcDbCircle' && gInAttrn12 == 5) ``` 图询图中`所有多段线`并且`图层为网格线`,同时`坐标x需大于10` ```js return (gInFeatureType == 'AcDbPolyline' && gInLayerName == '网格线' && gInExtendMinX >= 10) ``` ## 查询方法 ```ts /** * 表达式查询实体 * @param param 参数 * @param cb 结果中每个点的处理回调。如果返回空的话,则用默认处理方法 */ exprQueryFeature(param: IExprQueryFeatures, cb?: (point: [number, number]) => [number, number] | null | undefined): Promise; /** * 表达式查询实体参数 */ export interface IExprQueryFeatures extends IQueryBaseFeatures { /** 表达式. */ expr: string; /** 记录开始位置. */ beginpos?: number; } ``` 写表达式时,需要根据条件判断是否返回,格式为: ```js return true; // 或 return false; ``` 如查询图中所有块实体,表达式为 ```js return gInFeatureType == 'AcDbBlockReference' ``` 查询写法为: ```js let query = await svc.exprQueryFeature({ // 表达式写法 // expr: `gOutReturn := if((gInFeatureType == 'AcDbPolyline' and gInLayerName == '网格线' and gInExtendMinX >= ${center.x} ), 1, 0);`, // 现在也支持js语法 // expr: `if (gInFeatureType == 'AcDbPolyline' && gInLayerName == '网格线' && gInExtendMinX >= ${center.x} ) { // return true; // } else { // return false; // }`, // 过滤出所有块对象实体(表达式写法) // expr: `gOutReturn := if((gInFeatureType == 'AcDbBlockReference'), 1, 0);`, // 过滤出所有块对象实体(js写法) expr: `return gInFeatureType == 'AcDbBlockReference'`, fields: "objectid,blockname,envelop", useCache: true, limit: 1000000 // 数量大些,相当于查找所有的 }) ```
// --后台查询实体数据[内存渲染]--直接在后台以内存方式打开已上传的CAD的DWG格式的图模式下,在后台通过条件查询实体数据
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
let res = await svc.openMap({
    mapid: env.exampleMapId,
    mapopenway: vjmap.MapOpenWay.Memory, // 以内存渲染方式打开
    style: vjmap.openMapDarkStyle()
})
if (res.error) {
    message.error(res.error)
}
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
let prj = new vjmap.GeoProjection(mapExtent);
let center = mapExtent.center();
let map = new vjmap.Map({
    container: 'map', // container ID
    style: svc.rasterStyle(),
    center: prj.toLngLat(center),
    zoom: 2,
    renderWorldCopies: false
});
map.attach(svc, prj);
map.fitMapBounds();
map.addControl(new vjmap.NavigationControl());
map.addControl(new vjmap.MousePositionControl({showZoom: true}));
const handleQuery= async () => {
    let query = await svc.exprQueryFeature({
        // 过滤出所有块对象实体
        expr: `return gInFeatureType == 'AcDbBlockReference'`,
        fields: "objectid,blockname,envelop",
        useCache: true,
        limit: 1000000 // 数量大些,相当于查找所有的
    })
    if (query.error) {
        message.error(query.error)
    } else {
        // 根据要过滤的块名称过滤结果
        let polygons = []
        for(let i = 0; i < query.result.length; i++) {
            let blockname = query.result[i].blockname;
            let bounds = map.getEnvelopBounds(query.result[i].envelop);
            polygons.push({
                points: map.toLngLat(bounds.toPointArray()),
                properties: {
                    name: "objectid:" + query.result[i].objectid,
                    color: '#ff00ff'
                }
            });
        }
        map.logInfo(`查询到符合的记数条数:${query.recordCount}`)
        // 绘制查找出来的块
        let polygon = new vjmap.Polygon({
            data: polygons,
            fillColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'yellow', ['get', 'color']],
            fillOpacity: ['case', ['to-boolean', ['feature-state', 'hover']], 0.8, 0.5],
            // 如果一开始不显示这个多边形,可以把透明度设置为0,鼠标移上去才会显示多边形
            // fillOpacity: ['case', ['to-boolean', ['feature-state', 'hover']], 0.8, 0.0],
            fillOutlineColor: ['get', 'color'],
            isHoverPointer: true,
            isHoverFeatureState: true
        });
        polygon.addTo(map);
    }
};
handleQuery()


## 表达式查询和Sql查询结合使用 [在线查看示例多种查询块对象子实体面积最大值](https://vjmap.com/demo/#/demo/map/service/22findzmultiquery) ```js // --多种查询块对象子实体面积最大值--查询块中面积最大的实体,先通过表达式查询,再通过条件查询。一起分析结果 let svc = new vjmap.Service(env.serviceUrl, env.accessToken) let res = await svc.openMap({ mapid: env.exampleMapId, mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开 style: vjmap.openMapDarkStyle() }) if (res.error) { message.error(res.error) } let mapExtent = vjmap.GeoBounds.fromString(res.bounds); let prj = new vjmap.GeoProjection(mapExtent); let center = mapExtent.center(); let map = new vjmap.Map({ container: 'map', // container ID style: svc.rasterStyle(), center: prj.toLngLat(center), zoom: 2, renderWorldCopies: false }); map.attach(svc, prj); map.fitMapBounds(); map.addControl(new vjmap.NavigationControl()); map.addControl(new vjmap.MousePositionControl({showZoom: true})); // let blockNames = ["M_F13", "_AXISO"]; // 要查找的块名称列表 块名称可通过在云端管理平台中点击块实体,右侧属性中的blockname const handleQuery= async () => { let query = await svc.exprQueryFeature({ expr: `return gInFeatureType == 'AcDbBlockReference'`, fields: "objectid,blockname,layername,positon,rotate,scaleX,scaleY,scaleZ", // 为空的话,表达所有字段 geom: false, // 内存模式 useCache: true, limit: 1000000 // 数量大些,相当于查找所有的 }) if (query.error) { message.error(query.error) return } // 根据要过滤的块名称过滤结果 // let findBlockName = new Set(blockNames); let objectIds = new Set(); // 块对象的objectid值 let blockEntities = []; for(let i = 0; i < query.result.length; i++) { let blockname = query.result[i].blockname; // if (!findBlockName.has(blockname)) { // 如果不在要找的里面 // continue // } blockEntities.push(query.result[i]) objectIds.add(query.result[i].objectid) } // 下面根据所有块的objectid去条件查询下面块中所包含的具体子实体。这个数据通过内存查询查不到。只有通过几何处理把块分解成了一个个子实体了,通过条件查询去查 // 条件查询中同一个块实体的objectid都是为此块的objectid_开头的 // 实体id // 块objectid命名规则:块id_引用的块定义id1_引用的块定义id2(可能有多个)_实体id_#; // 表格命名规则:objectid命名规则:块id_引用的块定义id1_引用的块定义id2(可能有多个)_实体id_@; // 组objectid命名规则:$实体id_实体内元素索引$组id; // 构造sql条件,所有块objectid_开头的一次先查出来。提高效率 // 模糊查找可能like中的匹配符%查找。%符号用于在模式的前后定义通配符。like 'k%'查找以字母 'k' 结尾 let condtionIds = Array.from(objectIds).map(id => `objectid like '${id}_%' `).join(" or ") // 把所有符合条件的id用字符串连起来 let condQuery = await svc.conditionQueryFeature({ condition: condtionIds, // objectid符合上面的条件就可以。 fields: "objectid,area", geom: true, limit: 100000 //设置很大,相当于把所有的都查出来。不传的话,默认只能取100条 }) if (query.error) { message.error(query.error) return } let blockFeatures = [] for(let i = 0; i < condQuery.result.length; i++) { let item = condQuery.result[i]; blockFeatures.push(item) } // 查找所有块下面的相关的实体,找出面积最大的子实体 let showDatas = [] for(let block of blockEntities) { // 以块objectid_开头的都是块的子实体 let blockFeature = blockFeatures.filter(b => b.objectid.indexOf(block.objectid + "_") === 0) let maxArea = Math.max(...blockFeature.filter(b => b.area).map(d => d.area)) showDatas.push({ ...block, area: maxArea }) } console.table(showDatas) message.info(`查询到符合的记数条数:${blockEntities.length} 请按F12查看控制台具体数据`) }; handleQuery(); ``` --- # AI数据查询可视化 可在 [唯杰地图云端管理平台](https://vjmap.com/app/cloud/#/)中,打开地图。点击工具栏上面的`AI数据查询` 进入 `AI数据查询可视化查询工具` 示例地址: [https://vjmap.com/app/visual/#/query?mapid=sys_zp&version=v1&mapopenway=GeomRender&theme=dark](https://vjmap.com/app/visual/#/query?mapid=sys_zp&version=v1&mapopenway=GeomRender&theme=dark) ![image-20250303161206288](https://vjmap.com/blogimages/202503031612318.png) ![image-20250303161516621](https://vjmap.com/blogimages/202503031615674.png) ## 演示教程 ![](https://vjmap.com/blogimages/aiquery.gif) ![](https://vjmap.com/blogimages/queryvisiual.webp) ## 位置坐标与几何坐标区别 位置坐标是指实体本身的位置坐标,如文字,位置坐标是文字的基点坐标,圆的位置坐标坐标是圆心坐标,曲线位置坐标是控制点或拟合点坐标。 几何坐标是指实际渲染的坐标,如文字会渲染成多边形,是多多边形数组坐标。圆几何坐标会离散化成多段线坐标。曲线几何坐标也会会离散化成多段线坐标。对于没有线宽或线型的直线或多段线来说,位置坐标和几何坐标是一样的,如果有线宽时,几何坐标会返回绘制线的多边形对象,如果有线型时,几何坐标会包含绘制线型的所有实体对象。 ![image-20240926165328271](https://vjmap.com/blogimages/202409261653324.png) ![image-20250121160312168](https://vjmap.com/blogimages/202501211603362.png) --- # 查询示例(自动分割子图) 一键在Web端把CAD图自动分割成多张图纸并导出子图或图片 # 介绍 ​ 在实际中,一个CAD文件中往往存放多张图纸,有时需要这些图纸分开,单独保存或显示。以往的做法是在cad中人工进行处理。今天小编教您在web端一键把CAD图自动分割成多张图纸并能把每个子图导出成单独的dwg文件或保存成图片。 例如要处理的CAD原图为: ![image-20221019173929903](https://vjmap.com/blogimages/image-20221019173929903.png) 自动识别所有子图的结果为(所有子图的范围都被紫色颜色所高亮显示了): ![image-20221019174054674](https://vjmap.com/blogimages/image-20221019174054674.png) # 实现 ## 先上效果图 ![autosplitmap.gif](https://vjmap.com/blogimages/autosplitmap.gif) ## 原理介绍 ### 自动分割图纸算法 ##### 算法原理: 子图的特征为,外面有一个图框,如果能找出所有图中的图框,就能根据图框位置自动拆分出所有子图了。 而图框的最外面为矩形,同时这个图框矩形外面没有被其他矩形所包含了。 - (1)遍历图中所有的矩形,获取所有的矩形范围 - (2) 因为有时候矩形是由四条线所组成的,所以需要获取图中所有的水平线和垂直线,然后判断能否组成矩形 - (3)对所有获取的矩形进行判断,如果这个矩形没有被其他矩形所包含了,则以为是子图的图框。 ##### 在图中查找所有线段坐标代码 ```javascript // 在图中查找所有的直线段 const getMapLines = async () => { // 查找图中所有的直线,二三维多段线 let queryEntTypes = ['AcDbLine', 'AcDbPolyline', 'AcDb2dPolyline', 'AcDb3dPolyline']; let cond = queryEntTypes.map(t => `name='${getTypeNameById(t)}'`).join(' or '); // sql条件 let query = await svc.conditionQueryFeature({ condition: cond, // 只需要写sql语句where后面的条件内容,字段内容请参考文档"服务端条件查询和表达式查询" fields: "objectid,points,envelop", // 只要id,坐标 limit: 100000 //设置很大,相当于把所有的都查出来。不传的话,默认只能取100条 }); let result = query.result || []; result.forEach(rst => rst.envelop = map.getEnvelopBounds(rst.envelop)); return result; } ``` ##### 获取所有子图范围矩形代码 ```javascript // 得到一个图里面所有的矩形 function findAllRectInMap(lines) { let allRects = []; // 矩形(有可能是四条直线或者 一条多段线4个点(闭合),5个点(闭合)所组成 // 先查找一条多段线4个点(闭合),5个点(闭合)所组成的矩形 lines.forEach(e => { if (e.points == "") { return; } let points = e.points.split(";").map(p => vjmap.GeoPoint.fromString(p)); if (points[0].equals(points[points.length - 1])) { // 如果是首尾闭合,则把最后那个点去了 points.length = points.length - 1; } if (points.length != 4) return; // 如果不是四个点。则不是矩形 // 判断四个点是否构成矩形 // 先计算中点的位置, 然后再计算中点到四个点的距离是不是一样即可。 let cx = (points[0].x + points[1].x + points[2].x + points[3].x) / 4.0; let cy = (points[0].y + points[1].y + points[2].y + points[3].y) / 4.0; let center = vjmap.geoPoint([cx, cy]); let dist = center.distanceTo(points[0]); let isDistEqual = true; for(let k = 1; k < points.length; k++) { if(!vjmap.isZero(center.distanceTo(points[k]) - dist)) { isDistEqual = false; break; } } if (!isDistEqual) return false;//不是矩形 let rectObj = { bounds: e.envelop, // 直接用获取到的外包矩形 ents: [e.objectid] }; allRects.push(rectObj) }); // 再查询由四条直线所组成的矩形 // 首先找到所有符合的线,条件为:坐标两个点,横线或竖线 lines = lines.filter(e => { let points = e.points.split(";"); if (points.length !=2 ) return false; e.geoStart = vjmap.GeoPoint.fromString(points[0]); delete e.geoStart.z;// 不考虑z值 e.geoEnd = vjmap.GeoPoint.fromString(points[1]); delete e.geoEnd.z;// 不考虑z值 e.startPoint = e.geoStart.toString(); e.endPoint = e.geoEnd.toString(); if (e.startPoint == e.endPoint) { // 同一个点 return false; } let line = points.map(e=>vjmap.geoPoint(e.split(","))) let isVLine = vjmap.isZero(line[0].x - line[1].x);//竖线 let isHLine = vjmap.isZero(line[0].y - line[1].y);//横线 if (!(isVLine || isHLine)) return false; // 并且是横线或竖线 e.isHorzLine = isHLine; e.findFlag = false; return true } ) // 把所有的坐标点存进一个字典数组中 let coordPointMap = {} // 坐标点字典 let entMap = {} // 实体字典 for(let ln of lines) { // id与线实体相关联 entMap[ln.objectid] = ln; coordPointMap[ln.startPoint] = coordPointMap[ln.startPoint] || new Set() coordPointMap[ln.startPoint].add(ln.objectid) coordPointMap[ln.endPoint] = coordPointMap[ln.endPoint] || new Set() coordPointMap[ln.endPoint].add(ln.objectid) } for(let c in coordPointMap) { coordPointMap[c] = Array.from(coordPointMap[c]) } // 查找下一个线 const findNextLine = (ln, isStartPoint, nextIsHortLine) => { const pt = isStartPoint ? ln.startPoint : ln.endPoint const findLines = coordPointMap[pt]; if (!findLines) return null; //先查找id开头相近的。有可能是同一个块 let idx = findLines.findIndex( e => e != ln.objectid && e.substr(0, 3) == ln.objectid.substr(0, 3)); if(idx < 0) { idx = findLines.findIndex( e => e != ln.objectid); if(idx < 0) return null; } const findLn = entMap[findLines[idx]]; if (findLn.isHorzLine != nextIsHortLine) return null; // 线类型不一样 let isLnStartPoint = findLn.startPoint != pt return { findLine: findLn, isStartPoint: isLnStartPoint } }; // 下面找矩形 for(let ln of lines) { if (ln.isHorzLine) continue;//只找竖线 // 找两个点都有相连的线 let n1 = coordPointMap[ln.startPoint].length; let n2 = coordPointMap[ln.endPoint].length; if (ln.findFlag) continue; // 按链接关系一直找下去,从起始能到终点,说明是一个矩形 let nextLine1 = findNextLine(ln, true, true) if (!nextLine1) continue; let nextLine2 = findNextLine(nextLine1.findLine, nextLine1.isStartPoint, false) if (!nextLine2) continue; let nextLine3 = findNextLine(nextLine2.findLine, nextLine2.isStartPoint, true) if (!nextLine3) continue; let nextLine4 = findNextLine(nextLine3.findLine, nextLine3.isStartPoint, false) if (!nextLine4) continue; if (nextLine4.findLine.objectid == ln.objectid && nextLine4.isStartPoint == true) { // 成功了,可以是一个矩形了 ln.findFlag = true; nextLine1.findLine.findFlag = true; nextLine2.findLine.findFlag = true; nextLine3.findLine.findFlag = true; // 增加矩形对象 let strBounds = '[' + ln.startPoint + ',' + (nextLine2.isStartPoint ? nextLine2.findLine.startPoint : nextLine2.findLine.endPoint) + ']'; let rectObj = { bounds: vjmap.GeoBounds.fromString(strBounds), ents: [ln.objectid, nextLine1.findLine.objectid, nextLine2.findLine.objectid, nextLine3.findLine.objectid] }; allRects.push(rectObj) } } return allRects; } // 自动拆分子图,显示所有子图的范围 // 原理为:查找图中的所有矩形(包括由直线所组成的矩形),这个矩形没有被其他矩形所包含,则应为是一个子图的范围 const splitMap = async () => { message.info('请点击高亮的子图框,选择”保存成新的子图“或"保存成图片"') let lnRes = await getMapLines(); let allRects = findAllRectInMap(lnRes); // 在所有矩形中,只有没有被其他矩形所包含的,才以为是一个新的图的图框 let mapRects = []; for(let i = 0; i < allRects.length; i++) { let isContain = false; for(let j = 0; j < allRects.length; j++) { if (i == j) continue; // 如果是自己 // 判断矩形是否包含 if ( allRects[j].bounds.isContains(allRects[i].bounds)) { isContain = true; break; } } if (!isContain) { mapRects.push(allRects[i]); // 没有包含的,才以为是一个新的图的图框 } } } ``` ### 把子图保存为单独的dwg图 根据范围保存为单独的dwg图可以利用唯杰地图提供的组合图形的服务接口,其文档为 ```typescript /** * 组合新地图参数 */ export interface IComposeNewMap { /** 地图ID. */ mapid: string; /** 地图版本(为空时采用当前打开的地图版本). */ version?: string; /** 地图裁剪范围,范围如[x1,y1,x2,y2], 为空的话,表示不裁剪 */ clipbounds?: [number, number, number, number]; /** 选择是包含还是相交(默认false表示包含,true相交) */ selByCrossing?: boolean; /** 四参数(x偏移,y偏移,缩放,旋转弧度),可选,对坐标最后进行修正*/ fourParameter?: [number, number, number, number]; /** 要显示的图层名称,为空的时候,表示全部图层 */ layers?: string[]; /** 生新成图的图层名称前缀 */ layerPrefix?: string; /** 生新成图的图层名称后缀 */ layerSuffix?: string; /** 保存的文件名称,为空的时候,自根据参数自动生成 */ savefilename?: string; } ``` 其实现代码为 ```javascript const saveSubMapByBounds = async points => { let bounds = vjmap.GeoBounds.fromDataExtent(points); bounds = map.fromLngLat(bounds); let curMapParam = svc.currentMapParam(); let rsp = await svc.composeNewMap([ { mapid: curMapParam.mapid, version: curMapParam.version, clipbounds: bounds.toArray() // 要裁剪的范围 } ]) let url = `https://vjmap.com/app/cloud/#/upload?fileid=${rsp.fileid}&mapid=${rsp.fileid}&isNewMapId=true`; window.open(url); }; ``` ### 把子图导出为图片 ​ 根据范围导出为图片可以利用唯杰地图提供的WMS图形的服务接口,其文档为 ```typescript /** * wms服务url地址接口 */ export interface IWmsTileUrl { /** 地图ID(为空时采用当前打开的mapid), 为数组时表时同时请求多个. */ mapid?: string | string[]; /** 地图版本(为空时采用当前打开的地图版本). */ version?: string | string[]; /** 图层名称(为空时采用当前打开的地图图层名称). */ layers?: string | string[]; /** 范围,缺省{bbox-epsg-3857}. (如果要获取地图cad一个范围的wms数据无需任何坐标转换,将此范围填cad范围,srs,crs,mapbounds填为空).*/ bbox?: string; /** 当前坐标系,缺省(EPSG:3857). */ srs?: string; /** cad图的坐标系,为空的时候由元数据坐标系决定. */ crs?: string | string[]; /** 地理真实范围,如有值时,srs将不起作用 */ mapbounds?: string; /** 宽. */ width?: number; /** 高. */ height?: number; /** 是否透明. */ transparent?: boolean; /** 不透明时的背景颜色,默认为白色。格式必须为rgb(r,g,b)或rgba(r,g,b,a),a不透明应该是255. */ backgroundColor?: string; /** 四参数(x偏移,y偏移,缩放,旋转弧度),可选,对坐标最后进行修正*/ fourParameter?: string | string[]; /** 是否是矢量瓦片. */ mvt?: boolean; /** 是否考虑旋转,在不同坐标系中转换是需要考虑。默认自动考虑是否需要旋转. */ useImageRotate?: boolean; /** 旋转时图像处理算法. 1或2,默认自动选择(旋转时有用)*/ imageProcessAlg?: number; /** 当前互联网底图地图类型 WGS84(84坐标,如天地图,osm), GCJ02(火星坐标,如高德,腾讯地图), BD09LL(百度经纬度坐标,如百度地图), BD09MC(百度墨卡托米制坐标,如百度地图)*/ webMapType?: "WGS84" | "GCJ02" | "BD09LL" | "BD09MC"; } ``` 其实现代码为 ```javascript const exportMapPngByBoundsUrl = points => { let bounds = vjmap.GeoBounds.fromDataExtent(points); bounds = map.fromLngLat(bounds); // bounds = bounds.square(); // 保证为正方形 bounds = bounds.scale(1.01); // 稍大点,要不边框线有可能刚好看不见了 let pictureWidth = 1024 ;// 要导出的图片宽高 let wmsUrl = svc.wmsTileUrl({ width: pictureWidth, height: Math.round(pictureWidth * bounds.height() / bounds.width()), srs: "", bbox: bounds.toString(), transparent: false, backgroundColor: 'rgb(0,0,0)' }); window.open(wmsUrl); }; ``` ## 最后 我们通过短短的一二百行代码即实现了把cad图自动分割成多张子图,并成单独保存为新的dwg文件和导出图片,大大减轻了人工手动分图的工作量。可访问 [https://vjmap.com/demo/#/demo/map/service/22findsubmapsplit](https://vjmap.com/demo/#/demo/map/service/22findsubmapsplit) 在线体验。 --- # 查询示例(根据测点编号自动标注位置) 无需人工标注位置,一键在Web端CAD图上根据测点编号自动标注位置 # 需求分析 CAD图如下图所示 ![image-20220913205333681](https://vjmap.com/blogimages/image-20220913205333681.png) 在后台数据库中只有测点编号及测点值的信息,如下列列表 测点编号 测点类型 测点值 DZ129-2 类型1 值1 DZ128-2 类型1 值2 DZ127-2 类型1 值3 J28 类型2 值5 J26 类型2 值6 J27 类型2 值7 J29 类型2 值8 ....... 那怎么把上面的测点值在CAD图相应位置展示出来呢?如下图 ![](https://vjmap.com/blogimages/image-20220913210056973.png) 因为后台数据库中没有测点坐标信息,传统的方法一般是事先人工在地图上手工标注每个测点对应的位置,然后把位置信息保存至后台数据库。下次再从数据中获取位置信息和测点信息显示在地图上。这个方法的缺点也很明显,如果测点多时,人工标注每个测点对应的位置是一件费时费力的事情。 那么有没有好的方法根据图的信息一键计算出相应测点的位置呢? # 实现 ## 规律分析 要找出测点编号对应的地理位置就得知道每个类型的测点的规律或规则是什么。 如上例图的两种不同的测点: 测点类型1 ![image-20220913210838860](https://vjmap.com/blogimages/image-20220913210838860.png) 规律为:每个测点旁边有一个填充的三角形测点符号 测点类型2 ![image-20220913210939752](https://vjmap.com/blogimages/image-20220913210939752.png) 规律为:每个测点旁边有一个填充的红色的圆形符号 # 根据规律查找对应的图形符号 查找方法有多种,如图像识别,图形分析等。因为图像识别干扰因素多,同时性能慢。而图形分析速度快,准确率高。这里我们采用图形分析来查找。 步骤为: (1) 遍历图中所有实体类型 (2)通过实体类型和实体范围大小来确定是否满足条件 ## 根据图形符号搜索旁边对应的测点编号,并建立关联关系 步骤为: (1) 搜索符号旁边一定范围(如可设置为符号范围的几倍),设置旁边最近的文字 (2) 根据文字的内容规则去排查非测点编号名称(如此图中测点编号是以 DZ或J开头的,长度也有限制) (3) 在所有满足条件的测点编号名称中查找一个距离最近的做为最终的测点编号名称 (4) 把测点编号名称和图形符号的坐标建立关联关系。 # 关键代码实现 ```js const queryMapText = async () => { let query = await svc.conditionQueryFeature({ // 查询所有文字(包括单行文本,多行文本、块注记文字,属性文字) 具体类型数字参考文档"服务端条件查询和表达式查询-支持的cad实体类型" condition: `(name='12' or name='13' or name='26' or name='27') and LENGTH(s4) >= 2 and LENGTH(s4) <=12 and ( s4 like 'J%' or s4 like 'DZ%')`, fields: "", limit: 100000 // 全部查吧 }) if (query.error) { message.error(query.error) } else { query.result.forEach(rst => { rst.position = vjmap.GeoPoint.fromString(rst.location); // 获取坐标 }) return query.result; } } // 获取图中的文字信息 let mapAllText = await queryMapText(); console.log(mapAllText); // 查找所有hatch const queryHatch = async () => { let query = await svc.conditionQueryFeature({ condition: `(name='11') `, // 类型为hatch fields: "", includegeom: true, limit: 100000 // 全部查吧 }) if (query.error) { message.error(query.error) } else { query.result.forEach(rst => { rst.geoBounds = vjmap.GeoBounds.fromString(rst.bounds); // 获取范围 let w = rst.geoBounds.width(); let h = rst.geoBounds.height(); // 如果半径为2,则是圆,旁边是J_的 if (vjmap.isZero(w - 2, 0.1) && vjmap.isZero(h - 2, 0.1)) { rst.type = "circle_20"; } else { if (rst.geom.geometries[0].type == "Polygon" && rst.geom.geometries[0].coordinates[0].length == 5) { // 如果是三角形填充 if (vjmap.isZero(w - 2.5, 1) && vjmap.isZero(h - 2.5, 1)) { rst.type = "triangle"; } } } }) return query.result; } } // 获取图中的文字信息 let mapAllCircleHatch = await queryHatch(); console.log(mapAllCircleHatch); // 圆旁边是文字J开头的 let dataCircleTextG = mapAllCircleHatch.filter(rst => rst.type == "circle_28") dataCircleTextG.forEach(circle => { // 查找距离最近的一个以J开头的文字 let texts = mapAllText.filter(t => t.text.substr(0, 1) == "J") let findTexts = texts.filter(t => t.position.distanceTo(circle.geoBounds.center()) <= 2.8 * 3) // 查找符合条件的文字 // 找一个距离最近的 if (findTexts.length > 0) { findTexts = findTexts.sort((a, b) => a.position.distanceTo(circle.geoBounds.center()) - b.position.distanceTo(circle.geoBounds.center()) ) drawDatas.push({ point: circle.geoBounds.center(), name: findTexts[0].text, color: "#0f0" }) } }) // 三角形旁边是文字DZ开头的 let dataTriangleTextDz = mapAllCircleHatch.filter(rst => rst.type == "triangle") dataTriangleTextDz.forEach(tri => { // 查找距离最近的一个以DZ开头的文字 let texts = mapAllText.filter(t => t.text.substr(0, 2) == "DZ") let findTexts = texts.filter(t => t.position.distanceTo(tri.geoBounds.center()) <= 2.8 * 3) // 查找符合条件的文字 // 找一个距离最近的 if (findTexts.length > 0) { findTexts = findTexts.sort((a, b) => a.position.distanceTo(tri.geoBounds.center()) - b.position.distanceTo(tri.geoBounds.center()) ) drawDatas.push({ point: tri.geoBounds.center(), name: findTexts[0].text, color: "#f0f" }) } }) console.log(drawDatas) // 把数据显示到图上 let symbols; const showDataInMap = (datas)=> { let geoDatas = [] for(let i = 0; i < datas.length; i++) { const pt = datas[i].point; const data = { point: map.toLngLat(pt), properties: { name: datas[i].name, textColor: datas[i].color } } geoDatas.push(data); } if (symbols) { symbols.remove();// 如果有先删除了 } symbols = new vjmap.Symbol({ data: geoDatas, iconImage: "stretchTextBackImg", iconAnchor: "bottom", iconOpacity: 0.5, iconOffset: [-2, -10], textTranslate: [-2, -6], textAnchor: "bottom", textField: ['get', 'name'], textFont: ['Arial Unicode MS Regular'], textSize: 14, textColor: ['get', 'textColor'], iconTextFit: "both", iconAllowOverlap: true, textAllowOverlap: true }); symbols.addTo(map); } showDataInMap(drawDatas); ``` # 最效效果 ![image-20220913212244279](https://vjmap.com/blogimages/image-20220913212244279.png) 上面的案例代码已开源,出于对用户数据保密的需要,案例代码用唯杰地图官网的示例图进行了替换,思路一致。访问 [https://vjmap.com/demo/#/demo/map/service/22findtextbyrules](https://vjmap.com/demo/#/demo/map/service/22findtextbyrules) ,查看效果和代码即可。 --- # 查询示例(自动识别CAD图纸提取信息) 前端自动识别CAD图纸提取信息方法总结 # 前言 CAD图纸自动识别和提取信息具有许多意义,包括以下几个方面: 1. 提高工作效率:传统上,对于大量的CAD图纸,人工识别和提取信息是一项耗时且繁琐的任务。通过自动化这一过程,可以大大提高工作效率,节省时间和人力资源。 2. 减少错误和精度提升:人工处理CAD图纸容易出现错误,例如错读数字或忽略细节。自动识别可以减少这些人为错误,并提高数据提取的准确性和精度。 3. 快速获取信息:对于需要从大量CAD图纸中获取特定信息的项目或任务来说,自动识别可以快速提取所需的数据,而无需人工逐个查找和处理。 4. 数据整合和分析:自动提取的CAD图纸信息可以与其他系统或软件集成,实现数据的整合和分析。这样,可以更好地理解和利用CAD数据,为决策和规划提供支持。 通过CAD图纸自动识别和提取信息有助于提高效率、快速自动的获取数据信息及提高获取信息的质量,那么怎么样才能在前端自动识别和提取CAD图纸信息呢? # 思路 以下图中的一个停车场的CAD图纸为例,怎么样自动识别CAD图所有停车位,获取每个停车位的位置及所有停车位个数呢? ![image-20230528175510120](https://vjmap.com/blogimages/image-20230528175510120.png) 一般识别的方法和流程为: - 观察CAD图纸:仔细查看CAD图纸,查找可能代表停车位的图形或符号。这可能是表示一些特定图标、标识或标记。例如此图中停车位都是一个矩形 - 检查图例和标注:CAD图纸通常包含图例或标注,用于解释不同元素的含义。检查图例和标注,看是否提供了关于要识别的说明或标识。例如此图中矩形的里面都包含了停车线的一个符号。![image-20230528180048709](https://vjmap.com/blogimages/image-20230528180048709.png) - 使用CAD的选择和查询功能:利用CAD选择和查询图形的功能,使您能够选择图纸上的特定对象并查看其属性。使用这些工具选择可能的工位对象,并查看其属性以获取更多信息。 - 检查图层和图纸结构:CAD图纸通常使用不同的图层来组织不同类型的元素。查看图层列表,并查找与工位相关的图层。如果有多个图层包含工位信息,您可以逐个查看这些图层以找到工位对象。 - 参考CAD标准和惯例:CAD绘图通常遵循一些标准和惯例,其中包括特定的符号和图形表示要识别的内容。 对于一些不能通过图形识别出来的CAD图,可以利用图像识别的方法。例如 - 图像处理和模式识别:使用图像处理技术,例如边缘检测、轮廓提取和形状识别,来分析CAD图像中的对象。您可以使用计算机视觉库,如OpenCV,来实现这些功能。 - 机器学习和深度学习:利用机器学习和深度学习算法,您可以训练模型来自动识别CAD图像中的对象。这种方法涉及将大量标记CAD图像用作训练数据,然后使用这些数据来训练分类器或卷积神经网络(CNN)。训练完成后,您可以将新的CAD图像输入已训练好的模型,并让其自动预测识别对象的位置。 以上面的CAD停车场CAD图为例,我们可以通过以下步骤来识别来自动识别出CAD图中所有的停车位: (1)通过观察,我们发现所有的停车位都是”0"图层,并且实体为类型为多段线"AcDbPolyline" ![image-20230528181819726](https://vjmap.com/blogimages/image-20230528181819726.png) (2) 我们在前端通过CAD数据查询,查询出”0“图层的所有多段线。结果如下 ![image-20230528182017976](https://vjmap.com/blogimages/image-20230528182017976.png) (3)通过查询到的数据我们发现,我们把所有停车位都找出来了。但是查到的数据中还包含我们不想要的数据(停车位中间的停车线也查询出来了)。所以我们需要对查询的数据做下过滤出线为矩形形状的线(五个点) ![image-20230528182353001](https://vjmap.com/blogimages/image-20230528182353001.png) (4) 这样,我们就把所有停车位都自动找出来了。我们可以获取停车位坐标的中心点,加个车符号,看下效果。效果如下: ![image-20230528182558689](https://vjmap.com/blogimages/image-20230528182558689.png) 上面示例的体验地址和视频教程如下 [https://vjmap.com/app/visual/#/edit?key=sys_be517afb&isLocal=false](https://vjmap.com/app/visual/#/edit?key=sys_be517afb&isLocal=false) [唯杰地图可视化之停车场路径规划](https://mp.weixin.qq.com/s?__biz=MzkyOTMyMzAxMw==&mid=2247483910&idx=1&sn=79a94bfe1401ab3f522148a88231d383&chksm=c20a036cf57d8a7a3cfa43272e4805dd8f4707a9bbdc2a07133d59429924c9b24125421000c3#rd) [https://www.bilibili.com/video/BV1oX4y1r7L3/](https://www.bilibili.com/video/BV1oX4y1r7L3/) # 前端CAD图纸自动识别实现总结 ## 方法一:自动通过要识别对象的CAD属性数据来识别 应用场景: 数据很有规律,直接通过观察CAD图中的对象的属性如`实体的图层`、`颜色`、`实体类型`、`面积属性`、`填充符号`、`是否是同一个块`等就可以自动识别出要识别的对象。 以上面的停车场CAD为例,我们再仔细观察,看下停车位的面积大小是一样的,如下图 ![image-20230528184240059](https://vjmap.com/blogimages/image-20230528184240059.png) 我们可以在查询时,可以直接通过`“实体类型”为多段线`、`“实体图层”为“0`,`“实体面积“为12720000`来直接查询出所有的停车位数据 ## 方法二: 通过CAD属性数据和相关业务规则来识别 应用场景: 对于不能直接通过属性数据来识别出的对象,如果在图上有相关联的可以识别的对象,可以先自动识别出好识别的对象,再利用业务规则识别出要真正需要识别的对象。 举例来说,以下图为例,我们想要识别出圆圈里面的所有标注的内容 ![image-20230528184918601](https://vjmap.com/blogimages/image-20230528184918601.png) 如果我们直接识别图有所有的文字的时候,会有很多无关的文字,过滤不出真正想要的文字标识。 而在实际业务中,这个文字在圆里面的。所以,我们第一步,可以通过查询出图中所有同样大小的圆,确定圆的坐标和半径后,再查找圆内所有的文字就是我们所需要的文字标注了。效果如下: ![image-20230528185257507](https://vjmap.com/blogimages/image-20230528185257507.png) 在线体验和源码地址: [https://vjmap.com/demo/#/demo/map/service/22findtextbyrules](https://vjmap.com/demo/#/demo/map/service/22findtextbyrules) ## 方法三: 通过业务逻辑来识别 应用场景:对于一些特别复杂的场景,需要通过写相关的业务逻辑代码来实现。 以一键在Web端把CAD图自动分割成多张图纸并导出子图或图片为例,这需要通过业务逻辑自动把图中所有的子图查找出来。 ![image-20230528185606520](https://vjmap.com/blogimages/image-20230528185606520.png) 自动分割图纸算法原理: 子图的特征为,外面有一个图框,如果能找出所有图中的图框,就能根据图框位置自动拆分出所有子图了。 而图框的最外面为矩形,同时这个图框矩形外面没有被其他矩形所包含了。 - (1)遍历图中所有的矩形,获取所有的矩形范围 - (2) 因为有时候矩形是由四条线所组成的,所以需要获取图中所有的水平线和垂直线,然后判断能否组成矩形 - (3)对所有获取的矩形进行判断,如果这个矩形没有被其他矩形所包含了,则以为是子图的图框。 这个之前的博文进行了详细的介绍,可参考原文: [一键在Web端把CAD图自动分割成多张图纸并导出子图或图片](https://www.cnblogs.com/vjmap/p/16807655.html) 在线体验和源码地址:[https://vjmap.com/demo/#/demo/map/service/22findsubmapsplit](https://vjmap.com/demo/#/demo/map/service/22findsubmapsplit) ## 方法四:通过图像相似性来识别 应用场景:对于无法或不方便通过图形查询坐标及属性方法来进行识别的对象,可以利用图像的相似性来进行识别。 图像相似性算法是指用于比较和判断两个或多个图像之间相似度的算法。下面列举了一些常见的图像相似性算法: 1. 感知哈希算法(Perceptual Hashing):该算法通过计算图像的哈希值,将图像转化为一个固定长度的二进制串,然后通过比较哈希值的差异来评估图像的相似度。 2. 结构相似性算法(Structural Similarity):结构相似性算法基于人眼感知的原理,通过比较图像的结构、纹理和亮度等特征,给出一个相似度的评分。 3. 均值哈希算法(Average Hashing):均值哈希算法通过将图像缩小为固定大小的低分辨率图像,并计算图像的平均像素值,然后将每个像素与平均值进行比较,生成一个二进制串作为图像的哈希值,通过比较哈希值的差异来评估图像的相似度。 4. 欧氏距离算法(Euclidean Distance):欧氏距离算法通过计算两个图像像素之间的欧氏距离来评估它们的相似度。较小的距离表示两个图像更相似。 5. 余弦相似度算法(Cosine Similarity):余弦相似度算法通过计算两个图像的向量之间的夹角余弦值来评估它们的相似度。较大的余弦值表示两个图像更相似。 6. 归一化交叉相关算法(Normalized Cross-Correlation):归一化交叉相关算法通过计算两个图像之间的交叉相关性来评估它们的相似度。较大的相关性表示两个图像更相似。 具体流程为: (1)通过提供的WMS服务把图形数据转化为图像数据 (2) 对图像数据进行相似性判断 (3)把判断结果的像素坐标数据转化为图形坐标数据进行显示 以下图为例 ![image-20230528190249751](https://vjmap.com/blogimages/image-20230528190249751.png) 如果图中的这个符号是同一个块的话,我们可以采用图形中判断是否是同一个块来自动识别。但如果绘制不规范,整个对象不是一个块时,就不太方便通过属性数据来自动识别了。这时候我们可以采用图像识别的方法,效果如下: ![image-20230528190519847](https://vjmap.com/blogimages/image-20230528190519847.png) 在线体验和源码地址:[https://vjmap.com/demo/#/demo/map/service/32mapmatchobject](https://vjmap.com/demo/#/demo/map/service/32mapmatchobject) ## 方法五: 通过AI机器深度学习来识别 应用场景: 对于一些复杂的对象,大小不太一样,旋转、缩放也不太一致的复杂的对象,这时候可以用深度学习自己去训练模型,用AI的方式来进行识别。 实现过程同方法四, 通过提供的WMS服务把图形数据转化为图像数据,再把图像在深度学习框架中进行目标检测,获取相应的坐标数据进行预测显示。 因为深度学习框架我们没有进行封装,需要客户自行实现,这里不再举例。 # 总结 CAD图纸自动识别和提取信息是一个复杂的任务,涉及CAD文件解析、规律总结、业务理解、图像处理、模式识别等领域的知识。您可能需要结合多种技术和方法,根据具体情况进行调整和改进。 --- # 查询示例(自动提取图中所有表格数据) 在CAD图中自动识别并导出表格数据,是相关领域数据处理的重要需求。由于CAD图形并不像传统的电子表格那样具备明确的行列关系,表格常以线条和文本形式存在,手动提取不仅费时费力,还容易出错。如何通过自动化工具通过图形解析快速、高效地识别表格结构,提取数据并导出至Excel、CSV等格式,已经成为提高工作效率的关键。以下是几种常见的方法: ## 常用方法 ### 1. 基于图形对象解析的方法 在CAD图中,表格通常由水平和垂直的直线或其他几何图形构成,其核心思想是通过解析这些图形对象,推断表格的结构。 主要步骤: - **对象提取**:使用相关工具提取CAD文件中的几何对象。通常,水平和垂直线的组合会形成表格的边框,文本对象则表示表格的内容。 - **结构识别**:通过检测平行和垂直直线的排列模式来确定行列分布。通常可以通过计算直线之间的间距及其交叉点,判断其是否组成了表格的单元格。 - **文字匹配**:识别并匹配每个单元格内的文字,通常通过坐标位置和表格框架的对应关系来确定文本的归属。 优点: - 对表格结构明确、规则的CAD图具有较高的准确性。 - 适用于可编程的场景,能针对大批量的CAD文件进行自动处理。 局限性: - 处理复杂、不规则表格时,可能难以自动识别正确的行列关系。 - 需要一定的编程和CAD知识。 ### 2. 使用AutoCAD内置的表格对象(Table Object) 自AutoCAD 2005版本起,CAD引入了专用的表格对象(Table),这是识别和导出表格数据的另一条有效路径。AutoCAD的表格对象类似于Excel中的表格,具备明确的行、列结构及单元格内容。 主要步骤: - **直接读取表格对象**:通过AutoCAD的API(如AutoCAD .NET API或ObjectARX),可以直接读取表格对象,获取行、列信息,以及每个单元格的文本、数值等内容。 - **数据提取与导出**:基于这些API,用户可以轻松将表格内容导出为CSV、Excel等常见的表格文件格式,便于后续的数据分析和处理。 优点: - 操作简便,表格对象的结构化数据便于直接提取。 - 对于表格对象较为标准的CAD文件,识别精度高。 局限性: - 仅适用于含有AutoCAD内置表格对象的图纸,无法处理手动绘制的表格。 ### 3. 使用OCR(光学字符识别)技术 对于某些复杂或非标准的表格,特别是手动绘制的表格,图形对象的解析可能存在较大挑战。在这种情况下,OCR技术是一种有效的替代方案。OCR能够从图像中识别出文字,并通过算法还原表格结构。 主要步骤: - **图像转换**:首先,将CAD图导出为高分辨率的图像格式(如PNG、TIFF)。这些图像能更好地呈现表格中的文字和边框。 - **OCR识别**:使用OCR软件对图像中的文字和表格线条进行识别。部分高级OCR工具可以自动识别表格行列,恢复数据的矩阵结构。 - **数据导出**:OCR工具通常可以直接将识别到的表格导出为Excel或CSV格式。 优点: - 对于复杂的手工绘制表格,OCR技术能有效识别图像中的内容,弥补了传统CAD对象解析的不足。 局限性: - OCR技术对图像质量有较高的要求,低分辨率的图纸可能导致识别精度下降。 - 部分表格中的内容(如特殊符号、格式)可能无法准确识别。 ### 总结 利用AutoCAD内置表格对象无法处理用线条绘制出的表格的情况;OCR技术适用于图像,对于复杂的CAD图纸处理,由于干扰因素比较多,精度很差。**基于图形对象解析的方法** ,尽管算法复杂,但如果能实现,则性能和效果是最佳的,也更具有通用性,能确保数据的准确性和完整性。 ## 图形对象解析解析表格算法介绍 通过分析CAD文件中的几何图形(如直线、文字对象等),判断它们是否组成了表格结构,并从中提取数据。以下是实现步骤: ### 1. **CAD文件读取与解析** 首先,使用唯杰地图的数据查询接口 [唯杰地图数据查询](https://vjmap.com/guide/dataQuery.html),查询图中所有的直线、多段线、二维多段线、三维多段线、单行文本、多行文本对象。如果使用内存直接打开的话,可以使用[后台表达式查询](https://vjmap.com/guide/exprQuery.html)图中所有的对象实体。 ### 2. **几何对象的提取与分类** 典型的表格是由水平和垂直的直线构成,因此首要任务是筛选出这些直线对象。对于较复杂的图形,可能还需要筛选出多段线或矩形。如果多段线来表示单元格边框,则需要先将其分解为简单的线段。 在提取直线时,需获取其几何参数,包括起点和终点的坐标。对于每个直线对象,记录其方向(水平或垂直)、长度、位置等参数。此外,提取表格内的文本对象,通过文本的插入点坐标与表格框架的相对位置,确定其所在的单元格。 ### 3. **表格框架的识别** 表格的框架通常由交叉的水平线和垂直线组成。识别表格框架的核心在于检测这些直线是否形成了规则的行列布局。具体步骤如下: - **水平线和垂直线的区分**:通过计算直线的斜率或起点、终点的相对位置,判定直线是水平线还是垂直线。 - **线段聚类**:将相互平行、间隔距离接近的线段分为一组。例如,所有水平线可按Y坐标排序,将Y坐标接近且在容差范围内的线段归为同一行。同样地,对垂直线按X坐标进行聚类,形成列结构。 - **交点计算**:通过水平线和垂直线的交点,确定表格的每个单元格的边界。这些交点是表格的关键节点,构成了单元格的顶点。 通过查看相关论文,在一篇<<表格识别中的算法改进>>论文中介绍的通过特征点识别表格边框帮助很大。 ![image-20241017141032865](https://vjmap.com/blogimages/202410171410009.png) ![image-20241017141139346](https://vjmap.com/blogimages/202410171411387.png) ### 4. **单元格结构的提取** 在识别出表格的框架后,可以通过水平线和垂直线的交点来确定每个单元格的边界。每个单元格的范围由相邻水平线和垂直线的交点定义。需要特别处理线条缺失的情况,例如某些手动绘制的表格可能不完整,部分边界线缺失,这时需要通过推测和补全技术来弥补边界的缺失。 ### 5. **文本与单元格的关联** 表格中的核心数据通常以文本形式存在。文本对象通常具有插入点坐标、字体信息以及文本内容。为了将文本与表格中的单元格正确关联,需要将每个文本对象的插入点与之前识别出的单元格范围进行比较,确定其所属的单元格。可以采用以下方法: - **坐标匹配**:通过检查文本插入点是否位于某个单元格的边界内,将该文本归属到该单元格中。由于文本插入点可能位于单元格的不同位置(如左下角或中心),需根据文本对齐方式进行调整。 - **多文本处理**:如果一个单元格中包含多个文本对象,则需要将这些文本内容合并为单一数据,或根据特定规则进行分隔和处理。 ### 6. **合并单元格的识别与处理** 合并单元格是表格中常见的复杂情况。通过检查相邻单元格是否共享相同的边界线,可以识别合并单元格。具体而言,如果某些行或列的线条缺失且相邻单元格的内容一致,则可以判断它们是合并单元格。处理时,将合并单元格的内容统一保存,并调整其输出形式,以确保数据的一致性。 ### 7. **优化与异常处理** 在表格解析的过程中,还需考虑一些可能的异常情况: - **线条断裂或不连续**:CAD图中的线条可能由于绘制精度问题而存在细微的断裂,识别时需要设置一定的容差值来处理这些异常情况。 - **复杂图形混淆**:有时图纸中会包含其他几何形状或注释,它们可能干扰表格的识别。通过采取对象筛选的方法,仅处理与表格相关的几何对象。 ## 实现效果 在[唯杰地图管理云平台](https://vjmap.com/app/cloud) 上传要提取表格的CAD图形,成功打开后,点击“更多功能”里面的“自动提取图中所有表格” ![extractTable.gif](https://vjmap.com/blogimages/extractTable.gif) 对于一些复杂的图形或有误差的图形,可适当的增大误差值或“选择区域手动提取表格数据” ![extractTable2.gif](https://vjmap.com/blogimages/extractTable2.gif) 提出的数据是后台返回表格数据的`json`格式,示例中用`table`组件做了展示。如果需要把数据导出为excel或csv格式,可以基于此示例(此示例已开源)完善下即可。 在`唯杰地图`中`表格特征点`用如下值表示,可以设置界面里面点击调试输出“位置标识”进行查看调试。 ![image-20241019210656737](https://vjmap.com/blogimages/202410192107825.png) ![image-20241019211021600](https://vjmap.com/blogimages/202410192110660.png) 接口文档: ```ts /** * 自动提取图中表格参数 */ export interface IExtractTable { /** 地图ID. */ mapid?: string; /** 地图版本(为空时采用当前打开的地图版本). */ version?: string; /** 图层样式名.为空时,将由选择的实体的图层来决定 */ layer?: string; /** 范围 [x1,y1,x2,y1] 为空表示此个图,否则为指定区域查询提取. */ bounds?: string; /** 查询条件(默认为所有线). */ condition?: string; /** 是否输出调试数据. */ debug?: boolean; /** 小数点计算精度. 正数表示精度为小数点后几位,负数表示精度为小数点前几位 默认为4 */ digit?: number; /** 误差值. 正数表示距离小于这个误差值就以为是直线(为零的话表示自动求误差值),负数表示自动获取的误差值比例倍数大小 默认为0自动*/ tol?: number; /** 表格边框最少点. 如果表格边框点数小于这个数将排除此表格. 默认 12*/ tableEdgeMinPoint?: number; /** 表格文本最少数. 如果表格中所有文本个数小于这个数将排除此表格. 默认 4*/ tableTextMinCount?: number; /** 单元格最大面积比.单元格面积占整体表格面积的比例不能超过这值,超过了此值,将不获取内容 . 默认 90*/ cellMaxArea?: number; /** 空值所占最小比例. 空值占所有表格的比例超过此值将排除此表格. 默认 90*/ cellEmptyRatio?: number; /** 单元格最大个数 允许的单元格最大个数,超过这值将排除此表格 . 默认 100000 */ tableMaxCellCount?: number; /** 线段中允许折线段数 默认情况下只提取水平线或垂直线,如果要允许折线,设置允许线段中折线的段数(非水平和垂直线段) . 默认 0 */ noHvLineSegCount?: number; /** 表格数据允许重复. 表格数据允许重复会尽可能搜索多的表格,但同一份数据可能在不同的表格中 默认false */ seachTableMost?: boolean; /** 查找子图范围 默认false */ findChildMapRects?: boolean; } /** * 自动提交图中的表格 * @param param 参数 * @return {Promise} */ async cmdExtractTable(param: IExtractTable) ``` 可点击 [https://vjmap.com/app/cloud/#/map/sys_table?version=v1&mapopenway=GeomRender&vector=false](https://vjmap.com/app/cloud/#/map/sys_table?version=v1&mapopenway=GeomRender&vector=false) 在线体验 ## 扩展应用 在之前介绍的[自动分割子图](https://vjmap.com/guide/queryCase1.html)的算法是在前端实现的,如果把上面提取表格的算法稍改进下,也可以应用于获取所有子图范围。 可需要把上面接口文档的中的`findChildMapRects`设置为`true`,即可获取所有子图范围。再调用拆分子图的功能即可。 ![splitmap.gif](https://vjmap.com/blogimages/splitmap.gif) ```javascript /** * 拆分子图参数 */ export interface ISplitChildMaps { /** 地图ID. */ mapid?: string; /** 文件ID. (有mapid,优先使用mapid,没有mapid,使用fileid) */ fileid?: string; /** 地图版本(为空时采用当前打开的地图版本). */ version?: string; /** 每个子图拆分后是否全图. 默认false */ isFullExtent?: boolean; /** 子图范围数组. ["x1,y1,x2,y2", "x1,y1,x2,y2", ...] */ clipBounds: string[]; /** 方法 cloneObjects 通过深度克隆实体,效率最快(默认), cloneDb 通过克隆文档数据库,效率较快;cloneMap 通过克隆图,效率较慢,但最能保持原样*/ method?: "cloneObjects" | "cloneDb" | "cloneMap"; /** 是否启动新进程处理(不影响主进程,看初始速度慢些) 默认true */ startNewProcess?: boolean; } /** * 拆分子图 * @param param 参数 * @return {Promise} */ async cmdSplitChildMaps(param: ISplitChildMaps) ``` 拆分子图在线示例 [https://vjmap.com/demo/#/demo/map/service/22findsubmapsplit2](https://vjmap.com/demo/#/demo/map/service/22findsubmapsplit2) --- # 查询示例(自动提取中心线如墙体道路) ## 前言 CAD图纸中的中心线提取是一项关键的几何信息简化技术,在不同领域具有重要应用价值。在建筑类图纸中,通过算法提取墙体的中心线,这种简化后的中心线便于进行空间分析、路径规划或BIM模型重构。对于能源领域,巷道中心线的提取更为关键,它能准确反映地下通道的走向、转折点和连接关系,为设计、逃生路线规划等提供基础框架。本质上,中心线提取实现了从具象图形到抽象拓扑的转换,是CAD数据处理中不可或缺的智能化预处理步骤。 CAD图纸中心线的提取技术,需要设计人员需要手动描画中心线,不仅耗时耗力,而且容易因人为误差影响精度。相比人工操作,自动提取中心线不仅速度更快,还能减少人为错误,确保数据一致性,并支持批量处理大规模图纸。这一技术进步极大降低了人工成本,推动了CAD数据处理向智能化、高效化方向发展。 ## 效果展示 提取墙体中心线 ![](https://vjmap.com/blogimages/center_wall.gif) 提取巷道中心线 ![](https://vjmap.com/blogimages/center_tube.gif) 视频教程: https://www.bilibili.com/video/BV14coEY5EdR/ ## 基本操作 在[唯杰地图管理云平台](https://vjmap.com/app/cloud) 上传要提取中心线的CAD图形,成功打开后,点击“更多功能”里面的“自动提取中心线(如墙体、道路)” ![image-20250414152232758](https://vjmap.com/blogimages/202504141522806.png) ### 界面控制 - 窗口可拖动:通过标题栏拖动整个设置窗口 - 底图透明度:使用滑块调节底图透明度(0-1之间) - 关闭按钮:点击右上角"关闭"按钮关闭设置窗口 ## 中心线提取 ## 参数设置 ### 平行线距离设置 1. 距离对设置 - 可添加多个距离对 - 每个距离对包含最小距离和最大距离 - 使用"拾距"按钮可以直接从图上测量距离 2. 自动设置 - 点击"自动设置数值参数中其他距离项"可自动计算其他距离参数 **这步设置很重要,不同的图纸设置值不同,需要根据实际图纸进行设置。** 因为图中可能存在不同的平行线宽,如下图所示,墙体厚度不同,这时候需要增加不同的距离对。 ![image-20250414153556060](https://vjmap.com/blogimages/202504141535109.png) 修改完距离选项后,需点击 `自动设置数值参数中其他距离项` 自动计算其他距离参数 ### 数值参数 1. 距离参数 - 中心线段能合并的最大距离 (在此范围之内的中心线将合并成一条中心线) - 允许的线段断开距离 (查找平行线时,如果平行线有断开时,断开距离小于此值将进行合并) - 中心线段允许的最小距离 (如果最后计算出的中心线距离小于此值将被排除) - 共线容差距离值 (判断是否共线的容差距离值) - 去除重复点容差值 (去除重复点的容差距离值) 2. 精度参数 - 精确到小数点后几位(0-10位) - 双线都重叠的最小比例(0-1)(用于判断平行线是否有中心线) - 判断平行线斜率角度误差(0-90度)(平行线斜率误差角度) - 合并中心线斜率角度误差(0-90度)(合并中心线时斜率误差角度) ### 处理选项 - 是否连接设置允许间隔范围内的线 - 是否处理合并端点 - 是否排除小于最小距离的线 - 是否把相连的线合成一条 - 中心线是否以GeoJson格式返回 (用于调试数据) - 是否显示平行线坐标 (用于调试数据) - 是否显示提取的线坐标 (用于调试数据) ### 自动提取中心线 1. 点击"自动提取中心线"按钮 2. 系统会自动分析当前视图范围内的图形并提取中心线 ### 手动矩形提取 1. 点击"手动矩形提取中线"按钮 2. 在地图上绘制一个矩形区域 3. 系统会提取该区域内的中心线 ### 多边形提取 1. 点击"多边形提取中线"按钮 2. 在地图上绘制一个多边形区域 3. 系统会提取该区域内的中心线 **注:如果参数设置对的情况下,因为图形其他元素影响导致自动提取不对时,可采取以下办法:** (1) 点击工具栏中的图层菜单 ,“只显示指定图层”, 可自动识别当前图层中的数据,防止其他数据干扰。 (2) 也可以通过手动矩形或多边形选择指定区域的图形数据进行提取。 ## 工具功能 ### 中心线编辑工具 - 显示/隐藏中心线:点击对应按钮切换中心线的显示状态 - 编辑/浏览模式切换:切换中心线的编辑和浏览模式 - 编辑模式下: 可点击中心线,对坐标进行拖动编辑,或按`Delete`键进行删除 ![image-20250414161148669](https://vjmap.com/blogimages/202504141611712.png) - 浏览模式下: 鼠标移动到中心线上时,会显示当前中心线的属性数据,点击线,会弹出此线的详细坐标数据和属性数据,可进行修改编辑 ![image-20250414161514529](https://vjmap.com/blogimages/202504141615564.png) ​ 如可在这里输入中心线的`名称值`,或者直接修改坐标值。 ### 手动采集 - 手动采集线:点击按钮后在地图上采集二维线 - 手动采集三维线:点击按钮后在地图上采集三维线 如果没有自动提取到的数据,可以用手动采集的方式去进行采集。手动采集三维线时,会根据当前点的图纸内容自动给当前点赋值高程(如果没有找到相关高程值可手动输入) ![image-20250414162038735](https://vjmap.com/blogimages/202504141620779.png) ### 中心线编辑 - 拆分中心线:选择一条中心线,指定拆分点进行拆分 ![image-20250414162650071](https://vjmap.com/blogimages/202504141626114.png) - 合并中心线:选择两条中心线进行合并 合并过程与拆分刚好相反,选择两条中心线,会自动合并成一条中心线 - 查看中心线数据:查看当前中心线的详细数据 查看中心线既可查看当前所有中心线数据,也可在这里直接修改中心线数据。 **如可把当前处理好的中心线这里复制出来,下次的时候,再把数据拷贝到这里进行还原。** ![image-20250414162757031](https://vjmap.com/blogimages/202504141627076.png) ## 坐标高程处理 如果需要给中心线的每个点赋值高程值时,需要先`自动找到图中所有高程注记`或 `手动增加高程注记` ,然后再根据这些点的高程值给中心线的每个点进行插值赋值。 ### 高程注记处理 1. 自动找高程注记 - 点击"自动找高程注记"按钮 - 系统会自动识别图中的高程注记 ![image-20250414163522616](https://vjmap.com/blogimages/202504141635665.png) 自动找高程注记的流程为:会查找所有符合半径大小的圆,再搜索圆旁边的高程值。 搜索到的高程值旁边会加上相同颜色的一个填充圆点。如搜索到的高程值1146.983是绿色的,查找的文字"1146.983"上面会显示绿色的小填充圆点。这样就能**直观的看到自动找到的高程值来源于图中的哪个文字**,**如果不对的话,可以点击高程注记右上角的关闭按钮来进行删除**。 2. 手动增加高程注记 - 点击"手动增加高程注记"按钮 - 在地图上点击位置添加高程值 对于没有自动找到的高程值,可以手动在一起位置增加对应的高程值。手动增加的高程值会以蓝色文字进行显示,以区别于自动搜索到的高程值。 ![image-20250414165344400](https://vjmap.com/blogimages/202504141653456.png) 3. 高程注记管理 - 删除高程注记:删除所有自动识别的高程注记 - 删除手动高程注记:删除手动添加的高程注记 - 中心线高程自动赋值:根据高程注记自动为中心线赋值 中心线高程自动赋值是根据`自动找到图中所有高程注记`和 `手动增加高程注记`对图中的所有中心线上的点进行高程插值赋值。 ![image-20250414170003060](https://vjmap.com/blogimages/202504141700112.png) ​ 没有高程注记的点,会根据其他点的坐标自动插值 如果线的颜色是红色,表明这条线上没有找到任何高程值,所有高程值赋值失败,需手动增加高程注记后再重新赋值 ![image-20250414170313222](https://vjmap.com/blogimages/202504141703259.png) ## 效果展示 ### 三维效果 - 中心线三维管道效果:将中心线转换为三维管道效果 ![image-20250414170750772](https://vjmap.com/blogimages/202504141707824.png) - 中心线拉伸效果:显示中心线的拉伸效果 - ![image-20250414171252950](https://vjmap.com/blogimages/202504141712993.png) - 移除拉伸效果:清除拉伸效果 ## 使用步骤 1. 先进行`平行线距离设置`、点击`自动设置数值参数中其他距离项`自动计算其他距离参数 2. 提取中心线时,建议先使用`自动提取`功能,如果效果不理想再尝试`手动提取` 3. 如果需要处理高程时,建议先使用`自动找高程注记`功能,再进行`中心线高程自动赋值`, 如果出现红色的线,根据需要手动添加或修改 4. 使用三维效果,查看数据是否正确,再依次排查修改。 ## SDK用法 ```js /** * 提取中心线命令参数接口 * @interface ExtractCenterLinesParams */ interface ExtractCenterLinesParams { /** 是否包含边界 */ isContains: boolean; /** 边界坐标字符串 */ bounds: string; /** 最小间距,多个值用逗号分隔 */ minSpaceDistance: string; /** 最大间距,多个值用逗号分隔 */ maxSpaceDistance: string; /** 地图ID */ mapid?: string; /** 地图版本 */ version?: string; /** 图层 */ layer?: string; /** 是否为几何渲染模式 */ geom?: boolean; /** 共线容差距离值 */ collinearTolerance: number; /** 去除重复点容差值 */ duplicateTolerance: number; /** 允许的线段断开距离 */ maxGapDistance: number; /** 精确到小数点后几位 */ numDecimalDigits: number; /** 双线都重叠的最小比例 */ minDlMinRatio: number; /** 判断平行线斜率角度误差(度) */ paralleSlopeAngleTolerance: number; /** 合并中心线斜率角度误差(度) */ mergeSlopeAngleTolerance: number; /** 中心线段能合并的最大距离 */ maxCenterMergeDistance: number; /** 中心线段允许的最小距离 */ minCenterDistance: number; /** 是否连接设置允许间隔范围内的线 */ bConnectGrapLine: boolean; /** 是否处理合并端点 */ bMergeNode: boolean; /** 是否排除小于最小距离的线 */ bFilterShortLine: boolean; /** 是否把相连的线合成一条 */ bMergeLine: boolean; /** 中心线是否以GeoJson格式返回 */ bCenterGeoJson: boolean; /** 是否显示平行线坐标 */ bSaveParalleLineJson: boolean; /** 是否显示提取的线坐标 */ bExtractLines: boolean; } /** * 提取中心线命令 * @function extractCenterLines * @param {ExtractCenterLinesParams} params - 提取中心线参数 * @param {string} [options="_null"] - 选项参数 * @param {string} [version="v1"] - API版本 * @returns {Promise} 返回提取的中心线数据 * @description * 该命令用于从地图中提取中心线,支持自动提取和手动区域提取。 * 可以通过设置不同的参数来控制提取的精度和效果。 * * @example * const result = await svc.execCommand("extractCenterLines", { * isContains: true, * bounds: "x1,y1,x2,y2", * minSpaceDistance: "2.5,3.5", * maxSpaceDistance: "7.5,8.5", * collinearTolerance: 1.0, * duplicateTolerance: 0.2, * // ... 其他参数 * }); ``` --- # 数据权限 # 唯杰地图之前端CAD图GIS数据访问权限配置 数字经济时代,数据要素的价值日益凸显,与之相应的,数据安全问题也越来越受到重视。[唯杰地图](https://vjmap.com/) VJMAP为`CAD`图或`自定义地图格式`WebGIS`可视化`显示开发提供的一站式解决方案,支持的格式如常用的`AutoCAD`的`DWG`格式文件、`GeoJSON`等常用`GIS`文件格式,它使用WebGL`矢量图块`和`栅格瓦片`呈现交互式地图, 实现了`CAD`与`GIS`完美结合, 实现了对CAD图纸的上传、打开、版本管理、属性查询、图层开关、批注、数据展示、互联网地图叠加、绘图等功能。那么怎么样配置才能保证前端CAD图GIS数据访问的安全呢? # 后台RBAC权限管理 唯杰地图后台采用的是RBAC权限管理。 基于角色的访问控制(Role-based access control,简称 RBAC),指的是通过用户的角色(Role)赋予其相关权限,这实现了细粒度的访问控制,并提供了一个相比直接授予单个用户权限,更简单、可控的管理方式。 不同的token代表了不同的角色权限。 默认的角色有`root`、`admin`、`guest` `root`具有最高权限;`admin`权限不能删除sys_开头的系统图形、不能进行切片等,其他功能同`root`权限; `guest`只有访问权限。 可以在后台设置不同角色的权限来实现,然后不同的token来实现对数据的安全问题。 后台可以对整个系统的权限进行分配和设置,而用户自己在前端上传图形,也可以通过设置`图形隐藏`和`图形密码`来实现数据安全。 # 图形隐藏 操作步骤 (1) 上传图形 通过 [唯杰地图云端图纸管理平台](https://vjmap.com/app/cloud)提供的[上传图形](https://vjmap.com/demo/#/demo/map/service/06uploadmap) ![image-20220306184355126](https://vjmap.com/blogimages/image-20220306184355126.png) (2) 上传成功后,取个图名称ID 在这里如果要设置图形隐藏,需要图名称取名为需要以`ns_`开头, 这样图打开后在图形列表中将自动隐藏,其他用户看不到。需访问此图时可直接输入图形ID来进行访问。 ![image-20220922205903801](https://vjmap.com/blogimages/image-20220922205903801.png) # 图形密码 上面的图形隐藏下次打开时略显麻烦,这时候也可以设置图形的密码,才实现对数据安全的保护。操作为在上传图形后设置`打开图需要密码` ![image-20220922210141493](https://vjmap.com/blogimages/image-20220922210141493.png) 上传成功后,图形列表里会对设置密码保护的图上面加一个`锁`的符号。点击图形打开时,需要输入密码才能访问此图形了。 ![image-20220922210311904](https://vjmap.com/blogimages/image-20220922210311904.png) ![image-20220922210358967](https://vjmap.com/blogimages/image-20220922210358967.png) 在这里说下图形密码访问权限的机制。 superKey: 此权限为超级权限,在后台进行设置。superKey可以用重置所有图的密码,或对图进行设置密码或取消密码,拥有访问所有图的权限。 secretKey: 此权限拥有对某个图的所有权限。secretKey是根据用户上传图形时设置的密码而来。如对某个图进行了密码`123456`,那么可通过`vjmap.Service`中的方法`pwdToSecretKey('123456')`把密码明码转成秘钥。此secretKey只对当前设置密码的图有效。用户不要保存和分发,通过输入`密码`获取此值即可。 accessKey: 此权限拥有对某个图的访问权限。accessKey是根据serectKey和一个随机的salt生成而来。和secretKey类似,accessKey能打开此有密码的图形,和secretKey不同的是,他无法对此图进行管理,如`删除图形`、`重命名`、`删除版本`等操作。此accessKey可进行分发。如不想每次打开图时弹出输入框,可通过设置`vjmap.Service`中的方法addAccessKey,如svc.addAccessKey("akxxxxxxxxxxxxxxxx"),这样在打开图时会自动判断此key,如果验证通过就不会弹出密码输入框。 权限大小比较 superKey(所有图最多权限) > secretKey(一个图的最高权限) > accessKey(一个图的访问权限) 可通过superKey重置密码secretKey或accessKey; 可通过secretKey重置或获取accessKey; 点击`操作`菜单 ,可进行密码设置,获取accessKey等操作。 ![image-20220922212044224](https://vjmap.com/blogimages/image-20220922212044224.png) 另外,如果不同的图可能有不同的key,如果想要实现打开这些图时不用每次输入密码,可以设置中设置accessKey的值,此值可以是superKey或每个图的accessKey,如多个时用逗号;分开保存即可。 ![image-20220922213310885](https://vjmap.com/blogimages/image-20220922213310885.png) # 总结 通过上面的设置,基于后台和前端用户的设置,我们可以实现对CAD图GIS数据访问的安全。因为[唯杰地图](https://vjmap.com/) https://vjmap.com 只是一个地图开发平台,没有对用户名、密码进行封装,官网示例中为了方便用户进行试用,示例中没有加入用户登录功能;实际项目中,开发者可结合自己的用户权限系统,实现用户登录功能。 --- # 地图比较 在实际中,当遇到图纸更新后,要对比图纸找出图纸的不同处,一直是一个比较耗时费力的事情,也是业内的一大痛点。一般CAD新旧图纸的内容对比,包括增加新的图形元素、减少原有的图形元素以及对原有的图形进行修改。如何摆脱CAD环境,在Web端轻松实现图纸对比功能呢? # 实现思路 通常对比图纸不同有两种思路: ## 数据比较法 此方法是对图纸的原始数据进行比较分析。思路是通过遍历图纸中的所有实体元素,根据属性数据逐一比较差异性比较,找出不同处。 优点:算法准确。能定位出不同的实体对象。 缺点:图纸大时运算量大;同时,如果同一个实体删除了重新绘制会导致ObjectID发生变化,导致不好判断是否是同一个实体,算法实现难度大。 示例: 点击原图 [https://vjmap.com/app/cloud/#/map/ca4c6acf5b32?version=v1&mapopenway=GeomRender&vector=false](https://vjmap.com/app/cloud/#/map/ca4c6acf5b32?version=v1&mapopenway=GeomRender&vector=false) 进入后点击上面工具栏上面的 智能识别 -> 地图对比异同 ![image-20250602160410194](https://vjmap.com/blogimages/202506021604407.png) ```js let result = await svc.execCommand("mapCompare", { mapid1: prop.mapid1, version1: prop.version1, mapid2: prop.mapid2, version2: prop.version2 }, "_null", "v1"); ``` ## 像素比较法 此方法是根据渲染后的图片进行比较。对图片的像素进行分析对比,找出不同的区域。 优点:速度快,算法实现相对容易。 缺点:只能定位出不同的区域,不能定位出具体是哪些实体。 在实际需求中,要求快速定位不同处,而无需定位到是哪些具体的实体对象。所以我们选用像素比较法来进行对比分析实现。 先上最终效果图如下: 同步对比分析效果: ![mapdiff1.gif](https://vjmap.com/blogimages/mapdiff1.gif) 地图卷帘效果效果: ![mapdiff2.gif](https://vjmap.com/blogimages/mapdiff2.gif) # 算法分析 大家看到图片像素对比分析,肯定第一反应是这算法太简单了。一个个像素判断是否相等,然后就知道差异性了。如果这么想,那就是把问题想的太简单了。实际中,由于渲染时反锯齿的功能,会导致相同的绘制内容也会导致像素值细微的区别。而算法的核心就是把这些干扰因素给排除,找到真正差异的部分。 ## 图片相似度计算方法总结 - 余弦相似度 ​ 把图片表示成一个向量,通过计算向量之间的余弦距离来表征两张图片的相似度 ​ 具体算法可参考 [https://zhuanlan.zhihu.com/p/93893211](https://zhuanlan.zhihu.com/p/93893211) - 直方图 ​ 按照某种距离度量的标准对两幅图像的直方图进行相似度的测量 ​ 具体算法可参考 [https://zhuanlan.zhihu.com/p/274429582](https://zhuanlan.zhihu.com/p/274429582) - 哈希算法 ​ 感知哈希可以用来判断两个图片的相似度,通常可以用来进行图像检索。感知哈希算法对每一张图片生成一个“指纹”,通过比较两张图片的指纹,来判断他们的相似度,是否属于同一张图片。常用的有三种:平均哈希(aHash),感知哈希(pHash),差异值哈希(dHash). 具体算法可参考 [此文章](https://blog.csdn.net/qq_32799915/article/details/81000437?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-81000437-blog-83271885.pc_relevant_aa&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-81000437-blog-83271885.pc_relevant_aa&utm_relevant_index=2) - 像素匹配pixelmatch 利用像素之间的匹配来计算相似度。 [https://github.com/whtsky/pixelmatch-py](https://github.com/whtsky/pixelmatch-py) # 实现 我们基于BS模式对图片进行对比分析找出不同处。在服务端实现解析CAD图纸,生成像素图片;利用pixelmatch算法找出不同处。在浏览器端加载CAD图并显示出不同的地方。 (1) Web端在线打开CAD图 在Web网页端展示CAD图形 [唯杰地图云端图纸管理平台](https://vjmap.com/app/cloud) 。 ![image-20220831205353007](https://vjmap.com/blogimages/image-20220831205353007.png) (2) 把CAD图转成图片 因为唯杰地图采用的把CAD图转成GIS数据渲染的思路,所以可以通过提供的WMS服务,渲染成指定像素大小的图片。这里为了对比结果准确,可以把渲染的级别设置大点,得到的图片像素大小也变大,更加清晰,对比结果更准确。 接口如下: ```js /** * wms服务url地址接口 */ export interface IWmsTileUrl { /** 地图ID(为空时采用当前打开的mapid), 为数组时表时同时请求多个. */ mapid?: string | string[]; /** 地图版本(为空时采用当前打开的地图版本). */ version?: string | string[]; /** 图层名称(为空时采用当前打开的地图图层名称). */ layers?: string | string[]; /** 范围,缺省{bbox-epsg-3857}. (如果要获取地图cad一个范围的wms数据无需任何坐标转换,将此范围填cad范围,srs,crs,mapbounds填为空).*/ bbox?: string; /** 当前坐标系,缺省(EPSG:3857). */ srs?: string; /** cad图的坐标系,为空的时候由元数据坐标系决定. */ crs?: string | string[]; /** 地理真实范围,如有值时,srs将不起作用 */ mapbounds?: string; /** 宽. */ width?: number; /** 高. */ height?: number; /** 是否透明. */ transparent?: boolean; /** 四参数(x偏移,y偏移,缩放,旋转弧度),可选,对坐标最后进行修正*/ fourParameter?: string | string[]; /** 是否是矢量瓦片. */ mvt?: boolean; /** 是否考虑旋转,在不同坐标系中转换是需要考虑。默认自动考虑是否需要旋转. */ useImageRotate?: boolean; } ``` (3) 像素对比分析算法 其反锯齿像素对比核心算法代码如下 ```c++ uint8_t blend(uint8_t c, double a) { return 255 + (c - 255) * a; } double rgb2y(uint8_t r, uint8_t g, uint8_t b) { return r * 0.29889531 + g * 0.58662247 + b * 0.11448223; } double rgb2i(uint8_t r, uint8_t g, uint8_t b) { return r * 0.59597799 - g * 0.27417610 - b * 0.32180189; } double rgb2q(uint8_t r, uint8_t g, uint8_t b) { return r * 0.21147017 - g * 0.52261711 + b * 0.31114694; } // 使用YIQ NTSC传输颜色空间测量感知色差”计算色差 double colorDelta(const uint8_t* img1, const uint8_t* img2, std::size_t k, std::size_t m, bool yOnly = false) { double a1 = double(img1[k + 3]) / 255; double a2 = double(img2[m + 3]) / 255; uint8_t r1 = blend(img1[k + 0], a1); uint8_t g1 = blend(img1[k + 1], a1); uint8_t b1 = blend(img1[k + 2], a1); uint8_t r2 = blend(img2[m + 0], a2); uint8_t g2 = blend(img2[m + 1], a2); uint8_t b2 = blend(img2[m + 2], a2); double y = rgb2y(r1, g1, b1) - rgb2y(r2, g2, b2); if (yOnly) return y; // 仅亮度差 double i = rgb2i(r1, g1, b1) - rgb2i(r2, g2, b2); double q = rgb2q(r1, g1, b1) - rgb2q(r2, g2, b2); return 0.5053 * y * y + 0.299 * i * i + 0.1957 * q * q; } void drawPixel(uint8_t* output, std::size_t pos, uint8_t r, uint8_t g, uint8_t b) { output[pos + 0] = r; output[pos + 1] = g; output[pos + 2] = b; output[pos + 3] = 255; } double grayPixel(const uint8_t* img, std::size_t i) { double a = double(img[i + 3]) / 255; uint8_t r = blend(img[i + 0], a); uint8_t g = blend(img[i + 1], a); uint8_t b = blend(img[i + 2], a); return rgb2y(r, g, b); } // 检查像素是否可能是抗锯齿的一部分 bool antialiased(const uint8_t* img, std::size_t x1, std::size_t y1, std::size_t width, std::size_t height, const uint8_t* img2 = nullptr) { std::size_t x0 = x1 > 0 ? x1 - 1 : 0; std::size_t y0 = y1 > 0 ? y1 - 1 : 0; std::size_t x2 = std::min(x1 + 1, width - 1); std::size_t y2 = std::min(y1 + 1, height - 1); std::size_t pos = (y1 * width + x1) * 4; uint64_t zeroes = 0; uint64_t positives = 0; uint64_t negatives = 0; double min = 0; double max = 0; std::size_t minX = 0, minY = 0, maxX = 0, maxY = 0; // 穿过8个相邻像素 for (std::size_t x = x0; x <= x2; x++) { for (std::size_t y = y0; y <= y2; y++) { if (x == x1 && y == y1) continue; // 中心像素和相邻像素之间的亮度增量 double delta = colorDelta(img, img, pos, (y * width + x) * 4, true); // 计算相等、较暗和较亮相邻像素的数量 if (delta == 0) zeroes++; else if (delta < 0) negatives++; else if (delta > 0) positives++; // 如果找到两个以上相同的同级,则绝对不是抗锯齿 if (zeroes > 2) return false; if (!img2) continue; // 记得最暗的像素 if (delta < min) { min = delta; minX = x; minY = y; } // 记住最亮的像素 if (delta > max) { max = delta; maxX = x; maxY = y; } } } if (!img2) return true; // 如果同级之间没有较暗和较亮的像素,则不是抗锯齿 if (negatives == 0 || positives == 0) return false; // 如果最暗或最亮的像素在两幅图像中都有两个以上相同的同级 //(绝对不是反走样),该像素是反走样的 return (!antialiased(img, minX, minY, width, height) && !antialiased(img2, minX, minY, width, height)) || (!antialiased(img, maxX, maxY, width, height) && !antialiased(img2, maxX, maxY, width, height)); } } ``` (4) 前端调用算法并展示 相关代码如下 ```javascript // 地图比较不同 let diff = await service.cmdMapDiff({ // 要比较图1的图名称 mapid1: mapId1, // 要比较图1的图版本,如为空,表示是最新版本 version1: "", // 要比较图1的图层样式名称,可为空。为空的用默认的 layer1: map1.getService().currentMapParam().layer, // 要比较图2的图名称,图名称可以和mapid1不一样 mapid2: mapId2, // 要比较图2的图版本,如为空,表示是最新版本 version2: "", // 要比较图2的图层样式名称,可为空。为空的用默认的 layer2: map2.getService().currentMapParam().layer }) if (diff.error) { message.error(diff.error); return; } const drawPolygons = (map, points, color) => { if (points.length === 0) return; points.forEach(p => p.push(p[0])) ;// 闭合 let polygons = points.map(p => { return { points: map.toLngLat(p), properties: { color: color } } }) vjmap.createAntPathAnimateLineLayer(map, polygons, { fillColor1: color, fillColor2: "#0ffb", canvasWidth: 128, canvasHeight: 32, frameCount: 4, lineWidth: 4, lineOpacity: 0.8 }); } if (diff.modify.length === 0) { message.info("完全相同,没有找到不同处"); return; } // 修改的部分 drawPolygons(map2, diff.modify, "#f00"); // 新增部分 drawPolygons(map2, diff.new, "#0f0"); // 删除部分 drawPolygons(map1, diff.del, "#00f"); ``` 以上前端的实现代码已开源至github。 地址:[https://github.com/vjmap/vjmap-playground/blob/main/src/02service_%E5%9C%B0%E5%9B%BE%E6%9C%8D%E5%8A%A1/17zmapDiff.js](https://github.com/vjmap/vjmap-playground/blob/main/src/02service_%E5%9C%B0%E5%9B%BE%E6%9C%8D%E5%8A%A1/17zmapDiff.js) 在线体验地址为:[https://vjmap.com/demo/#/demo/map/service/17zmapDiff](https://vjmap.com/demo/#/demo/map/service/17zmapDiff) --- # 实体对象智能识别 ## 概述 实体对象智能识别能够在CAD图纸中智能识别和匹配相似的实体对象。该系统采用模式匹配算法,支持几何变换(缩放、旋转),并提供了丰富的配置选项和可视化界面。 系统提供两种主要的识别方式: ### 1. 块参照实体识别 针对CAD图纸中以块(Block)形式存在的对象,系统能够: - **批量获取所有块参照**:自动检索图纸中的所有块参照实体,支持从元数据或表达式查询获取 - **块信息管理**:显示块名称、对象ID、图层名称、边界范围、位置坐标、旋转角度、缩放比例等详细信息 - **可视化展示**:通过动画线框和高亮显示所有块的位置和边界 - **交互式操作**:支持点击表格行定位到对应块,支持显示/隐藏所有块的可视化效果 - **悬浮提示**:鼠标悬停时显示块的详细属性信息,包括属性数据 ![](https://vjmap.com/blogimages/mapblockrefs.gif) ### 2. 智能对象识别 针对不是块形式的普通CAD实体对象,系统提供: - **模式匹配识别**:基于用户选择的参考实体,在图纸中智能匹配相似的对象 - **几何变换支持**:支持缩放、旋转等几何变换的容差匹配 - **多条件筛选**:支持颜色、图层、文本内容等多种匹配条件 - **配置化管理**:支持保存和加载识别配置,提高识别效率 ![](https://vjmap.com/blogimages/recognizer.gif) ## 核心功能特性 ### 1. 智能实体选择 - **多种选择模式**:支持框选、点选、多边形选择三种实体选择方式 - **实时预览**:选择后立即显示所选实体数量 - **边界计算**:自动计算所选实体的边界范围 ### 2. 几何变换支持 - **缩放变换**:支持设置缩放比例范围(0.1-10倍) - **旋转变换**:支持设置旋转角度范围(0-360度) - **变换容差**:可配置变换匹配的容差值 ### 3. 匹配条件配置 - **颜色匹配**:可设置是否要求实体颜色相同 - **图层匹配**:可设置是否要求实体在同一图层 - **文本内容匹配**:可设置文本类实体是否要求内容相同 - **自定义匹配规则**:支持针对不同实体类型配置匹配属性 ### 4. 配置管理系统 - **配置保存**:支持将识别配置保存到服务器 - **配置加载**:支持从服务器加载已保存的配置 - **配置覆盖**:支持覆盖同名配置 - **配置删除**:支持删除不需要的配置 ### 5. 缩略图生成 - **自动生成**:选择实体后自动生成120x80像素的缩略图 - **配置关联**:缩略图与配置一起保存,便于快速识别 - **实时更新**:选择实体变化时自动更新缩略图 ## 使用场景 ### 1. 块参照实体管理 - **块实体统计**:快速统计图纸中所有块参照的数量和类型分布 - **块位置定位**:通过表格快速定位到指定块的位置,便于查看和编辑 - **块属性查看**:查看块的详细属性信息,包括位置、旋转角度、缩放比例等 - **块重复性分析**:识别相同块名的多个实例,分析设计重复性 ### 2. CAD图纸标准化检查 - 检查图纸中相似元素的一致性 - 发现不规范的设计元素 ### 3. 重复元素检测 - 识别图纸中的重复设计 - 优化图纸存储和传输 ### 4. 设计模式分析 - 分析图纸中的设计模式 - 提取可复用的设计元素 ### 5. 智能对象匹配 - 基于参考实体智能匹配相似对象 - 支持几何变换的模糊匹配 - 提高CAD图纸处理的自动化程度 - ## 技术实现架构 ### 1. 核心数据结构 #### 模式配置数据 ```typescript let patternConfig: any = { "name": "", "sameColor": true, "sameLayer": false, "global": { "match": { "all": { "equalMatchProperty": ["color"] }, "AcDbCircle": { "scaleMatchProperty": ["radius"] }, "AcDbArc": { "scaleMatchProperty": ["radius"] }, "AcDbLine": { "scaleMatchProperty": ["length"] }, "AcDbText": { "scaleMatchProperty": ["height"] } } }, "rules": [{ "tolerance": 0.00001, "translateTolerance": 0.015, "transform": { "scale": { "min": 0.5, "max": 2.0 }, "rotation": { "min": 0, "max": 360 } }, "referenceObjectIds": [] }] } ``` ### 2. 关键算法实现 #### 块参照实体检索算法 ```typescript const detectAllBlockRefsObjects = async () => { try { removeAntPathAnimateLine(); let svc = map.getService(); let metadata = await svc.metadata(); let blockReferences = [] if("blockReferences" in metadata) { // 从元数据中获取块参照信息(新版本) let blockRefs = metadata.blockReferences; if (blockRefs) { blockReferences = JSON.parse(blockRefs); } } else { // 通过表达式查询获取块参照信息(旧版本兼容) if (map.logInfo) map.logInfo("正在获取所有块数据,请稍候...", "success", 2000); let query = await svc.exprQueryFeature({ expr: `gOutReturn := if((gInFeatureType == 'AcDbBlockReference'), 1, 0);`, fields: "", // 查询所有字段 geom: false, // 内存模式 useCache: true, limit: 1000000 // 查找所有块参照 }); if (query.error) { ElMessage({ type: 'error', message: query.error, }); return; } blockReferences = query.result.map((r: any) => { return { objectId: r.objectid, blockName: r.blockname, layerName: r.layername, bounds: r.bounds, position: r.positon, ...r }; }); } // 保存数据并创建可视化效果 blockRefsData.value = blockReferences; let data = getData(); createAntPathAnimateLine(data); createHighlightBlocks(data); ElMessage({ type: 'success', message: `共找到 ${blockReferences.length} 个块参照实体。`, }); } catch (error) { showError("获取块参照实体失败:" + (error as any).message); } } ``` #### 块可视化高亮算法 ```typescript const createHighlightBlocks = (data: any) => { let polygons = data let polygon = new vjmap.Polygon({ data: polygons, fillColor: ['case', ['to-boolean', ['feature-state', 'hover']], '#0ff', '#f00'], // 默认透明,鼠标悬停时显示高亮 fillOpacity: ['case', ['to-boolean', ['feature-state', 'hover']], 0.1, 0.0], fillOutlineColor: ['get', 'color'], isHoverPointer: true, isHoverFeatureState: true }); polygon.addTo(map); // 悬浮提示功能 polygon.hoverPopup((f: any, popup: any) => { let bounds = vjmap.GeoBounds.fromDataExtent(f); popup.setLngLat([bounds.center().x, bounds.max.y]); return `

块名称: ${f.properties.blockName}

图层名称: ${f.properties.layerName}
对象ID: ${f.properties.objectId}${f.properties.attributeDef ? '
属性数据: ' + f.properties.attributeDef : ''}` }, { anchor: 'bottom' }); polygonOverlay = polygon; } ``` #### 实体选择算法 ```typescript const select = async () => { removeAntPathAnimateLine() let result = await selectFeatures(map, null, btnCtx, false) if (!result || !result.features) return; currentFeatures.value = result.features patternConfig.rules[patternConfig.rules.length - 1].referenceObjectIds = result.features // 选择后生成缩略图 await generateThumbnail() } ``` #### 缩略图生成算法 ```typescript const generateThumbnail = async () => { if (currentFeatures.value.length === 0) { thumbnailImage.value = '' return } try { isGeneratingThumbnail.value = true // 获取所有选中对象的边界 let allBounds = null for (const feature of currentFeatures.value) { const bounds = vjmap.GeoBounds.fromString(feature.properties.bounds) if (!allBounds) { allBounds = bounds } else { allBounds.updateByBounds(bounds) } } if (allBounds) { // 生成 120x80 的缩略图 let objectIds = currentFeatures.value.map((item: any) => item.properties.objectid) const base64Image = await getObjectsThumbnail(map, objectIds, allBounds, 120, 80) thumbnailImage.value = base64Image } } catch (error) { console.error('Generate thumbnail failed:', error) thumbnailImage.value = '' } finally { isGeneratingThumbnail.value = false } } ``` #### 智能识别算法 ```typescript const detectAllObjects = async () => { try { if (!formData.value.keepLastResult) { removeAntPathAnimateLine(); } if (currentFeatures.value.length == 0) { ElMessage({ type: 'error', message: '请先选择要识别的实体', }) return; } let svc = map.getService(); ElMessage({ type: 'success', message: '正在执行智能识别,请稍候...', }) // 调用后端识别服务 let res = await svc.execCommand("objectDetection", { mapid: svc.currentMapParam()?.mapid, version: svc.currentMapParam()?.version, layer: svc.currentMapParam()?.layer, pattern: JSON.stringify(patternConfig), detailedLog: formData.value.detailedLog, bounds: formData.value.bounds }, "_null", "v1"); // 处理识别结果 if (res && res.result && res.result.length > 0) { // 在地图上显示识别结果 let geoDatas = []; for (let i = 0; i < res.result.length; i++) { const bounds = vjmap.GeoBounds.fromString(res.result[i].bounds) const pts = bounds.toPointArray(); pts.push(pts[0]) geoDatas.push({ points: map.toLngLat(pts.map(p => vjmap.geoPoint(p))), properties: { color: "#ff0000" } }) } // 创建动画线图层 let antPathAnimateLine = vjmap.createAntPathAnimateLineLayer(map, geoDatas, { fillColor1: "#f00", fillColor2: formData.value.keepLastResult ? vjmap.randomColor() : "#0ff", canvasWidth: 128, canvasHeight: 32, frameCount: 4, lineWidth: 2, lineOpacity: 0.8 }); map._dectionAntPathAnimateLines = map._dectionAntPathAnimateLines || []; map._dectionAntPathAnimateLines.push(antPathAnimateLine); ElMessage({ type: 'success', message: `共找到 ${geoDatas.length} 个匹配的对象。`, }) } } catch (error) { showError("识别失败:" + (error as any).message); } } ``` ### 4. 配置管理实现 #### 配置保存 ```typescript const saveConfig = async () => { try { // 确保配置列表已加载,用于检查重复名称 if (configList.value.length === 0) { await loadConfigList() } // 获取配置名称 const name = await getInput('保存配置', '请输入配置名称', patternConfig.name || '') if (!name || typeof name !== 'string') { ElMessage.warning('配置名称不能为空') return } // 检查名称是否重复 const exists = checkConfigExists(name) let shouldOverwrite = false if (exists) { try { const action = await ElMessageBox.confirm( `配置名称 "${name}" 已存在,请选择操作`, '名称重复', { confirmButtonText: '覆盖', cancelButtonText: '新增', distinguishCancelAndClose: true, type: 'warning' } ) shouldOverwrite = true patternConfig.name = name } catch (error: any) { if (error === 'cancel') { patternConfig.name = name } else { return } } } else { patternConfig.name = name } // 如果是覆盖操作,先删除旧配置 if (shouldOverwrite) { const existingConfig = configList.value.find((c: any) => c.name === name) if (existingConfig) { const svc = map.getService() const deleteUrl = svc.serviceUrl(`recognizer/delete?filename=${existingConfig.filePrefix}`) await svc.httpDel(deleteUrl) } } // 添加缩略图到配置中 if (thumbnailImage.value) { patternConfig.thumbnail = thumbnailImage.value } const svc = map.getService() const url = svc.serviceUrl("recognizer/add") const response = await svc.httpPost(url, patternConfig) const result = response.data if (result.code === 0) { ElMessage.success(shouldOverwrite ? '配置覆盖成功' : '配置保存成功') await loadConfigList() } else { ElMessage.error('配置保存失败') } } catch (error) { console.error('Save config failed:', error) ElMessage.error('配置保存失败') } } ``` #### 配置加载 ```typescript const selectConfig = async (config: any) => { try { selectedConfigId.value = config.filePrefix // 获取配置详细数据 const svc = map.getService() const url = svc.serviceUrl(`recognizer/detail?filenames=${config.filePrefix}`) const response = await svc.httpGet(url) const result = response.data if (result.code === 0 && result.data && result.data.length > 0) { const configData = result.data[0] // 加载配置数据 patternConfig = configData // 同步表单数据 if (configData.rules && configData.rules.length > 0) { const rule = configData.rules[0] if (rule.transform) { if (rule.transform.scale) { formData.value.allowScale = true formData.value.scaleMin = rule.transform.scale.min || 0.5 formData.value.scaleMax = rule.transform.scale.max || 2 } else { formData.value.allowScale = false } if (rule.transform.rotation) { formData.value.allowRotation = true formData.value.rotationMin = rule.transform.rotation.min || 0 formData.value.rotationMax = rule.transform.rotation.max || 360 } else { formData.value.allowRotation = false } } } // 同步其他配置项 formData.value.sameColor = configData.sameColor || false formData.value.sameLayer = configData.sameLayer || false // 更新缩略图 if (configData.thumbnail) { thumbnailImage.value = configData.thumbnail } else { thumbnailImage.value = '' } currentFeatures.value = patternConfig.rules[patternConfig.rules.length - 1].referenceObjectIds ElMessage.success(`已加载配置: ${config.name}`) } } catch (error) { console.error('Load config failed:', error) ElMessage.error('选择配置失败') } } ``` 5. --- # 服务端渲染表达式语法 ## 支持 支持以下基本算术运算、功能和过程: - (00) 类型:Scalar, Vector, String - (01) 基本运算符:+、-、*、/、%、^ - (02) 赋值::=, +=, -=, *=, /=, %= - (03) 等于与不等于:=、==、<>、!=、<、<=、>、>= - (04) 逻辑运算符:and, mand, mor, nand, nor, not, or, shl, shr, xnor, xor, true, false - (05) 函数:abs, avg, ceil, clamp, equal, erf, erfc, exp, expm1, floor, frac, log, log10, log1p, log2, logn, max, min, mul, ncdf, not_equal, root, round, roundn, sgn, sqrt, sum, swap, trunc - (06) 三角函数:acos, acosh, asin, asinh, atan, atanh, atan2, cos, cosh, cot, csc, sec, sin, sinc, sinh, tan, tanh, hypot, rad2deg, deg2grad, deg2rad, grad2deg - (07) 控制结构:if-then-else, ternary conditional, switch-case, return-statement - (08) 循环语句:while、for、repeat-until、break、continue - (09) 字符串处理:in,like,ilike,concatenation - (10)优化:constant-folding, simple strength reduction and dead code elimination - (11) 微积分:数值积分与微分 ## 示例表达式 - (01) sqrt(1 - (3 / x^2)) - (02) clamp(-1, sin(2 * pi * x) + cos(y / 2 * pi), +1) - (03) sin(2.34e-3 * x) - (04) if(((x[2] + 2) == 3) and ((y + 5) <= 9),1 + w, 2 / z) - (05) inrange(-2,m,+2) == if(({-2 <= m} and [m <= +2]),1,0) - (06) ({1/1}*[1/2]+(1/3))-{1/4}^[1/5]+(1/6)-({1/7}+[1/8]*(1/9)) - (07) a * exp(2.2 / 3.3 * t) + c - (08) z := x + sin(2.567 * pi / y) - (09) u := 2.123 * {pi * z} / (w := x + cos(y / pi)) - (10) 2x + 3y + 4z + 5w == 2 * x + 3 * y + 4 * z + 5 * w - (11) 3(x + y) / 2.9 + 1.234e+12 == 3 * (x + y) / 2.9 + 1.234e+12 - (12) (x + y)3.3 + 1 / 4.5 == [x + y] * 3.3 + 1 / 4.5 - (13) (x + y[i])z + 1.1 / 2.7 == (x + y[i]) * z + 1.1 / 2.7 - (14) (sin(x / pi) cos(2y) + 1) == (sin(x / pi) * cos(2 * y) + 1) - (15) 75x^17 + 25.1x^5 - 35x^4 - 15.2x^3 + 40x^2 - 15.3x + 1 - (16) (avg(x,y) <= x + y ? x - y : x * y) + 2.345 * pi / x - (17) while (x <= 100) { x -= 1; } - (18) x <= 'abc123' and (y in 'AString') or ('1x2y3z' != z) - (19) ((x + 'abc') like '*123*') or ('a123b' ilike y) - (20) sgn(+1.2^3.4z / -5.6y) <= {-7.8^9 / -10.11x }- ## 内置操作和功能 ### 算术和赋值运算符 |操作符|说明| |:---|:---| | + | x 和 y 之间的加法。(例如:x + y) | | - | x 和 y 之间的减法。(例如:x - y) | | * | x 和 y 之间的乘法。(例如:x * y) | | / | x 和 y 之间的除法。(例如:x / y) | | % | x 相对于 y 的模量。(例如: x % y) | | ^ | x 的 y 次方。(例如:x ^ y) | | := | 将 x 的值分配给 y。其中 y 是一个变量或向量类型。(例如: y := x) | | += | 将 x 增加右侧表达式的值。其中 x 是变量或向量类型。 (例如: x += abs(y - z)) | | -= | 将 x 递减右侧表达式的值。其中 x 是变量或向量类型。(例如: x[i] -= abs(y + z)) | | *= | 将 x 乘以的值赋值x 右侧的表达式。其中 x 是变量或向量类型。(例如: x *= abs(y / z)) | | /= | 将 x 除以表达式的值赋值 在 x 的右侧。其中 x 是一个 变量或向量类型。(例如: x[i + j] /= abs(y * z)) | | %= | 将右侧表达式的值赋给 x 模x 的手边。其中 x 是变量或向量类型。(例如: x[2] %= y ^ 2) | ### 等于与不等于 |操作符|说明| |:---|:---| | == 或 = | 仅当 x 严格等于 y 时才为真。(例如:x == y)| | <> 或 != | 仅当 x 不等于 y 时才为真。(例如:x <> y 或 x != y)| | < | 仅当 x 小于 y 时才为真。(例如: x < y) | | <= | 仅当 x 小于或等于 y 时才为真。(例如:x <= y)| | > | 仅当 x 大于 y 时才为真。(例如:x > y) | | >= | 仅当 x 大于或等于 y 时才为真。(例如:x >= y)| ### 布尔运算 |操作符|说明| |:---|:---| | true | 真实状态或除零以外的任何值(通常为 1)。| | false | 错误状态,值恰好为零。| | and| 逻辑与,仅当 x 和 y 都为真时才为真。(例如:x and| y) | | mand | 多输入逻辑与,仅当所有输入都为 | 时为真的。表达式从左到右短路。 (例如: mand(x > y, z < w, u or v, w and x)) | | mor | 多输入逻辑或,如果至少是 | 之一,则为真,输入是真的。从左到右短路 ;表达式。(例如: mor(x > y, z < w, u or v, w and x)) | | nand | 逻辑 NAND,仅当 x 或 y 为假时才为真。(例如:x nand y) | | nor| 逻辑 NOR,仅当 x 或 y 的结果为假时才为真(例如:x nor y) | | not| 逻辑非,否定输入的逻辑意义。(例如: not(x and y) == x nand y) | | or | 逻辑或,如果 x 或 or 为真,则为真。(例如:x or y) | | xor| 逻辑异或,仅当 x 和 y 的逻辑状态为 True (例如:x xor y) | | xnor | 逻辑 XNOR,当 x 和 y 的双条件满足。(例如: x xnor y) | | &| 类似于 AND 但从左到右的表达式(例如: (x & y) == (y and x)) | | | | 与 OR 类似,(例如: (x | y) == (y or x)) | ### 通用函数 |函数|示例| |:---|:---| | abs | abs(x) | | avg | avg(x,y,z,w,u,v) == (x + y + z + w + u + v) / 6) | | ceil | ceil(x) | | clamp | clamp(r0,x,r1) | | equal | | | erf | erf(x) | | erfc |erfc(x) | | exp | exp(x) | | expm1 | expm1(x) | | floor | floor(x) | | frac |frac(x) | | hypot | hypot(x,y) = sqrt(x*x + y*y)| | iclamp | iclamp(r0,x,r1) | | inrange | inrange(r0,x,r1) | | log | log(x) | | log10 | log10(x) | | log1p | log1p(x) | | log2 | log2(x) | | logn | logn(x,8) | | max | max(x,y,z,w,u,v) | | min | min(x,y,z,w,u) | | mul | mul(x,y,z,w,u,v,t) == (x * y * z * w * u * v * t) | | ncdf | ncdf(x) | | not_equal| | | pow | pow(x,y) == x ^ y | | root | root(x,3) == x^(1/3) | | round | round(x) | | roundn | roundn(1.2345678,4) == 1.2346 | | sgn | sgn(x) | | sqrt | sqrt(x) | | sum | sum(x,y,z,w,u,v,t) == (x + y + z + w + u + v + t) | | swap | swap(x,y) or x <=> y) | | trunc | trunc(x)) | ### 三角函数 |函数|说明| |:---|:---| | acos | | | acosh | | | asin | | | asinh | | | atan | | | atan2 | | | atanh | | | cos | | | cosh | | | cot | | | csc | | | sec | | | sin | | | sinc | | | sinh | | | tan | | | tanh | | | deg2rad | | | deg2grad | | | rad2deg | | | grad2deg | | ### 字符处理函数 |函数|示例| |:---|:---| | = , == || | !=, <> | | | <=, >= | | | < , > | not((x <= 'AbC') and ('1x2y3z' <> y)) or (z == x) | | in | x in y or 'abc' in 'abcdefgh' | | like | T x like y or 'abcdefgh' like 'a?d*h' | | ilike | x ilike y or 'a1B2c3D4e5F6g7H' ilike 'a?d*h' | | [r0:r1] | x[1:4] == 'bcde' ; x[ :5] == x[:10 / 2] == 'abcdef' ; x[2 + 1: ] == x[3:] =='defgh' ; x[ : ] == x[:] == 'abcdefgh' ; 5. x[4/2:3+2] == x[2:5] == 'cdef' | := | y := x ; y := 'abc' ;y := x[:i + j] ; y := '0123456789'[2:7] ;y := '0123456789'[2i + 1:7] ;y := (x := '0123456789'[2:7]);y[i:j] := x ; y[i:j] := (x + 'abcdefg'[8 / 4:5])[m:n] | + | x + y; (x + 'a1B2c3D4' + y)[i:2j] | <=> | 交换 x 和 y 的值。其中 x 和 y 是可变的 x <=> y | | [] | 字符串大小运算符返回字符串的大小 'abc'[] == 3; (('abc' + 'xyz')[1:4])[] == 4 | (6) 控制结构 |函数|示例| |:---|:---| | if | 如果 x 为真,则返回 y,否则返回 z。 if (x, y, z) ; if ((x + 1) > 2y, z + 1, w / v); if (x > y) z; if (x <= 2*y) { z + w }; | | if-else | 1. if (x > y) z; else w; | | | 2. if (x > y) z; else if (w != u) v; | | | 3. if (x < y) { z; w + 1; } else u; | | | 4. if ((x != y) and (z > w)) | | | { | | | y := sin(x) / u; | | | z := w + 1; | | | } | | | else if (x > (z + 1)) | | | { | | | w := abs (x - y) + z; | | | u := (x + 1) > 2y ? 2u : 3u; | | | } | | switch | switch | | | { | | | case x > (y + z) : 2 * x / abs(y - z); | | | case x < 3 : sin(x + y); | | | default : 1 + x; | | | } | | while | while ((x -= 1) > 0) | | | { | | | y := x + z; | | | w := u + y; | | | } | | repeat | repeat | | | y := x + z; | | | w := u + y; | | | until ((x += 1) > 100) | | for | for (var x := 0; (x < n) and (x != y); x += 1) | | | { | | | y := y + x / 2 - z; | | | w := u + y; | | | } | | break | while ((i += 1) < 10) | | | { | | | if (i < 5) | | | j -= i + 2; | | | else if (i % 2 == 0) | | | break; | | | else | | | break[2i + 3]; | | | } | | continue | for (var i := 0; i < 10; i += 1) | | | { | | | if (i < 5) | | | continue; | | | j -= i + 2; | | | } | | return | 1. return [1]; | | | 2. return [x, 'abx']; | | | 3. return [x, x + y,'abx']; | | | 4. return []; | | | 5. if (x < y) | | | return [x, x - y, 'result-set1', 123.456]; | | | else | | | return [y, x + y, 'result-set2']; | | ?: | 1. x ? y : z | | | 2. x + 1 > 2y ? z + 1 : (w / v) | | | 3. min(x,y) > z ? (x < y + 1) ? x : y : (w * v) | | ~ | eg: | | | ~(i := x + 1, j := y / z, k := sin(w/u)) == (sin(w/u))) | | | ~{i := x + 1; j := y / z; k := sin(w/u)} == (sin(w/u))) | | [*] | 评估其 case 语句所针对的任何结果. 真的返回值将为零或结果 | | | [*] | | | { | | | case (x + 1) > (y - 2) : x := z / 2 + sin(y / pi); | | | case (x + 2) < abs(y + 3) : w / 4 + min(5y,9); | | | case (x + 3) == (y * 4) : y := abs(z / 6) + 7y; | | | } | | [] | 向量大小运算符返回向量的大小 | | | 1. v[] | | | 2. max_size := max(v0[],v1[],v2[],v3[]) | --- # 服务端条件查询和表达式查询 ## 条件查询 针对后台数据库表结构,使用sql语句条件部分进行数据查询。只需要写`where`后面的部分 表结构如下 |字段名|类型|说明| |:---|:---|:---| |id|integer|id| |objectid|text|实体id(块objectid命名规则:块id_引用的块定义id1_引用的块定义id2(可能有多个)_实体id; 组objectid命名规则:组id$实体id_实体内元素索引;表格命名规则:表格id@实体id_实体内元素索引,其余实体命名规则: id_实体内元素索引| |layerindex|integer|图层索引| |name|text|实体类型值,如"1"表示AcDbLine,具体值查看下面的“支持的CAD实体类型”| |color|int|颜色(根据rgb颜色计算此值,例如黑色为0x000000,加上透明度为0xFF000000, 变成无符号 0xff000000<<0结果为 -16777216)| |data|blob|几何数据| |s1|text|线型| |s2|text|扩展字典数据| |s3|text|坐标数据,不同实体表达不一样| |s4|text|字符型属性数据, 不同实体表达不一样| |s5|text|字符型属性数据, 不同实体表达不一样| |n1|float|颜色索引| |n2|float|线型比例| |n3|float|线宽| |n4|float|透明度| |n5|float|数值型属性数据, 不同实体表达不一样| |n6|float|数值型属性数据, 不同实体表达不一样| |n7|float|数值型属性数据, 不同实体表达不一样| |n8|float|数值型属性数据, 不同实体表达不一样| |n9|float|数值型属性数据, 不同实体表达不一样| |n10|float|数值型属性数据, 不同实体表达不一样| |geom|GEOMETRY|实体外包矩形| (实体类型各字段含义详细文档见此文档后面说明) ## 渲染模式下表达式内置变量 |变量|说明|类型|举例| |:---|:---|:---|:---| |gInColor|当前实体输入颜色|(数值型)|4278255615 |gInColorRed|当前实体输入颜色(红色分量)|(数值型)|255 |gInColorGreen|当前实体输入颜色(绿色分量)|(数值型)|255 |gInColorBlue|当前实体输入颜色(蓝色分量)|(数值型)|0 |gInZoom|当前缩放级别|(数值型)|1 |println|调试输出函数|| |gOutColor|输出颜色(要修改的颜色)|(数值型)|4278255615 ## 查询模式下样式表达式内置变量 |变量| 说明 |类型|举例| |:---|:-----------------------|:---|:---| |gInLayerIndex| 当前实体图层索引 |(数值型)|0| |gInObjectId| 当前实体对象ID |(字符串)|'283' 判断如 gInObjectId=='283' |gInEntityObjId| 当前实体所属父对象ID |(字符串)|'1C' 判断如 gInEntityObjId=='1C' 取 gInObjectId中_前面的字符,如为块时gInObjectId=='283_1',则 gInEntityObjId= '283' |gInFeatureType| 当前实体类型 |(字符串)|'AcDbPolyline' 判断如 gInFeatureType=='AcDbPolyline' |gInLayerName| 当前实体图层名称 |(字符串)|'图层1' 判断如 gInLayerName=='图层1' |gInColor| 当前实体颜色 |(数值型)|4278255615 |gInColorRed| 当前实体颜色(红色分量) |(数值型)|255 |gInColorGreen| 当前实体颜色(绿色分量) |(数值型)|255 |gInColorBlue| 当前实体颜色(蓝色分量) |(数值型)|0 |gInColorAlpha| 当前实体颜色(透明度分量) |(数值型)|0 |println| 调试输出函数 | |gInExtendMinX| 当前实体图形范围(右下角X) |(数值型)|700.10 |gInExtendMinY| 当前实体图形范围(右下角y) |(数值型)|10.01 |gInExtendMaxX| 当前实体图形范围(左上角X) |(数值型)|800.20 |gInExtendMaxY| 当前实体图形范围(左上角y) |(数值型)|20.02 |gInAttrs1| 当前实体属性一值 |(字符串) s1 线型| |gInAttrs2| 当前实体属性二值 |(字符串) s2 扩展字典数据| |gInAttrs3| 当前实体属性三值 |(字符串) s3 不同实体表达不一样| |gInAttrs4| 当前实体属性四值 |(字符串) s4 字符型属性数据, 不同实体表达不一样| |gInAttrs5| 当前实体属性五值 |(字符串) s5 字符型属性数据, 不同实体表达不一样| |gInAttrn6| 当前实体属性六值 |(数值型) n1 颜色索引| |gInAttrn7| 当前实体属性七值 |(数值型) n2 线型比例| |gInAttrn8| 当前实体属性八值 |(数值型) n3 线宽| |gInAttrn9| 当前实体属性九值 |(数值型) n4 透明度| |gInAttrn10| 当前实体属性十值 |(数值型) n5 数值型属性数据, 不同实体表达不一样| |gInAttrn11| 当前实体属性11值 |(数值型) n6 不同实体表达不一样| |gInAttrn12| 当前实体属性12值 |(数值型) n7 不同实体表达不一样| |gInAttrn13| 当前实体属性13值 |(数值型) n8 不同实体表达不一样| |gInAttrn14| 当前实体属性14值 |(数值型) n9 不同实体表达不一样| |gInAttrn15| 当前实体属性15值 |(数值型) n10 不同实体表达不一样| |gInCoordX| 当前实体坐标X数组 |(数值型数组)|[10,20...] gInCoordX[0]=10| |gInCoordY| 当前实体坐标Y数组 |(数值型数组)|[10,20...] gInCoordY[0]=10| |gInCoordZ| 当前实体坐标Z数组 |(数值型数组)|[10,20...] gInCoordZ[0]=10| |gInCoordCount| 当前实体坐标个数 |(数值型)|2| |gOutColorRed| 返回的颜色(红色分量)(修改当前实体颜色) |(数值型)|255,如gOutColorRed := 255;表示所有级别红色分量为255; 也可以是数组表示,如gOutColorRed[0] := 122;gOutColorRed[5] := 200;表示1-4级别红色分量是122,5级别以上是红色分量200 |gOutColorGreen| 返回的颜色(红色分量)(修改当前实体颜色) |(数值型)|255,用法同上 |gOutColorBlue| 返回的颜色(红色分量)(修改当前实体颜色) |(数值型)|255,用法同上 |gOutColorAlpha| 返回的颜色(透明度分量)(修改当前实体颜色) |(数值型)|255,用法同上 |gOutLineWidth| 返回的线宽值(修改当前实体线宽值) |(数值型)|3,用法同上 |gOutVisible| 返回是否可见(修改当前实体是否可见) |(数值型)|1或0,用法同上 |gOutReturn| 0 不加入选择集;1加入选择集 | ## 查询数据表达式 同上 ## 支持的CAD实体类型 | 类名称 |说明| 类型值 | |:---------------------------|:---|:----| | AcDbLine |直线| 1 | | AcDbPolyline |多段线| 2 | | AcDb2dPolyline |二维折线| 3 | | AcDb3dPolyline |三维多段线| 4 | | AcDbSpline |样条曲线| 5 | | AcDbArc |圆弧| 6 | | AcDbCircle |圆| 7 | | AcDbEllipse |椭圆| 8 | | AcDbCurve |曲线| 9 | | AcDbBlockReference |块参照| 10 | | AcDbHatch |填充| 11 | | AcDbMText |多行文本| 12 | | AcDbText |单行文本| 13 | | AcDbShape |型实体| 14 | | AcDbRasterImage |栅格图片| 15 | | AcDbWipeout |遮罩实体| 16 | | AcDb2LineAngularDimension |角度标注[两条线]| 17 | | AcDb3PointAngularDimension | 角度标注[三点]| 18 | | AcDbAlignedDimension |对齐标注| 19 | | AcDbArcDimension |圆弧标注| 20 | | AcDbDiametricDimension |直径标注| 21 | | AcDbOrdinateDimension |坐标标注| 22 | | AcDbRadialDimension |半径标注| 23 | | AcDbRadialDimensionLarge |半径折线标注| 24 | | AcDbRotatedDimension |转角标注| 25 | | AcDbAttributeDefinition |属性注记| 26 | | AcDbAttribute |块属性| 27 | | AcDbTable |表格| 28 | | AcDbMLeader |引线标注| 29 | | AcDbRegion |面域| 30 | | AcDbPolyFaceMesh |多面网格实体| 31 | | AcDbPolygonMesh |多边形网格| 32 | | AcDbSurface |曲面| 33 | | AcDb3dSolid |三维实体| 34 | | AcDbFace |三维面| 35 | | AcDbSolid |实线实体| 36 | ## 查询返回字段 | 字段名称 | 说明 | |:--------------|:--------------------------------------| | objectid | 实体唯一id,图更新后也不会变化 | | layerindex | 图层索引 | | name | 实体类型 | | geojson | 坐标 WKT 墨卡托坐标格式 | | envelop | 外包矩形 (WKT 墨卡托坐标格式 ) | | bounds | 外包矩形(CAD坐标)([minx,miny,maxx, maxy] ) | | isEnvelop | 查询时是否按真正的范围计算,还是按外包盒(如果点太多,点选时会按范围返回) | | color | 实体颜色 | | linetype | 线型 | | linetypeScale | 线型比例 | | lineWidth | 线宽 | | alpha | 透明度 | | xdata | 扩展数据 | | points | 坐标序列 | | indices | 点索引序列 | | thickness | 厚度 | | area | 面积 | | isclosed | 是否闭合 | | polyType | 多段线类型 | | center | 圆中心点 | | radius | 圆半径 | | startAngle | 圆弧开始角度 | | endAngle | 圆弧结束角度 | | rotate | 旋转角度 | | positon | 位置 | | normal | 法向量 | | elevation | 高度 | | length | 长度 | | location | 位置 | | contents | 文本内容(多行文本时有样式内容) | | height | 文本高 | | width | 文本宽 | | textHeight | 文字高度 | | text | 文本内容 | | actualHeight | 多行文本实际高 | | actualWidth | 多行文本实际宽 | | bulge | 凸度 | | origin | 基点 | | u | uv中的u | | v | uv中的v | | size | 大小 | | isFit | 是否拟合 | | fitTol | 拟合值 | | degree | 度数 | | horzMode | 水平对齐模式 | | majorAxis | 轴 | | data3d | 3d实体等材质属性数据 | ## 实体类型各字段含义 s1 线型(字符型); s2 扩展字典数据(字符型); n1 颜色索引; n2 线型比例; n3 线宽; n4 透明度; | 类名称 | 类说明 | 类型值 | s3(字符型) | s4(字符型) | s5(字符型) | n5 | n6 | n7 | n8 | n9 | n10 | |:---------|:----------|:----|:--------|:----|:----------|:----------|:-------------|:--------------|:----|:----|:----| | AcDbLine | 直线 | 1 | points | | | thickness | length | | | | | | AcDbPolyline | 多段线 | 2 | points | bulge | data3d | area | isclosed | linetypeScale | thickness | elevation | | | AcDb2dPolyline | 二维折线 | 3 | points | | | area | isclosed | polyType | thickness | elevation | | | AcDb3dPolyline | 三维多段线 | 4 |points | | | area | isclosed | polyType | | | | | AcDbSpline | 样条曲线 | 5 | points | | | area | isclosed | isFit | fitTol |degree | | | AcDbArc | 圆弧 | 6 | center | | | area | isclosed | radius | startAngle | endAngle | thickness | | AcDbCircle | 圆 | 7 | center | | | area | isclosed | radius | thickness | | | | AcDbEllipse | 椭圆 | 8 | center | majorAxis | | area | isclosed | radius | startAngle |endAngle | | | AcDbCurve | 曲线 | 9 | | | | area | isclosed | | | | | | AcDbBlockReference | 块参照 | 10 | positon | attributeDef | blockname | rotate | scaleX | scaleY | scaleZ | | | | AcDbHatch | 填充 | 11 | points |patternName | | elevation | patternAngle | patternScale | | | | | AcDbMText | 多行文本 | 12 | location | contents | text | height | width | rotate | textHeight | actualHeight | actualWidth | | AcDbText | 单行文本 | 13 | location |text | | height | rotate | horzMode | | | | | AcDbShape | 型实体 | 14 | positon | normal | | rotate | size | | | | | | AcDbRasterImage | 栅格图片 | 15 | origin | | | | | | | | | | AcDbWipeout | 遮罩实体 | 16 | | | | | | | | | | | AcDb2LineAngularDimension | 角度标注[两条线] | 17 | | | | | | | | | | | AcDb3PointAngularDimension | 角度标注[三点] | 18 | | | | | | | | | | | AcDbAlignedDimension | 对齐标注 | 19 | | | | | | | | | | | AcDbArcDimension | 圆弧标注 | 20 | | | | | | | | | | | AcDbDiametricDimension | 直径标注 | 21 | | | | | | | | | | | AcDbOrdinateDimension | 坐标标注 | 22 | | | | | | | | | | | AcDbRadialDimension | 半径标注 | 23 | | | | | | | | | | | AcDbRadialDimensionLarge | 半径折线标注 | 24 | | | | | | | | | | | AcDbRotatedDimension | 转角标注 | 25 | | | | | | | | | | | AcDbAttributeDefinition | 属性注记 | 26 | location |text | contents | height | rotate | horzMode | | | | | AcDbAttribute | 块属性 | 27 | location |text | contents | height | rotate | horzMode | | | | | AcDbTable | 表格 | 28 | positon | attributeDef | contents | rotate | scaleX | scaleY | scaleZ | | | | AcDbMLeader | 引线标注 | 29 | location | contents | text | height | width | rotate | textHeight |actualHeight | actualWidth | | AcDbRegion | 面域 | 30 | points | indices | data3d | area | perimeter | | | | | | AcDbPolyFaceMesh | 多面网格实体 | 31 | points | indices | data3d | | | | | | | | AcDbPolygonMesh | 多边形网格 | 32 | points |indices | data3d | | | | | | | | AcDbSurface | 曲面 | 33 | points | indices | data3d | | | | | | | | AcDb3dSolid | 三维实体 | 34 | points | indices| data3d | | | | | | | | AcDbFace | 三维面 | 35 | points | indices | data3d | | | | | | | | AcDbSolid | 实体实体 | 36 | points | | | thickness | length | isclosed | | | | ## 内置样式函数 ```c // gRed(inColor) 取颜色红色分量 function_t( "gRed", "var color := inColor % 256 ; " "color ", "inColor"); // gGreen(inColor) 取颜色绿色分量 function_t( "gGreen", "var color := inColor% (256 * 256) / 256; " "color ", "inColor"); // gBlue(inColor) 取颜色蓝色分量 function_t( "gBlue", "var color := inColor % (256 * 256 * 256) / 256 / 256 ; " "color ", "inColor"); // gFilterGrey(r, g, b) 灰度滤镜(取三种颜色的平均值) function_t( "gFilterGrey", "var grey := round(( clrRed + clrGreen + clrBlue) / 3);" "var color := grey + grey * 256 + grey * 256 * 256; " "color ", "clrRed", "clrGreen", "clrBlue"); // gFilterGreyMin(r, g, b) 灰度滤镜(取三种颜色的最小) function_t( "gFilterGreyMin", "var grey := round(min(clrRed,clrGreen,clrBlue);" "var color := grey + grey * 256 + grey * 256 * 256; " "color ", "clrRed", "clrGreen", "clrBlue"); // gFilterGreyMax(r, g, b) 灰度滤镜(取三种颜色的最大) function_t( "gFilterGreyMax", "var grey := round(max(clrRed,clrGreen,clrBlue);" "var color := grey + grey * 256 + grey * 256 * 256; " "color ", "clrRed", "clrGreen", "clrBlue"); // gFilterWeight(r, g, b) 灰度滤镜(加权平均值) function_t( "gFilterWeight", "var grey := round(0.3 * clrRed + 0.59 * clrGreen + 0.11 * clrBlue);" "var color := grey + grey * 256 + grey * 256 * 256; " "color ", "clrRed", "clrGreen", "clrBlue"); // gFilterBlackWhite(r, g, b) 黑白滤镜 function_t( "gFilterBlackWhite", "var grey := round(( clrRed + clrGreen + clrBlue) / 3); " "var color := if(grey >= 100, 16777215, 0); " "color ", "clrRed", "clrGreen", "clrBlue"); // gFilterOpposite(r, g, b) 反向滤镜 function_t( "gFilterOpposite", "var red := round(255 - clrRed);" "var green := round(255 - clrGreen);" "var blue := round(255 - clrBlue);" "var color := blue + green * 256 + red * 256 * 256; " "color ", "clrRed", "clrGreen", "clrBlue"); // gFilterMaxAvg(r, g, b) 去色滤镜 rgb三种颜色取三种颜色的最值的平均值。 function_t( "gFilterMaxAvg", "var grey := round((max(clrRed,clrGreen,clrBlue) + min(clrRed,clrGreen,clrBlue))/2);" "var color := grey + grey * 256 + grey * 256 * 256; " "color ", "clrRed", "clrGreen", "clrBlue"); // gFilterRed(r, g, b) 单色滤镜 只保留红色 function_t( "gFilterRed", "var red := clrRed;" "var green := 0;" "var blue := 0;" "var color := blue + green * 256 + red * 256 * 256; " "color ", "clrRed", "clrGreen", "clrBlue"); // gFilterGreen(r, g, b) 单色滤镜 只保绿色 function_t( "gFilterGreen", "var red := 0;" "var green := clrGreen;" "var blue := 0;" "var color := blue + green * 256 + red * 256 * 256; " "color ", "clrRed", "clrGreen", "clrBlue"); // gFilterBlue(r, g, b) 单色滤镜 只保留蓝色 function_t( "gFilterBlue", "var red := 0;" "var green := 0;" "var blue := clrBlue;" "var color := blue + green * 256 + red * 256 * 256; " "color ", "clrRed", "clrGreen", "clrBlue"); // gFilterRemoveRed(r, g, b) 滤镜去掉红色 function_t( "gFilterRemoveRed", "var red := 0;" "var green := clrGreen;" "var blue := clrBlue;" "var color := blue + green * 256 + red * 256 * 256; " "color ", "clrRed", "clrGreen", "clrBlue"); // gFilterRemoveGreen(r, g, b) 滤镜去掉绿色 function_t( "gFilterRemoveGreen", "var red := clrRed;" "var green := 0;" "var blue := clrBlue;" "var color := blue + green * 256 + red * 256 * 256; " "color ", "clrRed", "clrGreen", "clrBlue"); // gFilterRemoveBlue(r, g, b) 滤镜去掉蓝色 function_t( "gFilterRemoveBlue", "var red := clrRed;" "var green := clrGreen;" "var blue := 0;" "var color := blue + green * 256 + red * 256 * 256; " "color ", "clrRed", "clrGreen", "clrBlue"); // gFilterOld(r, g, b) 怀旧滤镜 function_t( "gFilterOld", "var red := round(0.393 * clrRed + 0.769 * clrGreen + 0.189 * clrBlue);" "var green := round(0.349 * clrRed + 0.686 * clrGreen + 0.168 * clrBlue);" "var blue := round(0.272 * clrRed + 0.534 * clrGreen + 0.131 * clrBlue);" "red := min(red, 255);" "green := min(green, 255);" "blue := min(blue, 255);" "var color := blue + green * 256 + red * 256 * 256; " "color ", "clrRed", "clrGreen", "clrBlue"); // gFilterFusion(r, g, b) 熔铸滤镜 function_t( "gFilterFusion", "var red := round(clrRed * 128 / (clrGreen + clrBlue + 1);" "var green := round(clrGreen * 128 / (clrRed + clrBlue + 1);" "var blue := round(clrBlue * 128 / (clrGreen + clrRed + 1);" "red := min(red, 255);" "green := min(green, 255);" "blue := min(blue, 255);" "var color := blue + green * 256 + red * 256 * 256; " "color ", "clrRed", "clrGreen", "clrBlue"); // gFilterFrozen(r, g, b) 冰冻滤镜 function_t( "gFilterFrozen", "var red := round((clrRed - clrGreen -clrBlue) * 3 /2);" "var green := round((clrGreen - clrRed -clrBlue) * 3 /2);" "var blue := round( (clrBlue - clrGreen - clrRed) * 3 /2);" "red := min(red, 255);" "green := min(green, 255);" "blue := min(blue, 255);" "red := max(red, 0);" "green := max(green, 0);" "blue := max(blue, 0);" "var color := blue + green * 256 + red * 256 * 256; " "color ", "clrRed", "clrGreen", "clrBlue"); // gFilterComic(r, g, b) 连环画滤镜 function_t( "gFilterComic", "var red := round(abs(clrGreen - clrBlue + clrGreen + clrRed) * clrRed / 256);" "var green := round(abs(clrBlue -clrGreen + clrBlue + clrRed) * clrRed / 256);" "var blue := round(abs(clrBlue -clrGreen + clrBlue + clrRed) * clrGreen / 256);" "red := min(red, 255);" "green := min(green, 255);" "blue := min(blue, 255);" "red := max(red, 0);" "green := max(green, 0);" "blue := max(blue, 0);" "var color := blue + green * 256 + red * 256 * 256; " "color ", "clrRed", "clrGreen", "clrBlue"); // gFilterBrown(r, g, b) 褐色滤镜 function_t( "gFilterBrown", "var red := round(clrRed * 0.393 + clrGreen * 0.769 + clrBlue * 0.189);" "var green := round(clrRed * 0.349 + clrGreen * 0.686 + clrBlue * 0.168);" "var blue := round(clrRed * 0.272 + clrGreen * 0.534 + clrBlue * 0.131);" "red := min(red, 255);" "green := min(green, 255);" "blue := min(blue, 255);" "var color := blue + green * 256 + red * 256 * 256; " "color ", "clrRed", "clrGreen", "clrBlue"); // gFilterBlueTheme(r, g, b) 蓝色主题滤镜 function_t( "gFilterBlueTheme", "var red := round(clrRed * 0.7);" "var green := round(clrGreen * 0.7);" "var blue := clrBlue;" "blue := max(100, blue);" "var color := blue + green * 256 + red * 256 * 256; " "color ", "clrRed", "clrGreen", "clrBlue"); // gFilterRedTheme(r, g, b) 红色主题滤镜 function_t( "gFilterRedTheme", "var red := clrRed;" "var green := round(clrGreen * 0.7);" "var blue := round(clrBlue * 0.7);" "red := max(100, red);" "var color := blue + green * 256 + red * 256 * 256; " "color ", "clrRed", "clrGreen", "clrBlue"); // gFilterGreenTheme(r, g, b) 绿色主题滤镜 function_t( "gFilterGreenTheme", "var red := round(clrRed * 0.7);" "var green := clrGreen;" "var blue := round(clrBlue * 0.7);" "green := max(100, green);" "var color := blue + green * 256 + red * 256 * 256; " "color ", "clrRed", "clrGreen", "clrBlue"); // gFilterCustomTheme(r, g, b, maxr, maxg, maxb) 自定义主题滤镜 function_t( "gFilterCustomTheme", "var red := if(maxr > 1, max(maxr, clrRed), round(clrRed * maxr);" "var green := if(maxg > 1, max(maxg, clrGreen), round(clrGreen * maxg);" "var blue := if(maxb > 1, max(maxb, clrBlue), round(clrBlue * maxb);" "var color := blue + green * 256 + red * 256 * 256; " "color ", "clrRed", "clrGreen", "clrBlue", "maxr", "maxg", "maxb"); ``` --- # 地图服务Rest接口 ## 介绍 地图服务采用的是Rest的服务接口,前端开发可采用封装好的`vjmap` `js` `sdk`中的`Service`来调用相关服务。如获取一个地图的元数据,可采用以下代码 ```js let svc = new vjmap.Service(env.serviceUrl, env.accessToken); let res = await svc.metadata("sys_zp" /*地图id*/, "v1" /*地图版本号,为空表示最新版本*/); console.log(res) ``` 上面代码在浏览器中执行时,F12在网络请求中,可以看到,是给地图服务发送了一个`http`请求. ![image-20230524140958184](https://vjmap.com/blogimages/image-20230524140958184.png) 所以在`后端`如果调用相同的功能,只需发送同样的`http`请求即可。 下面在`PostMan`中进行测试下 ![image-20230524141331530](https://vjmap.com/blogimages/image-20230524141331530.png) 测试发现,通过`PostMan`中调用地图服务的`rest`接口,返回了正确的结果。 需要注意的是:`所有的服务调用`要需指定是`application/json`,同时要在`请求头`里面带上`Token`字段 或者 如果是`Get`请求,则加上`token=xxxx`的查询参数,用于`鉴权`, 如需打开`有密码保护的图`同时不提示输入密码,请在请求头中加入`secretKey`或`accessKey`。(下面的所有示例中将不在重复这条,实际中记得加Token) ## 常用服务接口 (为简便起见,下面的服务地址只写相对地址。如`https://vjmap.com/server/api/v1/map/cmd/metadata/sys_zp/v1`将简写为`/api/v1/map/cmd/metadata/sys_zp/v1`) ### 上传图形 | | | |:-----|:--------------------| | 服务地址 | /api/v1/map/uploads | | 请求类型 | Post | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | fileToUpload | 文件二进制内容 || ### 创建或打开图形 | | | |:-----|:--------------------| | 服务地址 | /api/v1/map/openmap/{mapid} | | 请求类型 | Get或Post | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | version | 文件二进制内容 | | | layer | 样式 | | | geom | 以几何渲染方式打开 | true | | fileid | 文件唯一ID. 地图ID第一次打开时,需传递fileid | cfbe8dc085fb | | imageLeft | 图像左上角坐标x 第一次打开图像类型时有效 | undefined | | imageTop | 图像左上角坐标y 第一次打开图像类型时有效 | | | imageResolution | 图像分辨率 第一次打开图像类型时有效 | | | uploadname | 上传时的文件名 | zp.dwg | | mapfrom | 地图来源参数(协同图形有效) | | | mapdependencies | 地图依赖项(协同图形有效) | | | subfrom | 地图来源参数(子项的设置)(协同图形有效) | | | subdependencies | 地图依赖项(子项的设置)(协同图形有效) | | | stylename | 样式图层名称 | | | layeron | 要开的图层索引列表,格式如[0,1,3] | | | layeroff | 要关的图层索引列表,格式如[2,4] | | | clipbounds | 地图裁剪范围,范围如[x1,y1,x2,y2] | | | bkcolor | 背景颜色 | 0 | | lineweight | 线宽,格式如[1,1,1,1,0] | | | expression | 样式表达式 | | | secretKey | 如第一次创建图时要对图加密可设置此key | | | accessKey | 如图设置了密码需访问key | | 注:第一次上传图形时,如需设置密码,需设置`secretKey`值。计算方式为对密码明文四次md5加密,取加密后的字符串前16位。 如用 `nodejs` 在后台上传图形并创建打开图示例 ```js // app.js const fs = require('fs') const FormData = require('form-data') const axios = require('axios') const prompt = require('prompt'); (async ()=>{ const defaultServiceUrl = 'https://vjmap.com/server/api/v1'; const defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MiwiVXNlcm5hbWUiOiJhZG1pbjEiLCJOaWNrTmFtZSI6ImFkbWluMSIsIkF1dGhvcml0eUlkIjoiYWRtaW4iLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjo0ODEzMjY3NjM3LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTY1OTY2NjYzN30.cDXCH2ElTzU2sQU36SNHWoTYTAc4wEkVIXmBAIzWh6M'; let formData = new FormData(); let { filepath } = await prompt.get('filepath'); // 请输入要上传的文件路径 if (!filepath) return; let file = fs.createReadStream(filepath) formData.append('fileToUpload', file); let len = await new Promise((resolve, reject) => { return formData.getLength((err, length) => (err ? reject(err) : resolve(length))); }); let res = await axios({ url: `${defaultServiceUrl}/map/uploads?token=${defaultAccessToken}`, method: 'POST', data: formData, headers: { ...formData.getHeaders(), 'Content-Length': len, }, }); /* { fileid: 'c036d8ca23eb', mapid: 'c036d8ca23eb', // mapid是建议的mapid,如果之前上传打开过,则为之前的mapid uploadname: 'base1.dwg' } */ console.log(res.data); // 上传只是把图上传到了服务器, 您新建地图的时候的,第一次的时候openMap要传上传的fileid,然后给新建的图取个mapid打开就可以了,以后就可以直接通过mapid打开图了 let { mapid } = await prompt.get('mapid'); // 请输入要上传的文件路径 if (!mapid) { // 如果没有输入mapid,则用上传的时候返回的默认mapid mapid = res.data.mapid; } let url = `${defaultServiceUrl}/map/openmap/${res.data.mapid}?version=&layer=&geom=true&fileid=${res.data.fileid}&uploadname=${res.data.uploadname}&mapfrom=&mapdependencies=&subfrom=&subdependencies=&stylename=&layeron=&layeroff=&clipbounds=&bkcolor=0&lineweight=&expression=`; // 如需设置密码,如密码是123456 // md5四次加密 strMd5是字符串md5值计算方法,请自行实现 // let secretKey = strMd5(strMd5(strMd5(strMd5("123456")))).substr(0, 16); // url += "&secretKey=" + secretKey let result = await axios({ url: url, method: 'GET', headers: { token: defaultAccessToken, }, }); console.log(result) })(); ``` 如果是需要在后台根据`参数创建图形`,则需要把`创建的实体对象`生成一个`json`对象,同时对`json序列化成字符串`转成`base64`编码,做为` fileid`传入 用nodejs在后台参数创建图形代码如下: ```js const axios = require("axios"); (async () => { const defaultServiceUrl = "https://vjmap.com/server/api/v1"; const defaultAccessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MiwiVXNlcm5hbWUiOiJhZG1pbjEiLCJOaWNrTmFtZSI6ImFkbWluMSIsIkF1dGhvcml0eUlkIjoiYWRtaW4iLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjo0ODEzMjY3NjM3LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTY1OTY2NjYzN30.cDXCH2ElTzU2sQU36SNHWoTYTAc4wEkVIXmBAIzWh6M"; let doc = {}; let entitys = []; // 具体的参数请查看官网文档 https://vjmap.com/doc/Class_DbDocument.html entitys.push({ typename: "DbLine", start: [14, 0], end: [14, 15], colorIndex: 1, }); entitys.push({ typename: "DbCircle", center: [14, 7.5], radius: 1.83, color: 0xff0000, }); entitys.push({ typename: "DbText", position: [14, 16], contents: "篮球场示意图", colorIndex: 1, horizontalMode: 4, height: 1, }); doc.entitys = entitys; // 转base64的方法也可以,但不推荐了,直接转json字符就可以了 //var bytes = new Buffer(JSON.stringify(doc)); //var base64 = "data:application/dwg;base64," + bytes.toString('base64')// let fileid = base64; let fileid = JSON.stringify(doc); // 上传只是把图上传到了服务器, 您新建地图的时候的,第一次的时候openMap要传上传的fileid,然后给新建的图取个mapid打开就可以了,以后就可以直接通过mapid打开图了 let mapid = "mybackendmap" let url = `${defaultServiceUrl}/map/updatemap/${mapid}`; let data = { fileid: fileid, clipbounds: '', bkcolor: 0, geom: true, uploadname: "" } // 如需设置密码,如密码是123456 // md5四次加密 strMd5是字符串md5值计算方法,请自行实现 // let secretKey = strMd5(strMd5(strMd5(strMd5("123456")))).substr(0, 16); // url += "&secretKey=" + secretKey let result = await axios({ url: url, method: "POST", headers: { token: defaultAccessToken, }, data }); console.log(result); })(); ``` 执行结果如下: ![image-20230524172844505](https://vjmap.com/blogimages/image-20230524172844505.png) ### 检查文件是否上传过 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/mapfile | | 请求类型 | Get | | 服务说明 | 此方法是在文件上传时用来检测后台是否有相同的文件,如果有则不再上传直接返回fileid,提高效率。实际中也可省去此步骤 | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | md5 | 文件md5值 |fbe8dc085fbcdac502c640cf2dd8065c| 获取文件md5值的方法node实现为(其他后端也可以参考同样实现) ```js import SparkMD5 = require("spark-md5"); /** * 获取文件的Md5值 * @param file * @return {Promise} */ function fileMd5(file: File): Promise { return new Promise((resolve, reject) => { const blobSlice = // @ts-ignore File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice; const chunkSize = 2097152; // Read in chunks of 2MB const chunks = Math.ceil(file.size / chunkSize); let currentChunk = 0; const spark = new SparkMD5.ArrayBuffer(); const fileReader = new FileReader(); fileReader.onload = function(e) { // @ts-ignore spark.append(e.target.result); // Append array buffer currentChunk++; if (currentChunk < chunks) { loadNext(); } else { var md5 = spark.end(); resolve(md5); } }; fileReader.onerror = function() { reject("file reader error"); }; function loadNext() { var start = currentChunk * chunkSize; var end = start + chunkSize >= file.size ? file.size : start + chunkSize; fileReader.readAsArrayBuffer(blobSlice.call(file, start, end)); } loadNext(); }); } ``` ### 更新图形 | | | |:-----|:--------------------| | 服务地址 | /api/v1/map/updatemap/{mapid} | | 请求类型 | Get或Post | 参数同`创建或打开图形` ### 栅格瓦片地址 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/tile/{mapid}/{version}/{stylename}/{zoom}/{x}/{y} | | 请求类型 | Get | | 服务说明 | mapid地图id;version版本号; stylename当前样式名;zoom当前级别;xy当前瓦片行列 | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | tag | fileid || ### 矢量瓦片地址 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/tile/{mapid}/{version}/{stylename}/{zoom}/{x}/{y}.mvt | | 请求类型 | Get | | 服务说明 | mapid地图id;version版本号; stylename当前样式名;zoom当前级别;xy当前瓦片行列 | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | tag | fileid || ### 执行地图命令 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/${cmdname}/${mapid}/${version}| | 请求类型 | Get 或 Post | | 服务说明 | cmdname命令名mapid地图id;version版本号; | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | 不同的命名名称参数不一样 | || ### 获取地图元数据 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/metadata/${mapid}/${version}| | 请求类型 | Get 或 Post | | 服务说明 | mapid地图id;version版本号; | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | geom | 是否是几何渲染 || ### 修改地图元数据 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/updateMetadata/${mapid}/${version}| | 请求类型 | Get 或 Post | | 服务说明 | mapid地图id;version版本号; | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | 元数据key值 | 元数据key对应的value值 || ### 主动关闭打开的地图 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/closemap/${mapid}/${version}| | 请求类型 | Get 或 Post | | 服务说明 | mapid地图id;version版本号; | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | 元数据key值 | 元数据key对应的value值 || ### 获取地图列表 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/listmaps/${mapid}/${version}| | 请求类型 | Get 或 Post | | 服务说明 | mapid地图id;为空,则获取所有的;如果传入的是地图ID数组,则获取指定的地图ID数组的信息。如果不想一次性获取,可通过传入分页对象获取,如{curPage: 1, pageCount: 10} ; version版本号,为空,则获取最新的; * 则获取所有的版本| | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | mapIds | mapid为空时,可指定地图id数组查询 || ### 查询实体 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/queryFeatures/${mapid}/${version}| | 请求类型 | Get 或 Post | | 服务说明 | mapid地图id; version版本号| | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:----------------------------------------------------------------------------------------------------| | querytype |查询类型,支持条件查询"condition",矩形范围查询"rect",点查询"point", 表达式查询"expresion"| condition | | beginpos | 记录开始位置 | 0 | | bounds | 范围 | "" | | condition | 查询条件 | layerindex = 0 | | fields | 是返回的字段列表,多个之是用逗号,分开,如. "name,objectid" | | | geom | 是否为几何图形查询 | true | | includegeom |是否返回几何数据,为了性能问题,realgeom为false时,如果返回条数大于1.只会返回每个实体的外包矩形,如果条数为1的话,会返回此实体的真实geojson;realgeom为true时每条都会返回实体的geojson | true | | isContains |是否为包含关系, true为包含关系,false为相交关系,默认false. (传入了bounds进行范围查询时有效) | false | | layername | 样式名称| 必填项,可通过createMapStyle接口返回的stylename,如api/v1/map/cmd/createMapStyle/yourMapid/v1?geom=true&token=xxx | |maxReturnCount| 返回最多的记录条数 | 50000 | | realgeom | 是否返回真实实体几何geojson.与 includegeom参数,结合使用。参考includegeom的用法 | true | | expr | 表达式 | | | x | 点查询x坐标 | | | y | 点查询y坐标 | | | pixelsize | 点查询像素大小 | | | zoom | 当前级别 | 1 | | toMapCoordinate | 查询返回的坐标默认为墨卡托坐标,返回会程序会自动转化为cad坐标,如果要直接返回cad地图坐标,需要把toMapCoordinate设置为true. | | js中sdk查询相关源码如下 ```js /** * 查询实体参数 */ export interface IQueryBaseFeatures { /** 当前级别. */ zoom?: number; /** 地图ID(为空时采用当前打开的mapid). */ mapid?: string; /** 地图版本(为空时采用当前打开的地图版本). */ version?: string; /** 图层名称(为空时采用当前打开的地图图层名称). */ layer?: string; /** 返回最多的记录条数. */ limit?: number; /** 是返回的字段列表,多个之是用逗号,分开,如. "name,objectid" */ fields?: string; /** 是否为几何图形查询. */ geom?: boolean; /** GeoJSON几何数据简化墨托卡距离,默认为零,不简化。例如允许10级别以上一个像素级别的误差,可用 map.pixelToGeoLength(1, 10) * vjmap.Projection.EQUATORIAL_SEMIPERIMETER * 2 / map.getGeoBounds(1.0).width() */ simplifyTolerance?: boolean; /** 启动cache(内存打开的图形有效). */ useCache?: boolean; /** 查询返回的坐标默认为墨卡托坐标,返回会程序会自动转化为cad坐标,如果要直接返回cad地图坐标,需要把toMapCoordinate设置为true. */ toMapCoordinate?: boolean; } /** * 点查询实体参数 */ export interface IPointQueryFeatures extends IQueryBaseFeatures { /** 查询的坐标X. */ x: number; /** 查询的坐标Y. */ y: number; /** 像素大小. */ pixelsize?: number; /** 条件. */ condition?: string; /** 返回最大的几何字节数. */ maxGeomBytesSize?: number; /** 当前一个像素表示多少几何长度,如果输入了此值,则为此值为主,否则,根据输入的zoom值后台自动计算. */ pixelToGeoLength?: number; } /** * 矩形查询实体参数 */ export interface IRectQueryFeatures extends IQueryBaseFeatures { /** 查询的坐标X1. (如果x1,y1,x2,y2同时不输的话,表示是查询整个图的范围 */ x1?: number; /** 查询的坐标Y1. */ y1?: number; /** 查询的坐标X2. */ x2?: number; /** 查询的坐标Y2. */ y2?: number; /** 条件. */ condition?: string; /** 返回最大的几何字节数. */ maxGeomBytesSize?: number; } /** * 表达式查询实体参数 */ export interface IExprQueryFeatures extends IQueryBaseFeatures { /** 表达式. */ expr: string; /** 记录开始位置. */ beginpos?: number; } /** * 条件查询实体参数 */ export interface IConditionQueryFeatures extends IQueryBaseFeatures { /** 条件. */ condition: string; /** 范围. */ bounds?: [number,number,number,number]; /** 记录开始位置. */ beginpos?: number; /** 是否返回几何数据,为了性能问题,realgeom为false时,如果返回条数大于1.只会返回每个实体的外包矩形,如果条数为1的话,会返回此实体的真实geojson;realgeom为true时每条都会返回实体的geojson */ includegeom?: boolean; /** 是否返回真实实体几何geojson.与 includegeom参数,结合使用。参考includegeom的用法*/ realgeom?: boolean; /** 是否为包含关系, true为包含关系,false为相交关系,默认false. (传入了bounds进行范围查询时有效)*/ isContains?: boolean; } /** * 点查询实体 * @param param 参数 * @param cb 结果中每个点的处理回调。如果返回空的话,则用默认处理方法 */ async pointQueryFeature(param: IPointQueryFeatures, cb?: (point: [number, number]) => [number, number] | null | undefined): Promise { const res = await this.execCommand( "queryFeatures", { querytype: "point", zoom: param.zoom ?? 1, pixelToGeoLength: param.pixelToGeoLength ?? undefined, layername: param?.layer ?? this._cur_map_param?.layer, x: param.x, y: param.y, pixelsize: param.pixelsize ?? 5, condition: param.condition ?? undefined, maxReturnCount: param.limit ?? undefined, maxGeomBytesSize: param.maxGeomBytesSize ?? undefined, fields: param.fields ?? "", geom: param.geom ?? undefined, simplifyTolerance: param.simplifyTolerance ?? undefined, useCache: param.useCache, toMapCoordinate: param.toMapCoordinate, }, param.mapid, param.version ); return this._processQueryResult(res, cb); } /** * 矩形查询实体 * @param param 参数 * @param cb 结果中每个点的处理回调。如果返回空的话,则用默认处理方法 */ async rectQueryFeature(param: IRectQueryFeatures, cb?: (point: [number, number]) => [number, number] | null | undefined): Promise { const res = await this.execCommand( "queryFeatures", { querytype: "rect", zoom: param.zoom ?? 1, layername: param?.layer ?? this._cur_map_param?.layer, x1: param.x1, y1: param.y1, x2: param.x2, y2: param.y2, condition: param.condition ?? undefined, maxReturnCount: param.limit ?? undefined, maxGeomBytesSize: param.maxGeomBytesSize ?? undefined, fields: param.fields ?? "", geom: param.geom ?? undefined, simplifyTolerance: param.simplifyTolerance ?? undefined, useCache: param.useCache, toMapCoordinate: param.toMapCoordinate, }, param.mapid, param.version ); return this._processQueryResult(res, cb); } /** * 表达式查询实体 * @param param 参数 * @param cb 结果中每个点的处理回调。如果返回空的话,则用默认处理方法 */ async exprQueryFeature(param: IExprQueryFeatures, cb?: (point: [number, number]) => [number, number] | null | undefined): Promise { const res = await this.execCommand( "queryFeatures", { querytype: "expresion", zoom: param.zoom ?? 1, layername: param?.layer ?? this._cur_map_param?.layer, expr: param.expr, beginpos: param.beginpos, maxReturnCount: param.limit ?? undefined, limit: param.limit ?? undefined, fields: param.fields ?? "", useCache: param.useCache ?? false, geom: param.geom ?? undefined, simplifyTolerance: param.simplifyTolerance ?? undefined, toMapCoordinate: param.toMapCoordinate, }, param.mapid, param.version ); return this._processQueryResult(res, cb); } /** * 条件查询实体 * @param param 参数 * @param cb 结果中每个点的处理回调。如果返回空的话,则用默认处理方法 */ async conditionQueryFeature(param: IConditionQueryFeatures, cb?: (point: [number, number]) => [number, number] | null | undefined): Promise { const res = await this.execCommand( "queryFeatures", { querytype: "condition", zoom: param.zoom ?? 1, layername: param?.layer ?? this._cur_map_param?.layer, condition: param.condition, beginpos: param.beginpos, maxReturnCount: param.limit ?? undefined, limit: param.limit ?? undefined, includegeom: param.includegeom ?? undefined, realgeom: param.realgeom ?? undefined, isContains: param.isContains ?? undefined, fields: param.fields ?? "", bounds: param.bounds ? JSON.stringify(param.bounds) : "", geom: param.geom ?? undefined, simplifyTolerance: param.simplifyTolerance ?? undefined, useCache: param.useCache, toMapCoordinate: param.toMapCoordinate, }, param.mapid, param.version ); return this._processQueryResult(res, cb); } // _processQueryResult函数是将返回的墨卡托坐标转cad坐标了。如果后端调用查询请求直接返回cad坐标,请将参数toMapCoordinate设置为true即可。 ``` 同时也可通过图形界面来看请求的参数 [https://vjmap.com/app/visual/#/query?mapid=sys_zp&version=v1&mapopenway=GeomRender&theme=dark](https://vjmap.com/app/visual/#/query?mapid=sys_zp&version=v1&mapopenway=GeomRender&theme=dark) (sys_zp改成自己的mapid) ![image-20230524160509120](https://vjmap.com/blogimages/image-20230524160509120.png) 在postMan中测试下。记得把参数`toMapCoordinate`设置为true,让直接返回cad坐标 ![image-20230524160843420](https://vjmap.com/blogimages/image-20230524160843420.png) ### 切换图层 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/switchlayer/${mapid}/${version}| | 请求类型 | Get 或 Post | | 服务说明 | mapid地图id;version版本号; | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | visibleLayers | 让可见的图层列表数组 || | darkMode | 是否是深色主题 || ### 获取样式图层名 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/createMapStyle/${mapid}/${version}| | 请求类型 | Get 或 Post | | 服务说明 | mapid地图id;version版本号; | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | geom | 是否是几何渲染 || | name | 样式名称 || | layeron | 要开的图层索引列表,格式如[0,1,3] || | layeroff | 要关的图层索引列表,格式如[2,4]. layeron与layeroff只要输入一个即可 || | clipbounds | 地图裁剪范围,范围如[x1,y1,x2,y2].如果只需入了数值的话,表示是缩放倍数 || | backcolor | 背景颜色 || | lineweight | 线宽,格式如[1,1,1,1,0].表式第1,2,3,4级线宽开,第5级线宽关,大于第5级的,以最后设置的级别状态为主,所以也是关。如为空,则和原图线宽显示状态相同 || | expression | 表达式 || ### 获取图形中有数据的范围区域 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/getDataBounds/${mapid}/${version}| | 请求类型 | Get 或 Post | | 服务说明 | mapid地图id;version版本号; | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | 无 | || ### 获取图的缩略图 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/thumbnail/${mapid}/${version}| | 请求类型 | Get 或 Post | | 服务说明 | mapid地图id;version版本号; | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | width | 像素高 || | height | 像素高 || | darkTheme | 是否是深色主题,是的话,图片将反色(黑色变白色) || ### WMS瓦片地址 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/wms/${mapid}/${version}| | 请求类型 | Get 或 Post | | 服务说明 | mapid地图id;version版本号; | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | layers | 图层名称(为空时采用当前打开的地图图层名称). || | bbox | 范围,缺省{bbox-epsg-3857}. (如果要获取地图cad一个范围的wms数据无需任何坐标转换,将此范围填cad范围,srs,crs,mapbounds填为空). || | srs | 当前坐标系,缺省(EPSG:3857) || | crs | cad图的坐标系,为空的时候由元数据坐标系决定. 也可直接输入proj4字符串 || | mapbounds | 地理真实范围,如有值时,srs将不起作用 || | width | 瓦片像素宽 || | height | 瓦片像素高 || | transparent | 不透明时的背景颜色,默认为白色。格式必须为rgb(r,g,b)或rgba(r,g,b,a),a不透明应该是255. || | backgroundColor | 像素高 || | fourParameter | 四参数(x偏移,y偏移,缩放,旋转弧度),可选,对坐标最后进行修正 || | isInverseFourParamter | 是否用上面的四参数进行反算,默认false || | mvt | 是否是矢量瓦片 || | useImageRotate | 是否考虑旋转,在不同坐标系中转换是需要考虑。默认自动考虑是否需要旋转 || | imageProcessAlg | 旋转时图像处理算法. 1或2,默认自动选择(旋转时有用) || | webMapType | 当前互联网底图地图类型 WGS84(84坐标,如天地图,osm), GCJ02(火星坐标,如高德,腾讯地图), BD09LL(百度经纬度坐标,如百度地图), BD09MC(百度墨卡托米制坐标,如百度地图)* || js的源码如下 ```js /** * wms服务url地址接口 */ export interface IWmsTileUrl { /** 地图ID(为空时采用当前打开的mapid), 为数组时表时同时请求多个. */ mapid?: string | string[]; /** 地图版本(为空时采用当前打开的地图版本). */ version?: string | string[]; /** 图层名称(为空时采用当前打开的地图图层名称). */ layers?: string | string[]; /** 范围,缺省{bbox-epsg-3857}. (如果要获取地图cad一个范围的wms数据无需任何坐标转换,将此范围填cad范围,srs,crs,mapbounds填为空).*/ bbox?: string; /** 当前坐标系,缺省(EPSG:3857). */ srs?: string; /** cad图的坐标系,为空的时候由元数据坐标系决定. 也可直接输入proj4字符串*/ crs?: string | string[]; /** 地理真实范围,如有值时,srs将不起作用 */ mapbounds?: string; /** 宽. */ width?: number; /** 高. */ height?: number; /** 是否透明. */ transparent?: boolean; /** 不透明时的背景颜色,默认为白色。格式必须为rgb(r,g,b)或rgba(r,g,b,a),a不透明应该是255. */ backgroundColor?: string; /** 四参数(x偏移,y偏移,缩放,旋转弧度),可选,对坐标最后进行修正*/ fourParameter?: string | string[]; /** 是否用上面的四参数进行反算,默认false*/ isInverseFourParamter?: boolean | boolean[]; /** 是否是矢量瓦片. */ mvt?: boolean; /** 是否考虑旋转,在不同坐标系中转换是需要考虑。默认自动考虑是否需要旋转. */ useImageRotate?: boolean; /** 旋转时图像处理算法. 1或2,默认自动选择(旋转时有用)*/ imageProcessAlg?: number; /** 当前互联网底图地图类型 WGS84(84坐标,如天地图,osm), GCJ02(火星坐标,如高德,腾讯地图), BD09LL(百度经纬度坐标,如百度地图), BD09MC(百度墨卡托米制坐标,如百度地图)*/ webMapType?: "WGS84" | "GCJ02" | "BD09LL" | "BD09MC" } wmsTileUrl(param: IWmsTileUrl): string { const mapid = param.mapid ?? this._cur_map_param?.mapid; const version = param.version ?? ((param.mapid == this._cur_map_param?.mapid) ? this._cur_map_param?.version : "_") ?? "_"; const layers = param.layers ?? this._cur_map_param?.layer; const bbox = param.bbox ?? "{bbox-epsg-3857}"; const srs = param.srs ?? "EPSG:3857";//底图坐标系 let crs = param.crs ?? ""; //cad图坐标系 if (crs.indexOf("+") >= 0) { // proj4字符串 // @ts-ignore crs = encodeURIComponent(crs); } const mapbounds = param.mapbounds ?? ""; const width = param.width ?? 256; const height = param.height ?? 256; const transparent = param.transparent ?? true; const fourParameter = param.fourParameter ?? ""; const isInverseFourParamter = param.isInverseFourParamter === true ? `&isInverseFourParamter=true` : ""; const mvt = param.mvt === true ? ".mvt" : "" const useImageRotate = param.useImageRotate === true ? `&useImageRotate=true` : ""; const imageProcessAlg = isNumber(param.imageProcessAlg) ? `&imageProcessAlg=${param.imageProcessAlg}` : ""; const webMapType = param.webMapType ? `&webMapType=${param.webMapType}` : ""; const backgroundColor = (param.backgroundColor && !transparent) ? `&backgroundColor=${param.backgroundColor}` : ""; if (!Array.isArray(mapid)) { // 如果不是数组,表示只请求一个地图的wms数据 return `${this._url("cmd")}/wms/${mapid}/${version}${mvt}` + `?mapbounds=${mapbounds}${bbox ? "&bbox=": ""}${bbox}&format=image/png&service=WMS&request=GetMap&srs=${srs}&transparent=${transparent}&width=${width}&height=${height}&layers=${layers}&crs=${crs}&fourParameter=${fourParameter}${isInverseFourParamter}${useImageRotate}${imageProcessAlg}${webMapType}${backgroundColor}&token=${this.accessToken}` } else { // 请求多个地图的wms数据 //&layers=${layers}&crs=${crs}&fourParameter=${fourParameter} //${mapid}/${version} let mapidArr = mapid; let versionArr = Array.isArray(version) && version.length == mapidArr.length ? version : mapidArr.map(m => version ?? "_") let layersArr = Array.isArray(layers) && layers.length == mapidArr.length ? layers : mapidArr.map(m => layers ?? "") let crsArr = Array.isArray(crs) && crs.length == mapidArr.length ? crs : mapidArr.map(m => crs ?? "") let fourParameterArr = Array.isArray(fourParameter) && fourParameter.length == mapidArr.length ? fourParameter : mapidArr.map(m => fourParameter ?? "") let isInverseFourParamterArr = Array.isArray(isInverseFourParamter) && isInverseFourParamter.length == mapidArr.length ? isInverseFourParamter : mapidArr.map(m => isInverseFourParamter ?? "") let param = `bbox=${bbox}&mapbounds=${mapbounds}&format=image/png&service=WMS&request=GetMap&srs=${srs}&transparent=${transparent}&width=${width}&height=${height}` let arrayParam = `mapid=${mapidArr.join("_,")}&version=${versionArr.join("_,")}&layers=${layersArr.join("_,")}&crs=${crsArr.join("_,")}&fourParameter=${fourParameterArr.join("_,")}&isInverseFourParamter=${isInverseFourParamterArr.join("_,")}` if (mapidArr.length == 1) { return `${this._url("cmd")}/wms/${mapidArr[0]}/${versionArr[0]}${mvt}` + `?mapbounds=${mapbounds}${bbox ? "&bbox=": ""}${bbox}&format=image/png&service=WMS&request=GetMap&srs=${srs}&transparent=${transparent}&width=${width}&height=${height}&layers=${layersArr[0]}&crs=${crsArr[0]}&fourParameter=${fourParameterArr[0]}&isInverseFourParamter=${isInverseFourParamterArr[0]}${useImageRotate}${imageProcessAlg}${webMapType}${backgroundColor}&token=${this.accessToken}` } else { return `${this._svr_url_service}wms?${param}&${arrayParam}${useImageRotate}${imageProcessAlg}${webMapType}${backgroundColor}&token=${this.accessToken}`; } } } ``` ### 删除地图 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/deletemap/${mapid}/${version}| | 请求类型 | Get 或 Post | | 服务说明 | mapid地图id;version版本号;如删除所有版本,输入"*"号 | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | retainVersionMaxCount | 删除所有版本时,保留的最新的版本总数。如总共有10个版本,retainVersionMaxCount为3时,会保存最新的3个版本,其余的都会删除 || ### 地图重命名 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/deletemap/${mapid}/_| | 请求类型 | Get 或 Post | | 服务说明 | mapid地图id; | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | newMapId | 新的地图id || ### 清空地图的几何和瓦片缓存数据 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/clearCacheTiles/${mapid}/${version}| | 请求类型 | Get 或 Post | | 服务说明 | mapid地图id; version版本号; | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | 无 | || ### 清空地图的瓦片缓存数据 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/clearCacheStyles/${mapid}/${version}| | 请求类型 | Get 或 Post | | 服务说明 | mapid地图id; version版本号; | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | 无 | || ### 删除地图样式 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/deletestyle/${mapid}/${version}| | 请求类型 | Get 或 Post | | 服务说明 | mapid地图id; version版本号; | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | styleid | || | onlycleardata | 仅清空数据 || ### 删除地图缓存 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/deletecache/${mapid}/${version}| | 请求类型 | Get 或 Post | | 服务说明 | mapid地图id; version版本号; | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | key | 缓存前缀 || ### 获取系统运行状态 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/runstatus/${mapid}/${version}| | 请求类型 | Get 或 Post | | 服务说明 | mapid地图id; version版本号; | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | detail | 是否详细数据 || ### 组合成新地图 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/composeNewMap/_null/v1| | 请求类型 | Get 或 Post | | 服务说明 | | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | mapid | 地图ID | | version | 地图版本 | | clipbounds | 地图裁剪范围,范围如[x1,y1,x2,y2], 为空的话,表示不裁剪 || | selByCrossing | 选择是包含还是相交(默认false表示包含,true相交) || | fourParameter | 四参数(x偏移,y偏移,缩放,旋转弧度),可选,对坐标最后进行修正 || | isInverseFourParamter | 是否用上面的四参数进行反算,默认false || | layers | 要显示的图层名称,为空的时候,表示全部图层 || | layerPrefix | 生新成图的图层名称前缀 || | layerSuffix | 生新成图的图层名称后缀 || | savefilename | 保存的文件名称,为空的时候,自根据参数自动生成 || | purge | 生成后清理图形数据以减少dwg文件大小 || | cadVersion | 导出的CAD版本号。如果为*表示为当前CAD图的版本 || js的源码如下 ```js /** * 组合新地图参数 */ export interface IComposeNewMap { /** 地图ID. */ mapid: string; /** 地图版本(为空时采用当前打开的地图版本). */ version?: string; /** 地图裁剪范围,范围如[x1,y1,x2,y2], 为空的话,表示不裁剪 */ clipbounds?: [number, number, number, number]; /** 选择是包含还是相交(默认false表示包含,true相交) */ selByCrossing?: boolean; /** 四参数(x偏移,y偏移,缩放,旋转弧度),可选,对坐标最后进行修正*/ fourParameter?: [number, number, number, number]; /** 是否用上面的四参数进行反算,默认false*/ isInverseFourParamter?: boolean; /** 要显示的图层名称,为空的时候,表示全部图层 */ layers?: string[]; /** 生新成图的图层名称前缀 */ layerPrefix?: string; /** 生新成图的图层名称后缀 */ layerSuffix?: string; /** 保存的文件名称,为空的时候,自根据参数自动生成 */ savefilename?: string; /** 生成后清理图形数据以减少dwg文件大小 */ purge?: boolean /** 导出的CAD版本号。如果为*表示为当前CAD图的版本 */ cadVersion?: string | "*" | "cad2000" | "cad2004" | "cad2007" | "cad2010" | "cad2013"| "cad2018" } /** * 组合成新地图 * @param param 组合参数 * @return {Promise} */ async composeNewMap(param: IComposeNewMap | IComposeNewMap[]) { if (!Array.isArray(param)) { param = [param] // 变成数组,下面统一处理 } let cmdParam: Record = {}; if (param.length > 1) { cmdParam['count'] = param.length; } for(let i = 0; i < param.length; i++) { let paramKey = i === 0 ? "" : ((i + 1) + "") cmdParam['mapid' + paramKey] = param[i].mapid; cmdParam['version' + paramKey] = param[i].version; cmdParam['clipBounds' + paramKey] = Array.isArray(param[i].clipbounds) ? JSON.stringify(param[i].clipbounds) : undefined; cmdParam['selByCrossing' + paramKey] = param[i].selByCrossing; cmdParam['fourParameter' + paramKey] = Array.isArray(param[i].fourParameter) ? JSON.stringify(param[i].fourParameter) : undefined; cmdParam['isInverseFourParamter' + paramKey] = param[i].isInverseFourParamter === true ? true : undefined; cmdParam['layers' + paramKey] = Array.isArray(param[i].layers) ? JSON.stringify(param[i].layers) : undefined; cmdParam['layerPrefix' + paramKey] = param[i].layerPrefix; cmdParam['layerSuffix' + paramKey] = param[i].layerSuffix; cmdParam['savefilename' + paramKey] = param[i].savefilename; cmdParam['purge' + paramKey] = param[i].purge; if (param[i].cadVersion) { cmdParam['cadVersion' + paramKey] = param[i].cadVersion; } } return await this.execCommand("composeNewMap", cmdParam, "_null", "v1", false); } ``` ### 投影prj文件WKT字符转proj4字符串 | | | |:-----|:-----------------------------------| | 服务地址 | api/v1/map/cmd/prjWktToPrj4Str/_/_ | | 请求类型 | Post | | 服务说明 | ; | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | wkt | 投影prj文件WKT字符 | ### 导出布局空间为dwg图 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/exportLayout/_null/v1| | 请求类型 | Get 或 Post | | 服务说明 | mapid地图id; version版本号; | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | mapid | 地图ID | | version | 地图版本(为空时采用当前打开的地图版本) | | layoutIndex | 布局索引,layoutIndex和layoutName只要输入一个就行,layoutIndex优先。如果都为空,则使用dwg当前的布局空间. | | layoutName | 布局名称. layoutIndex和layoutName只要输入一个就行,layoutIndex优先。如果都为空,则使用dwg当前的布局空间 | ### 目标匹配 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/objectMatch/_null/v1| | 请求类型 | Get 或 Post | | 服务说明 | | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | mapid | 地图ID | | version | 地图版本(为空时采用当前打开的地图版本) | | layer | 图层样式名.为空时,将由选择的实体的图层来决定| | mapBounds | 目标匹配的地图范围,默认为全图的数据范围 | | objectIds | 目标匹配的实体objectid数组,用||隔开. | | objectBounds | 目标匹配的实体范围 | | layeron | 目标匹配的地图打开的图层 | | size | 匹配时的图像尺寸,默认10000 | | method | "matchTemplate" | "matchPattern" | | maxCount | 分数,小于此分类将不显示 (0 -1),默认 0.6 | | minReduceArea | 金字塔大小 范围(64-2048).默认256, 方法为matchPattern有效 | | canOverlap | 允许重叠 (默认false). | | maxOverlap | 重叠比例 范围(0-0.8),默认 0.3 | | toleranceAngle | 角度范围(-180, 180)有旋转的时候需要。方法为matchPattern有效. 默认180 | | useToleranceRange | 是否使用角度区间范围,默认false. 方法为matchPattern有效 | | tolerance1 | 角度区间1开始角度. 方法为matchPattern有效 | | tolerance2 | 角度区间1结束角度. 方法为matchPattern有效 | | tolerance3 | 角度区间2开始角度. 方法为matchPattern有效 | | tolerance4 | 角度区间2结束角度. 方法为matchPattern有效 | ### 获取创建实体的几何数据 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/createEntityGeoData/_null/v1| | 请求类型 | Get 或 Post | | 服务说明 | | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | filedoc | 文件文档 | | mapBounds | 图形范围.如果不填的话,则使用全图范围 | | renderAccuracy | 渲染精度,默认1,有时候图形特别大导致圆或圆弧精度不够时,不够光滑,可以先清空之前的缓存数据,再重新上传时,改变渲染精度来使圆或圆弧光滑些。注:提高精度会导致空间数据文件增大,渲染性能下降| | excludeAttribute | 返回的数据中排除属性数据( 默认true) | | useZip | 返回的数据是否启用zip压缩( 默认true) | ### 获取所有工作区 | | | |:-----|:--------------------| | 服务地址 | api/v1/service/getWorkspace| | 请求类型 | Get | | 服务说明 | | ## 工作区切换 上面的请求接口都是默认工作区的接口地址。 如果要切换到不同的工作区中,需要请求头地址统一修改下。 `/api/v1/map/` 统一修改为 `/api/v1/workspace/工作区名称/` 如上传的地址为 `/api/v1/map/uploads` 在工作区`w1`中上传需改为`/api/v1/workspace/w1/uploads` 切换工作区的前端代码实现 ```js /** * 切换至工作区 * @param workspaceName 工作区名称 * @return */ function switchWorkspace(workspaceName) { workspaceName = workspaceName.trim() this._workspaceName = workspaceName; if (workspaceName != "") { // 指定工作区 this._svr_url_map = this.serverUrl + "workspace/" + workspaceName + "/"; } else { // 默认工作区 this._svr_url_map = this.serverUrl + "map/"; } } ``` ### 全文搜索接口 #### 添加单个文档 | | | |:-----|:--------------------| | 服务地址 | /api/v1/service/fullsearch/add | | 请求类型 | Post | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | document | 文档内容,包含以下字段:
- id: 唯一ID
- map_ver: 地图版本
- content: 文字内容
- type: 类型(1:单行文字,2:多行文本,3:属性注记,4:块属性注记,5:图层,6:块名称,7:线型名称,8:填充符号名称,9:用户自定义)
- min_x: 边界最小X坐标
- min_y: 边界最小Y坐标
- max_x: 边界最大X坐标
- max_y: 边界最大Y坐标
- props: 属性(JSON格式)
- create_time: 创建时间
- workspace: 工作区名称
- data: 数据 || #### 批量添加文档 | | | |:-----|:--------------------| | 服务地址 | /api/v1/service/fullsearch/batchAdd | | 请求类型 | Post | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | documents | 文档列表,每个文档包含以下字段:
- id: 唯一ID
- map_ver: 地图版本
- content: 文字内容
- type: 类型(1:单行文字,2:多行文本,3:属性注记,4:块属性注记,5:图层,6:块名称,7:线型名称,8:填充符号名称,9:用户自定义)
- min_x: 边界最小X坐标
- min_y: 边界最小Y坐标
- max_x: 边界最大X坐标
- max_y: 边界最大Y坐标
- props: 属性(JSON格式)
- create_time: 创建时间
- workspace: 工作区名称
- data: 数据 || #### 更新文档 | | | |:-----|:--------------------| | 服务地址 | /api/v1/service/fullsearch/update | | 请求类型 | Put | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | document | 文档内容,包含以下字段:
- id: 唯一ID
- map_ver: 地图版本
- content: 文字内容
- type: 类型(1:单行文字,2:多行文本,3:属性注记,4:块属性注记,5:图层,6:块名称,7:线型名称,8:填充符号名称,9:用户自定义)
- min_x: 边界最小X坐标
- min_y: 边界最小Y坐标
- max_x: 边界最大X坐标
- max_y: 边界最大Y坐标
- props: 属性(JSON格式)
- create_time: 创建时间
- workspace: 工作区名称
- data: 数据 || #### 删除文档 | | | |:-----|:--------------------| | 服务地址 | /api/v1/service/fullsearch/delete | | 请求类型 | Delete | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | id | 文档ID || #### 按地图版本删除文档 | | | |:-----|:--------------------| | 服务地址 | /api/v1/service/fullsearch/deleteByMapver | | 请求类型 | Delete | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | map_ver | 地图版本 || | workspace | 工作区名称 || #### 批量删除文档 | | | |:-----|:--------------------| | 服务地址 | /api/v1/service/fullsearch/batchDelete | | 请求类型 | Post | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | ids | 文档ID数组 || | query | 查询条件 || | map_ver | 地图版本 || | workspace | 工作区 || | type | 文档类型 || | bounding_box | 空间边界框 [minX, minY, maxX, maxY] || | time_range | 时间范围 || #### 按ID前缀删除文档 | | | |:-----|:--------------------| | 服务地址 | /api/v1/service/fullsearch/deleteByIDPrefix | | 请求类型 | Delete | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | id_prefix | ID前缀 || #### 搜索文档 | | | |:-----|:--------------------| | 服务地址 | /api/v1/service/fullsearch/search | | 请求类型 | Get 或 Post | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | query | 搜索关键词 || | map_ver | 地图版本(可选,支持多值逗号分隔,如map1,map2_v2) || | workspace | 工作区(可选,支持多值逗号分隔,如ws1,ws2),如果是全部工作区,请输入* || | type | 文档类型(可选,支持单值、多值、范围、混合模式,如1,2,5或1-3或1-3,6) || | bounding_box | 空间边界框(可选,格式为"minX,minY,maxX,maxY") || | time_range | 时间范围(可选,格式为"开始时间~结束时间"如"2023-01-01~2023-12-31"或数字N表示N天内) || | limit | 每页结果数量 || | offset | 分页偏移量 || | fields | 返回字段(逗号分隔) || | facet | 是否聚合MapVer信息 (true/1 开启聚合) || #### 获取文档数量 | | | |:-----|:--------------------| | 服务地址 | /api/v1/service/fullsearch/count | | 请求类型 | Get | #### 手动触发数据同步 | | | |:-----|:--------------------| | 服务地址 | /api/v1/service/fullsearch/sync | | 请求类型 | Get | #### 获取工作区索引状态 | | | |:-----|:--------------------| | 服务地址 | /api/v1/service/fullsearch/workspaceStatus | | 请求类型 | Get | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | workspace | 工作区名称 || #### 获取搜索建议 | | | |:-----|:--------------------| | 服务地址 | /api/v1/service/fullsearch/suggestions | | 请求类型 | Get | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | prefix | 搜索前缀 || | map_ver | 地图版本 || | workspace | 工作区 || | type | 文档类型 || | limit | 返回建议数量限制 || #### 通过ID前缀搜索文档 | | | |:-----|:--------------------| | 服务地址 | /api/v1/service/fullsearch/searchByIDPrefix | | 请求类型 | Get 或 Post | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | id_prefix | ID前缀 || | map_ver | 地图版本(可选) || | workspace | 工作区(可选) || | limit | 每页数量 || | offset | 偏移量 || | fields | 返回的字段名,多个字段用逗号分隔 || ## 智能识别相关接口 ### 获取识别器配置列表 | | | |:-----|:--------------------| | 服务地址 | /api/v1/service/recognizer/list | | 请求类型 | Get | | 服务说明 | 获取所有保存的识别器配置列表 | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | 无 | || ### 删除识别器配置 | | | |:-----|:--------------------| | 服务地址 | /api/v1/service/recognizer/delete | | 请求类型 | Delete | | 服务说明 | 删除指定的识别器配置文件 | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | filename | 配置文件前缀名称 | config_20231201_001 | ### 添加识别器配置 | | | |:-----|:--------------------| | 服务地址 | /api/v1/service/recognizer/add | | 请求类型 | Post | | 服务说明 | 保存新的识别器配置 | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | name | 配置名称 | 我的识别配置 | | sameColor | 实体对象是否需要同一颜色 | true | | sameLayer | 实体对象是否需要在同一图层 | false | | sameTextContent | 文本内容是否必须相同 | false | | thumbnail | 缩略图base64数据(可选) | data:image/png;base64,... | | global | 全局匹配规则配置 | {...} | | rules | 识别规则数组 | [...] | ### 获取识别器配置详情 | | | |:-----|:--------------------| | 服务地址 | /api/v1/service/recognizer/detail | | 请求类型 | Get | | 服务说明 | 获取指定识别器配置的详细信息 | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | filenames | 配置文件名称,多个用逗号分隔 | config_20231201_001,config_20231201_002 | ### 执行对象智能识别 | | | |:-----|:--------------------| | 服务地址 | /api/v1/map/cmd/objectDetection/_null/v1 | | 请求类型 | Get 或 Post | | 服务说明 | 在指定地图中执行智能对象识别,根据配置的模式识别相似的实体对象 | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | mapid | 地图ID | sys_zp | | version | 地图版本号 | v1 | | layer | 图层样式名称 | default | | pattern | 识别模式配置JSON字符串 | {"name":"测试","sameColor":true,"rules":[...]} | | patternList | 多个识别模式配置数组(与pattern二选一) | ["{"name":"配置1",...}","{"name":"配置2",...}"] | | detailedLog | 是否输出详细匹配信息 | false | | bounds | 识别范围,GeoBounds格式字符串 | -1000,-1000,1000,1000 | ## 图形分析相关接口 ### 提取中心线 | | | |:-----|:--------------------| | 服务地址 | /api/v1/map/cmd/extractCenterLines/_null/v1 | | 请求类型 | Get 或 Post | | 服务说明 | 从平行线段中提取中心线,用于管道、道路等工程制图应用 | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | mapid | 地图ID | sys_zp | | version | 地图版本号 | v1 | | layer | 图层样式名称 | default | | geom | 是否是几何渲染 | true | | bounds | 提取范围,坐标或多边形 | -1000,-1000,1000,1000 | | isContains | 是否为包含关系(true包含,false相交) | false | | minSpaceDistance | 最小距离数组,逗号分隔 | 2.5,5.0 | | maxSpaceDistance | 最大距离数组,逗号分隔 | 7.5,15.0 | | collinearTolerance | 共线容差距离值 | 1.0 | | duplicateTolerance | 去除重复点容差值 | 0.2 | | maxGapDistance | 允许的线段断开距离 | 15 | | numDecimalDigits | 精确到小数点后几位 | 3 | | minDlMinRatio | 双线都重叠的最小比例 | 0.01 | | paralleSlopeAngleTolerance | 判断平行线斜率角度误差(度) | 1.5 | | mergeSlopeAngleTolerance | 合并中心线斜率角度误差(度) | 20 | | maxCenterMergeDistance | 中心线段能合并的最大距离 | 15 | | minCenterDistance | 中心线段允许的最小距离 | 5.1 | | bConnectGrapLine | 是否连接设置允许间隔范围内的线 | true | | bMergeNode | 是否处理合并端点 | true | | bFilterShortLine | 是否排除小于最小距离的线 | true | | bMergeLine | 是否把相连的线合成一条 | true | | bSaveParalleLineJson | 是否显示平行线坐标 | false | | bCenterGeoJson | 中心线是否以GeoJson格式返回 | false | | bExtractLines | 是否显示提取的线坐标 | false | ### 地图实体对比 | | | |:-----|:--------------------| | 服务地址 | /api/v1/map/cmd/mapCompare/_null/v1 | | 请求类型 | Get 或 Post | | 服务说明 | 对比两个地图版本之间的实体差异,返回新增、删除、修改的实体列表 | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | mapid1 | 第一个地图ID | sys_zp | | version1 | 第一个地图版本号 | v1 | | mapid2 | 第二个地图ID | sys_zp | | version2 | 第二个地图版本号 | v2 | ### 地图像素对比 | | | |:-----|:--------------------| | 服务地址 | api/v1/map/cmd/mapDiff/_null/v1| | 请求类型 | Get 或 Post | | 服务说明 | | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | mapid1 | 地图一ID | | version1 | 地图一版本(为空时采用当前打开的地图版本) | | layer1 | 地图一样式图层 | | mapid2 | 地图二ID | | version2 | 地图二版本(为空时采用当前打开的地图版本) | | layer2 | 地图二样式图层 | | noCompareNew | 不比较新增部分 | | noCompareDelete | 不比较删除部分 | | size | 比较大小 | | cellsize | 比较单元大小 | | diffMinAlpha | 不同的像素最小值 | | diffMinAlpha | 不同的透明最小值 | | diffMinColor | 不同的颜色最小值 | ### 地图相似度分析 | | | |:-----|:--------------------| | 服务地址 | /api/v1/map/cmd/imageSimilarity/_null/v1 | | 请求类型 | Get 或 Post | | 服务说明 | 分析指定地图与其他地图的相似度,返回按相似度排序的地图ID列表 | | 请求参数 | 参数说明 | 参数示例 | |:-----|:--------|:--------| | mapid | 参考地图ID | sys_zp | | version | 参考地图版本号 | v1 | --- # 地图服务后端SDK接口及下载 > vjmap 后端 REST 接口文档 — 面向**二次开发者**的版本(`vjmapservice/`)。 后端SDK下载` python`、`nodejs`、`swagger`服务。下载地址:[http://vjmap.com/download/vjmapservice.zip](http://vjmap.com/download/vjmapservice.zip) ![image-20260425205121562](https://vjmap.com/blogimages/202604252051713.png) ## 目录 - [00-overview.md](#00-overviewmd) - [01-getting-started.md](#01-getting-startedmd) - [02-conventions.md](#02-conventionsmd) - [03-authentication.md](#03-authenticationmd) - [04-workspace.md](#04-workspacemd) - [api/index.md](#apiindexmd) - [api/01-map-management.md](#api01-map-managementmd) - [api/02-tiles-styles.md](#api02-tiles-stylesmd) - [api/03-query.md](#api03-querymd) - [api/04-analysis.md](#api04-analysismd) - [api/05-compose-export.md](#api05-compose-exportmd) - [api/06-webcad.md](#api06-webcadmd) - [api/07-workspace.md](#api07-workspacemd) - [api/08-data-storage.md](#api08-data-storagemd) - [api/09-fullsearch.md](#api09-fullsearchmd) - [api/10-symbols.md](#api10-symbolsmd) - [api/11-system.md](#api11-systemmd) - [api/12-service-misc.md](#api12-service-miscmd) - [api/13-ai.md](#api13-aimd) --- ## 00-overview.md # 概述(Overview) ## 1. 产品定位 vjmap 是一套**以 CAD/GIS 图档为中心**的服务端系统,核心能力包括: - 上传 DWG / DXF / 图片等源文件,在服务端解析成数据库 - 以**栅格瓦片**或 **MVT 矢量瓦片**的形式提供 Web 地图访问 - 基于 **cmd 指令**完成元数据查询、样式切换、几何分析、表格/中心线提取、文件导出等 - 存储用户 KV 数据、工作区、符号库、全文索引等辅助能力 - 支持 **Python / Node.js SDK** 直接对接; 典型使用场景: - 将单位内部 DWG 档案转成 Web 可浏览地图 - 需要把 CAD 图与 WMS / MVT 图层混合使用 - 针对 DWG 做图形分析(比较、表格提取、中心线)并导出 PDF / DXF - 在业务系统里存储轻量 KV 配置或自定义属性 ## 2. 系统架构 ``` ┌────────────────┐ JWT ┌──────────────────────┐ stdin/stdout ┌──────────────────────────┐ │ Client (Web/ │ ─────────► │ web服务 │ ───────────────► │ 图形引擎服务 │ │ Python / Node) │ │ :27660 │ │ geom / render / export │ └────────────────┘ ◄───────── │ router + handler │ ◄─────────────── │ appCmd / mapPsCmd / ... │ ▲ │ + 缓存 / 限流 / 权限 │ │ 持久化 │ │ REST / HTTP └──────────────────────┘ └──────────────────────────┘ │ │ │ ├── Workspace(多租户命名空间) │ ├── Full-text search │ ├── 符号库 │ └── AI / 向量检索(可选) │ └── Tile 通过 CDN / HTTP 缓存直接给前端 ``` - **web服务**(:入口;做 JWT 鉴权、路由转发、缓存清理、工作区路由改写。 - **图形服务 **:实际的 CAD 解析/渲染/分析。 - **数据存储**:每张图对应一个 数据库文件(默认 `data/maps/{hash}.db`),元数据、样式、缓存瓦片都放在里面;另有全局 KV、工作区、符号库等独立存储。 - **可选 AI**服务:`/ai/*`、`/aimcp/*`、`/aiagent/*` 三套子服务通过反代挂载;不是所有部署都启用。 ## 3. 常用接口分 13 大类 | # | 分类 | 典型接口 | 文档 | |---|------|----------|------| | 1 | 地图管理 | upload / openmap / metadata / listmaps / thumbnail | `api/01-map-management.md` | | 2 | 瓦片/样式 | tile / mvt / wms / createMapStyle / switchlayer | `api/02-tiles-and-styles.md` | | 3 | 要素查询 | queryFeatures / createEntityGeoData | `api/03-query.md` | | 4 | 图形分析 | mapDiff / mapCompare / extractTable / centerLines | `api/04-analysis.md` | | 5 | 组合/导出 | composeNewMap / exportPdf / exportDxf | `api/05-compose-and-export.md` | | 6 | WebCAD | listWebcadDraws / saveWebcadPatch(可选) | `api/06-webcad.md` | | 7 | 工作区 | getWorkspace / workspaceCreate | `api/07-workspace.md` | | 8 | KV 存储 | data/{key} / datas / datakeys | `api/08-data-storage.md` | | 9 | 全文搜索 | fullsearch/search / add / delete | `api/09-fullsearch.md` | | 10 | 符号库 | symbol/categories / symbol/list | `api/10-symbols.md` | | 11 | 系统 | heart / version / runstatus / viewlogs | `api/11-system.md` | | 12 | /service 杂项 | qrcode / fonts / uploadImg | `api/12-service-misc.md` | | 13 | AI(可选) | vectorSearch / chatFaq / vectorDocs | `api/13-ai.md` | ## 4. 该怎么读这份文档? 按兴趣从下面挑一条路径: 1. **第一次接触 vjmap**:先过 [`01-getting-started.md`](01-getting-started.md) 把 Python 或 Node SDK 跑起来。 2. **看得懂 curl 就够**:直接翻 `api/01-map-management.md`,里面每个接口都有 `curl` + Python + Node 三列。 ## 5. 命名与术语 | 术语 | 含义 | |------|------| | `mapid` | 图唯一 ID;通常是业务名或 `_` 占位 | | `version` | 图版本号;默认 `v1` | | `layer` | 图层名;多数 cmd 通过请求体 `layers` 字段指定 | | `fileid` | 源文件 ID,一般等于 md5 前缀 | | `secretKey` / `accessKey` | 加密图的访问密钥(前者 16 字符,后者 `ak` 开头 18 字符) | | `workspace` | 命名工作区;默认工作区等价于 `""`(空) | | `geom render` | 图形服务子进程,负责几何渲染/切片 | ## 6. 约定(Conventions)速览 - 所有 `/api/v1/map/...` 接口需要 `token=`(query 或 header) - 95 % 的 cmd 接口既接受 GET 也接受 POST;SDK 统一走 POST - 成功响应结构两类: - 网关包装 `{code:0, msg:"", data:{...}}` - 图形服务 JSON(直接是 `{...}` 或数组);SDK 自动识别并 unwrap - 错误:HTTP 非 2xx → 抛 `VjmapError`;网关 `{code≠0}` 也视为错误抛出 - 详细规则见 [`02-conventions.md`](02-conventions.md) --- ## 01-getting-started.md # 快速上手(Getting Started) 在 5 分钟内用 Python 或 Node.js SDK 跑通 vjmap 的最小闭环。 ## 1. 前置条件 - 已部署的 vjmap 服务,默认运行在 `http://127.0.0.1:27660` - 一个有效 JWT Token(开发环境可直接从后端控制台获取;生产请走登录接口) - Python ≥ 3.8 **或** Node.js ≥ 18 ## 2. 健康检查 服务是否活着: ```bash curl -s http://127.0.0.1:27660/heart # => {"status":"ok","timestamp":"..."} curl -s http://127.0.0.1:27660/version # => {"arch":"amd64","os":"windows","status":"ok","time":"...","version":"..."} ``` ## 3. Python 一分钟体验 ```bash # 1. 拷贝 SDK cp sdk/python/vjmap.py your-project/ # 2. 装依赖 pip install requests # 3. hello world python - <<'PY' from vjmap import Vjmap svc = Vjmap("http://127.0.0.1:27660/api/v1", "") print("heart ->", svc.heart()) print("version ->", svc.version()) print("status ->", svc.run_status(detail=True)["application"]) PY ``` 期望输出: ```text heart -> {'status': 'ok', 'timestamp': '...'} version -> {'arch': 'amd64', 'os': 'windows', 'status': 'ok', 'time': '...', 'version': '2026041001'} status -> {'pid': '...', 'runtime': ..., ...} ``` ## 4. Node.js 一分钟体验 ```bash cp sdk/nodejs/vjmap.js your-project/ node - <<'JS' const { Vjmap } = require('./vjmap'); (async () => { const svc = new Vjmap('http://127.0.0.1:27660/api/v1', ''); console.log('heart ->', await svc.heart()); console.log('version ->', await svc.version()); console.log('status ->', (await svc.runStatus()).application); })(); JS ``` ## 5. 最常用的 4 步闭环 > 下面示例用 Python;Node 版本见 `examples/nodejs/`。 ### 5.1 列出已有的图 ```python maps = svc.list_maps() # 对应 /api/v1/map/cmd/listmaps/_/v1 for mapid, versions in maps.items(): print(mapid, [v["version"] for v in versions]) ``` ### 5.2 打开图并读元数据 ```python svc.open_map(mapid="sys_zp", version="v1", mapopenway="GeomRender") meta = svc.metadata() print("layers:", [l["name"] for l in meta.get("layers", [])]) print("bounds:", meta.get("bounds")) ``` ### 5.3 取栅格瓦片 URL( XYZ source) ```python url = svc.raster_tile_url(mapid="sys_zp", version="v1", layer="0,1,2,3,4,5,6,7,8", z="{z}", x="{x}", y="{y}") print(url) # => http://127.0.0.1:27660/api/v1/map/tile/sys_zp/v1/0,1,.../{z}/{x}/{y}?token=... ``` 把 URL 直接填到 vjmap 的 `raster` source 就能显示。 ### 5.4 关闭图(可选,释放 图形服务 子进程) ```python svc.close_map(mapid="sys_zp", version="v1") ``` ## 6. 下一步 - 想系统了解参数规则?→ [`02-conventions.md`](02-conventions.md) - 弄清 token / secretKey?→ [`03-authentication.md`](03-authentication.md) - 多租户分区?→ [`04-workspace.md`](04-workspace.md) - 跳到接口分类索引 → [`api/index.md`](api/index.md) --- ## 02-conventions.md # 通用约定(Conventions) 所有接口的共性规则集中在这里,具体接口文档不再重复。 ## 1. Base URL 与前缀 - 默认:`http://127.0.0.1:27660/api/v1` - 文档里写成 `{base}`;SDK 自动补 `/` 结尾 - 三种子前缀: | 前缀 | 用途 | |------|------| | `{base}/map/...` | 默认工作区的所有图相关接口 | | `{base}/workspace/{name}/...` | 命名工作区下的图相关接口(与 `/map/` 等价但隔离存储) | | `{base}/service/...` | 与图无关的工作区/KV/符号库/全文搜索/AI 等接口 | 系统级根路径: | 路径 | 说明 | |------|------| | `/heart` | 健康检查 | | `/version` | 版本 | ## 2. HTTP 方法 - **cmd 接口**(路径形如 `/api/v1/map/cmd///`)**既接受 GET 也接受 POST**;网关 `Router.Any` 统一挂载。SDK 默认走 **POST**,原因: 1. 请求体大时 GET 超长 2. 语义上是"动作"而非"只读资源" - 其它接口遵循标准 REST:GET 查、POST 创建、PUT 修改、DELETE 删除 ## 3. 鉴权 > 详情见 [`03-authentication.md`](03-authentication.md)。 - **必带**:`token=`(推荐放 query,也可放 header) - **加密图**:请求头 `secretKey: _;_...` 或 query `secret_key=...` - **全局 access key**(`ak` 开头 18 位):可放在 `accessKey` 位置用来**一次性访问多张加密图** ## 4. 路径占位符 | 占位 | 常用取值 | 含义 | |------|---------|------| | `{mapid}` | `sys_zp`、`_`、`_null` | 图 ID;`_` / `_null` 用在不需要 mapid 的 cmd 上 | | `{version}` | `v1`、`_` | 版本号;`_` 表示与具体版本无关 | | `{layer}` | `"0,1,2"`、`default`、空 | 图层名/ID;多个用逗号 | | `{z}/{x}/{y}` | `10/854/402` | 标准 XYZ 瓦片坐标 | | `{key}` | 用户自定义 | KV 存储的 key(建议 URL-safe) | **重要**:SDK 会对占位符做 `encodeURIComponent`,别再手动编码。 ## 5. 请求体约定 | 场景 | Content-Type | 备注 | |------|--------------|------| | 绝大多数 cmd | `application/json` | SDK 默认 | | 文件上传(`/map/uploads`、`/service/uploadImg`) | `multipart/form-data` | 字段名 `file` | | KV 存值 `POST /data/{key}` | `application/x-www-form-urlencoded` | 字段 `data=` | ## 6. 响应结构 两类并存,SDK 都能识别: ### 6.1 web服务 网关标准响应 ```json { "code": 0, "msg": "", "data": { /* 实际内容 */ } } ``` - `code == 0` → SDK unwrap 成 `data` - `code != 0` → SDK 抛 `VjmapError(status=200, body={...})` ### 6.2 图形服务 ```json { "bounds": [...], "layers": [...], ... } ``` 直接返回,不包 `{code,msg,data}`。 ### 6.3 错误响应 - HTTP 4xx / 5xx:SDK 抛 `VjmapError(status=4xx|5xx, url, body)` - cmd 执行失败但网关路由成功:多数 cmd 用 `{error: "..."}` 或 `{result: {succeed: false}}` 表达;SDK 不会自动判断业务成功/失败,请业务侧自行校验 ## 7. 分页 / 列表 大部分列出接口没有分页,一次返回全部: - `listmaps`:按 mapid 聚合,返回 `{mapid: [ {version, db, filename, ...} ]}` - `symbol/list/{cat}`:数组 - `getWorkspace`:数组 少数有 `limit` / `offset` / `pageSize` / `pageNum`(例如 `fullsearch/search`),每个接口文档单独说明。 ## 8. 幂等性与重试 | 操作 | 幂等 | 可自动重试? | |------|------|-------------| | `openmap` (GeomRender) | 否 | 否,会占用 图形服务 子进程 | | `metadata` / `listmaps` / `runstatus` | 是 | 是 | | `updatemap` / `updateMetadata` | 否 | 否 | | `deletemap` / `deletestyle` | 是 | 是,但业务需防误触 | | 瓦片 GET | 是 | 是 | SDK 不内置重试; ## 9. 超时 - SDK 默认 60 秒;超长 cmd(`composeNewMap`、`saveOfflineTileDb`、`extractTable` on 大图)可能要 3-5 分钟,调用时显式 `timeout=300`(秒)。 - 图形服务单请求默认 `600000 ms = 10 min`(`runstatus.process.reqTimeout`),超过会被强杀。 ## 10. 文件下载 / 流式接口 导出 PDF、DXF、离线瓦片包、缩略图等接口返回 **二进制流**。SDK 里对应方法: - Python:`svc._request("POST", url, stream=True)` 返回原始 `requests.Response`,自己 `.iter_content()` 写文件 - Node:`svc._request('POST', url, { stream: true })` 返回原始 `Response`,自己 `await res.arrayBuffer()` 或管道到文件 ## 11. 命名风格(跨语言) | 语言 | 风格 | 例 | |------|------|----| | Python SDK | `snake_case` | `open_map`, `close_map`, `raster_tile_url` | | Node SDK | `camelCase` | `openMap`, `closeMap`, `rasterTileUrl` | | 文档正文 | 二者并列列出 | 每个接口都给两种示例 | ## 12. 日志与可观测 - `runstatus?detail=true`:看引擎 pid、活跃请求、子进程等 - `viewlogs?record=100`:看最近 100 条日志;含 cmd 耗时、错误 --- ## 03-authentication.md # 鉴权与加密图(Authentication) ## 1. JWT Token - vjmap 后端用 **标准 HS256 JWT** 做鉴权 - Claims 里包含 `ID` / `Username` / `AuthorityId` / `BufferTime` / `exp` / `iss=vjmap` - 过期时间:默认 `BufferTime=86400 秒`;`exp` 远期刷新由 `{UpdateTokenRoute}` 负责 - 生产环境拿 token 的方式:通过业务登录接口;开发环境可直接从 vjmap 后台复制 ### 1.1 Token 放哪里 三种都接受,**优先级从高到低**: 1. HTTP Header:`token: eyJ...` 2. Query:`?token=eyJ...` 3. Form:`token=eyJ...`(仅个别接口) SDK 默认走 **Query**(便于 curl 排障);如果你的反代会剥 query,可传 `header=True`(Python)/`{headers:{token}}` 客户端自行覆盖。 ### 1.2 Token 的 5 分钟内测 ```bash TOKEN="eyJhbG..." curl -s "http://127.0.0.1:27660/version?token=$TOKEN" curl -s "http://127.0.0.1:27660/api/v1/map/cmd/runstatus/_/v1?token=$TOKEN&detail=true" ``` ### 1.3 常见错误 | 现象 | 原因 | |------|------| | `HTTP 401` | token 过期 / 签名不匹配 | | `{code: 10006, msg: "..."}`(网关响应) | token 合法但权限不够(例如不是 root)| | `HTTP 200` 但业务字段异常 | token OK,但参数错误 | ## 2. 加密图(secretKey / accessKey) vjmap 允许对图设置访问密钥,打开/瓦片/元数据请求都要带。 ### 2.1 secretKey(每图一把,16 字符) - 创建/上传图时可通过 `secretKey` 字段设置 - 明文密码 → `secretKey` 算法(与 Service.ts `pwdToSecretKey` 一致): - 连续 4 次 md5 后取前 16 字符 - 空串 → 空串 - 长度 == 18 且以 `ak` 开头 → 视为 accessKey,原样返回 - SDK:`compute_secret_key(pwd)` / `computeSecretKey(pwd)` ### 2.2 accessKey(全局,18 字符 `ak...`) - 跨图通用的"超级密钥",典型用途:服务端代为访问所有图 - 放在请求头/query 里的字段也叫 `secretKey`,但值是 18 位 ak 串 - SDK 识别:如果密钥串长度 == 18 且前缀 `ak`,不再 md5 拼装,直接透传 ### 2.3 多密钥传递 一个请求可能同时携带多个 secretKey(覆盖多张图): - Header: `secretKey: key1_;_key2_;_ak1234...abcd` - Query: 用多个 `secret_key=` 或一个 `secret_key=key1_;_key2` SDK Python: ```python svc._secret_keys = ["<16chars>", "<16chars>", "ak........"] # 后续请求会自动拼 header ``` SDK Node: ```javascript svc._secretKeys = [ '16chars...', 'ak........' ]; ``` ### 2.4 加密图典型流程 ```python svc = Vjmap(base, token) svc._secret_keys = [compute_secret_key("myPass123")] svc.open_map(mapid="finance_map", version="v1", mapopenway="GeomRender", secretKey="myPass123") # 打开时也可带明文 meta = svc.metadata() # 会自动 carry secretKey ``` ## 3. Super Key(运维视角) `.env` 里的 `VJMAP_MAP_SUPER_KEY` 本质是一把 **accessKey**,含义是: - 整个实例的所有加密图都接受它 - 二次开发一般不需要它;**只有**测试框架需要跑加密场景时才用 ## 4. CSRF / 同源 - 后端不强制 CSRF token;依赖 JWT 签名 - 浏览器侧调用请注意 CORS:网关默认允许 `Origin: *`;如果在受限部署里碰到 CORS,需要运维修改 `ReqMiddlewareCors` ## 5. 速率限制 - **默认无全局速率限制** - 单个 cmd 的并发由 `runstatus.maxParallelProcessGeomNum`(几何渲染)控制,典型值 2-4 - 超并发请求会被排队,不会报错但会"看起来卡住" ## 6. 过期 Token 的自动刷新 网关里有一个 `{UpdateTokenRoute}`(常见为 `/api/v1/map/updateToken`)。 --- ## 04-workspace.md # 工作区(Workspace) 工作区是 vjmap 的**命名空间机制**:不同工作区下的图、符号、KV 存储、全文索引**互相隔离**。 ## 1. 概念 - **默认工作区**:不带名字;URL 用 `/api/v1/map/...` 前缀 - **命名工作区**:人为创建;URL 变为 `/api/v1/workspace/{name}/...` 前缀 - 工作区本身也有状态:`{name, description, createTime, ...}`,由 `/api/v1/service/getWorkspace` 列出 - 一个 token 理论上可访问所有工作区;实际权限由 `AuthorityId` 决定 ## 2. 切换工作区(SDK) ### Python ```python svc = Vjmap("http://127.0.0.1:27660/api/v1", token) svc.switch_workspace("team_a") # 之后所有 map_url / cmd_url 都会自动带上 /workspace/team_a svc.open_map("finance", "v1", mapopenway="Memory") # → /api/v1/workspace/team_a/cmd/openmap/finance/v1 svc.switch_workspace("") # 切回默认工作区 ``` ### Node ```javascript svc.switchWorkspace('team_a'); await svc.openMap({ mapid: 'finance', version: 'v1', mapopenway: 'Memory' }); svc.switchWorkspace(''); ``` ## 3. 生命周期接口 | # | 操作 | URL | 方法 | |---|------|------|------| | 1 | 列出 | `/api/v1/service/getWorkspace` | GET | | 2 | 创建 | `/api/v1/service/workspaceCreate` | POST | | 3 | 修改 | `/api/v1/service/workspaceModify` | POST | | 4 | 删除 | `/api/v1/service/workspaceDelete/{name}` | DELETE | | 5 | 状态 | `/api/v1/service/workspaceStatus` | GET | 详细字段、响应示例请看 [`api/07-workspace.md`](api/07-workspace.md) 。 ## 4. 底层 URL 改写规则 - web服务网关把 `/api/v1/workspace/{name}/*` 改写成 **带 workspace 参数**的 `/api/v1/map/*`;图形服务不感知名字,由网关保证隔离 - 因此凡是 `/api/v1/map/` 下的路径都能在 `/workspace/{name}/` 下一模一样使用 - `/api/v1/service/*`(全局 KV、符号库、全文搜索)**不受**工作区前缀影响;它们的隔离通过各自的 `workspace` 参数实现(大部分接口接受 `workspace=` query) ## 5 反模式 - 不要在**热路径**里频繁调用 `switch_workspace()` 再立刻发请求;SDK 内部只是改前缀,没 IO,但业务代码可能会混乱。建议**一个 svc 实例绑一个 workspace**。 - 不要把 `workspace` 和 `mapid` 拼到一起当复合键用;保持**每图一个 mapid,workspace 单独字段**。 --- ## api/index.md # 接口文档总索引(API Index) > 本页把 13 个分类、73 个 接口分别映射到具体文档、SDK 方法。 ## 0. 阅读顺序建议 1. 先读 [`../00-overview.md`](../00-overview.md)、[`../02-conventions.md`](../02-conventions.md) 2. 再按需要翻下面分类文档 3. 每个接口文档包含 4 块:**URL & 方法** / **入参** / **出参** / **示例(curl + Python + Node + 真实响应)** ## 1. 分类一览 | # | 分类 | 接口数 | 文档 | Phase | |---|------|-------:|------|-------| | 01 | 地图管理(Map Management) | 15 | [`01-map-management.md`](01-map-management.md) | C.1 | | 02 | 瓦片 & 样式(Tiles & Styles) | 10 | [`02-tiles-and-styles.md`](02-tiles-and-styles.md) | C.2 | | 03 | 要素查询(Query) | 4 | [`03-query.md`](03-query.md) | C.3 | | 04 | 图形分析(Analysis) | 6 | [`04-analysis.md`](04-analysis.md) | C.4 | | 05 | 组合 / 导出(Compose & Export) | 6 | [`05-compose-and-export.md`](05-compose-and-export.md) | C.5 | | 06 | WebCAD(可选) | 6 | [`06-webcad.md`](06-webcad.md) | C.6 | | 07 | 工作区(Workspace) | 5 | [`07-workspace.md`](07-workspace.md) | C.7 | | 08 | KV 存储(Data Storage) | 5 | [`08-data-storage.md`](08-data-storage.md) | C.8 | | 09 | 全文搜索(Full-text Search) | 3 | [`09-fullsearch.md`](09-fullsearch.md) | C.9 | | 10 | 符号库(Symbols) | 3 | [`10-symbols.md`](10-symbols.md) | C.10 | | 11 | 系统(System) | 4 | [`11-system.md`](11-system.md) | C.11 | | 12 | /service 杂项(Service Misc) | 3 | [`12-service-misc.md`](12-service-misc.md) | C.12 | | 13 | AI(可选) | 3 | [`13-ai.md`](13-ai.md) | C.13 | | - | **合计** | **73** | - | - | ## 2. 快速找接口(常用任务 → 文档) | 我要做…… | 看这里 | |----------|--------| | 上传一个 DWG 文件并在后端渲染 | `01-map-management.md` § uploads / openmap | | 读元数据(图层列表、范围) | `01-map-management.md` § metadata | | 生成 XYZ 瓦片 URL | `02-tiles-and-styles.md` § tile / mvt | | 切换显隐图层并应用样式 | `02-tiles-and-styles.md` § createMapStyle / switchlayer | | 找几何要素 | `03-query.md` § queryFeatures | | 两图对比 | `04-analysis.md` § mapDiff / mapCompare | | 导出 PDF / DXF | `05-compose-and-export.md` | | WebCAD 修改落盘 | `06-webcad.md` | | 存一段用户配置 JSON | `08-data-storage.md` § data/{key} | | 全文搜索 | `09-fullsearch.md` | | 看服务是否在运行 | `11-system.md` § heart / runstatus | ## 3. SDK 方法名速查 Python 与 Node 的方法名一一对应(snake_case ↔ camelCase): | Python | Node | 对应 URL | |--------|------|---------| | `heart()` | `heart()` | `GET /heart` | | `version()` | `version()` | `GET /version` | | `run_status(detail)` | `runStatus(detail)` | `GET /api/v1/map/cmd/runstatus/_/v1` | | `upload_map(...)` | `uploadMap(...)` | `POST /api/v1/map/uploads` | | `check_map_file(md5)` | `checkMapFile(md5)` | `GET /api/v1/map/mapfile` | | `open_map(mapid, version, ...)` | `openMap({mapid, version, ...})` | `GET/POST /api/v1/map/openmap/{mapid}` | | `update_map(...)` | `updateMap(...)` | `POST /api/v1/map/updatemap/{mapid}` | | `close_map(mapid, version)` | `closeMap(mapid, version)` | `POST /api/v1/map/cmd/closemap/{mapid}/{version}` | | `delete_map(mapid, version)` | `deleteMap(mapid, version)` | `POST /api/v1/map/cmd/deletemap/{mapid}/{version}` | | `rename_map(old, new)` | `renameMap(old, new)` | `POST /api/v1/map/cmd/renamemap/{old}/_` | | `list_maps()` | `listMaps()` | `POST /api/v1/map/cmd/listmaps/_/v1` | | `metadata(mapid, version)` | `metadata(mapid, version)` | `POST /api/v1/map/cmd/metadata/{mapid}/{version}` | | `update_metadata(...)` | `updateMetadata(...)` | `POST /api/v1/map/cmd/updateMetadata/{mapid}/{version}` | | `thumbnail_url(...)` | `thumbnailUrl(...)` | `GET /api/v1/map/cmd/thumbnail/{mapid}/{version}` | | `support_format()` | `supportFormat()` | `GET /api/v1/map/cmd/supportFormat/_/v1` | | `const_data()` | `constData()` | `GET /api/v1/map/cmd/constData/_/v1` | | `get_data_bounds(mapid, version)` | `getDataBounds(mapid, version)` | `POST /api/v1/map/cmd/getDataBounds/{mapid}/{version}` | | `raster_tile_url(...)` | `rasterTileUrl(...)` | `GET /api/v1/map/tile/...` | | `vector_tile_url(...)` | `vectorTileUrl(...)` | `GET /api/v1/map/tile/.../{z}/{x}/{y}.mvt` | | `wms_tile_url(...)` | `wmsTileUrl(...)` | `GET /api/v1/map/cmd/wms/...` | | `create_map_style(...)` | `createMapStyle(...)` | `POST /api/v1/map/cmd/createMapStyle/...` | | `cmd_switch_layers(...)` | `cmdSwitchLayers(...)` | `POST /api/v1/map/cmd/switchlayer/...` | | `cmd_update_style(...)` | `cmdUpdateStyle(...)` | `POST /api/v1/map/cmd/updateStyle/...` | | `slice_layer(...)` | `sliceLayer(...)` | `POST /api/v1/map/cmd/slicelayer/...` | | `get_slice_cache_zoom(...)` | `getSliceCacheZoom(...)` | `GET /api/v1/map/cmd/getSliceCacheZoom/...` | | `delete_style(...)` | `deleteStyle(...)` | `POST /api/v1/map/cmd/deletestyle/...` | | `query_features(...)` | `queryFeatures(...)` | `POST /api/v1/map/cmd/queryFeatures/...` | | `create_entity_geo_data(...)` | `createEntityGeoData(...)` | `POST /api/v1/map/cmd/createEntityGeoData/_null/v1` | | `coord_transform(...)` | `coordTransform(...)` | `POST /api/v1/map/cmd/coordtransform/_/_` | | `prj_wkt_to_prj4_str(...)` | `prjWktToPrj4Str(...)` | `POST /api/v1/map/cmd/prjWktToPrj4Str/_/_` | | `cmd_map_diff(...)` | `cmdMapDiff(...)` | `POST /api/v1/map/cmd/mapDiff/_null/v1` | | `cmd_map_compare(...)` | `cmdMapCompare(...)` | `POST /api/v1/map/cmd/mapCompare/_null/v1` | | `cmd_extract_table(...)` | `cmdExtractTable(...)` | `POST /api/v1/map/cmd/extractTable/_null/v1` | | `cmd_extract_center_lines(...)` | `cmdExtractCenterLines(...)` | `POST /api/v1/map/cmd/extractCenterLines/_null/v1` | | `cmd_match_object(...)` | `cmdMatchObject(...)` | `POST /api/v1/map/cmd/objectMatch/_null/v1` | | `cmd_image_svg_to_dwg(...)` | `cmdImageSvgToDwg(...)` | `POST /api/v1/map/cmd/imageSvgToDwg/_null/v1` | | `cmd_compose_new_map(...)` | `cmdComposeNewMap(...)` | `POST /api/v1/map/cmd/composeNewMap/_null/v1` | | `cmd_split_map(...)` | `cmdSplitMap(...)` | `POST /api/v1/map/cmd/cmdSplitMap/_null/v1` | | `cmd_export_layout(...)` | `cmdExportLayout(...)` | `POST /api/v1/map/cmd/exportLayout/_null/v1` | | `cmd_export_pdf(...)` | `cmdExportPdf(...)` | `POST /api/v1/map/cmd/exportPdf/{mapid}/{version}` | | `cmd_export_dxf(...)` | `cmdExportDxf(...)` | `POST /api/v1/map/cmd/exportDxf/{mapid}/{version}` | | `cmd_save_offline_tile_db(...)` | `cmdSaveOfflineTileDb(...)` | `POST /api/v1/map/cmd/saveOfflineTileDb/_null/v1` | | `list_webcad_draws()` | `listWebcadDraws()` | `GET /api/v1/map/cmd/listWebcadDraws/_/v1` | | `get_webcad_data(...)` | `getWebcadData(...)` | `POST /api/v1/map/cmd/getWebcadData/...` | | `save_webcad_patch(...)` | `saveWebcadPatch(...)` | `POST /api/v1/map/cmd/saveWebcadPatch/...` | | `delete_webcad_draw(...)` | `deleteWebcadDraw(...)` | `POST /api/v1/map/cmd/deleteWebcadDraw/...` | | `convert_webcad(...)` | `convertWebcad(...)` | `POST /api/v1/map/cmd/convertWebCAD/...` | | `export_webcad(...)` | `exportWebcad(...)` | `POST /api/v1/map/cmd/exportWebCAD/_null/v1` | | `get_workspaces()` | `getWorkspaces()` | `GET /api/v1/service/getWorkspace` | | `workspace_create(...)` | `workspaceCreate(...)` | `POST /api/v1/service/workspaceCreate` | | `workspace_modify(...)` | `workspaceModify(...)` | `POST /api/v1/service/workspaceModify` | | `workspace_delete(name)` | `workspaceDelete(name)` | `DELETE /api/v1/service/workspaceDelete/{name}` | | `workspace_status()` | `workspaceStatus()` | `GET /api/v1/service/workspaceStatus` | | `get_custom_data(key)` | `getCustomData(key)` | `GET /api/v1/service/data/{key}` | | `save_custom_data(key, val)` | `saveCustomData(key, val)` | `POST /api/v1/service/data/{key}` | | `delete_custom_data(key)` | `deleteCustomData(key)` | `DELETE /api/v1/service/data/{key}` | | `save_multiple_custom_data(...)` | `saveMultipleCustomData(...)` | `POST /api/v1/service/datas` | | `get_custom_data_keys(prefix)` | `getCustomDataKeys(prefix)` | `GET /api/v1/service/datakeys` | | `full_search(...)` | `fullSearch(...)` | `GET/POST /api/v1/service/fullsearch/search` | | `full_search_add(...)` | `fullSearchAdd(...)` | `POST /api/v1/service/fullsearch/add` | | `full_search_delete(...)` | `fullSearchDelete(...)` | `DELETE /api/v1/service/fullsearch/delete` | | `symbol_categories()` | `symbolCategories()` | `GET /api/v1/service/symbol/categories` | | `symbol_list(cat)` | `symbolList(cat)` | `GET /api/v1/service/symbol/list/{cat}` | | `symbol_get(id)` | `symbolGet(id)` | `GET /api/v1/service/symbol/{id}` | | `view_logs(record)` | `viewLogs(record)` | `GET /api/v1/map/cmd/viewlogs/_/v1` | | `qrcode_url(content, size)` | `qrcodeUrl(content, size)` | `GET /api/v1/service/qrcode` | | `glyphs_url(fontstack, range)` | `glyphsUrl(fontstack, range)` | `GET /api/v1/service/fonts/...` | | `upload_img(file)` | `uploadImg(file)` | `POST /api/v1/service/uploadImg` | | `vector_search(...)` | `vectorSearch(...)` | `POST /api/v1/service/vectorSearch` | | `chat_faq(q)` | `chatFaq(q)` | `GET /api/v1/service/chatFaq` | | `vector_docs(...)` | `vectorDocs(...)` | `GET /api/v1/service/vectorDocs` | > --- ## api/01-map-management.md # 地图管理(Map Management) > 分类 1 / 13 · 接口 15 个 · **快速目录** | # | 接口 | 方法 | URL | |---|------|------|-----| | 1 | [秒传检测](#1-秒传检测-check_map_file) | GET | `/api/v1/map/mapfile` | | 2 | [上传源文件](#2-上传源文件-upload_map) | POST | `/api/v1/map/uploads` | | 3 | [打开/创建地图](#3-打开创建地图-open_map) | POST | `/api/v1/map/openmap/{mapid}` | | 4 | [修改地图](#4-修改地图-update_map) | POST | `/api/v1/map/updatemap/{mapid}` | | 5 | [关闭地图](#5-关闭地图-close_map) | GET | `/api/v1/map/cmd/closemap/{mapid}/{version}` | | 6 | [删除地图](#6-删除地图-delete_map) | GET | `/api/v1/map/cmd/deletemap/{mapid}/{version}` | | 7 | [重命名地图](#7-重命名地图-rename_map-已知不稳定) | GET | `/api/v1/map/cmd/renamemap/{old}/_` | | 8 | [删除源文件](#8-删除源文件-delete_map_file) | GET | `/api/v1/map/cmd/deletemapfile/_/v1` | | 9 | [列出地图](#9-列出地图-list_maps) | GET | `/api/v1/map/cmd/listmaps/_/v1` | | 10 | [读元数据](#10-读元数据-metadata) | GET | `/api/v1/map/cmd/metadata/{mapid}/{version}` | | 11 | [修改元数据](#11-修改元数据-update_metadata) | POST | `/api/v1/map/cmd/updateMetadata/{mapid}/{version}` | | 12 | [缩略图](#12-缩略图-thumbnail) | GET | `/api/v1/map/cmd/thumbnail/{mapid}/{version}` | | 13 | [支持的格式](#13-支持的格式-support_format) | GET | `/api/v1/map/cmd/supportFormat/_/v1` | | 14 | [系统常量](#14-系统常量-const_data) | GET | `/api/v1/map/cmd/constData/_/v1` | | 15 | [数据范围](#15-数据范围-get_data_bounds) | GET | `/api/v1/map/cmd/getDataBounds/{mapid}/{version}` | **共性约定(重要)** - 所有 `cmd/*` 路径:`GET` **不需要 body**,`POST` **要求 body 至少一个字段**,否则后端回 500 `param error`。SDK 对无 body 的 cmd 统一走 GET。 - `openmap` 通过 **POST body** 传 `mapid/version/fileid/uploadname/mapopenway`;`fileid ` 必须来自 `upload_map` 的返回,不要自己拼 md5。 - 所有路径都带 `?token=`(SDK 自动加)。 --- ## 1. 秒传检测 `check_map_file` **URL**:`GET /api/v1/map/mapfile?md5={md5}` **用途**:客户端上传前先算文件 md5,若 `result:true` 则复用已有文件,跳过上传。 ### 入参(Query) | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `md5` | string(32) | 是 | 源文件 MD5(小写 hex) | | `token` | string | 是 | JWT | ### 出参 ```json {"result": false} ``` - `result=true`:已有同 md5 文件;可直接跳过上传用 `fileid = md5[:12]` - `result=false`:需要走 `upload_map` ### 示例 ```bash curl -s "http://127.0.0.1:27660/api/v1/map/mapfile?token=$T&md5=caf27de8a2259b2c4e04f2afde2f5c42" ``` ```python svc.check_map_file("caf27de8a2259b2c4e04f2afde2f5c42") ``` ```javascript await svc.checkMapFile('caf27de8a2259b2c4e04f2afde2f5c42'); ``` --- ## 2. 上传源文件 `upload_map` **URL**:`POST /api/v1/map/uploads` **Content-Type**:`multipart/form-data` ### 入参(Form) | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `file` | binary | 是 | 源文件 | | `md5` | string(32) | 是 | 预算的 MD5 | | `secretKey` | string | 否 | 设置加密图的 16 位密钥;SDK 中默认空 | ### 出参 ```json {"fileid":"caf27de8a225","mapid":"caf27de8a225","uploadname":"caf27de8a225.dwg"} ``` - `fileid`:生成的**12 位十六进制**(= md5 前 12 位) - `mapid`:默认等于 `fileid`;后续 `open_map` 可以给另一个业务名 - `uploadname`:服务端实际落盘的文件名 ### 示例 ```bash curl -s -X POST "http://127.0.0.1:27660/api/v1/map/uploads?token=$T" \ -F "file=@./my.dwg" \ -F "md5=$(md5sum my.dwg | awk '{print $1}')" ``` ```python up = svc.upload_map("./my.dwg") fileid, uploadname = up["fileid"], up["uploadname"] ``` ```javascript const up = await svc.uploadMap('./my.dwg'); const { fileid, uploadname } = up; ``` --- ## 3. 打开/创建地图 `open_map` **URL**:`POST /api/v1/map/openmap/{mapid}?version={version}` (SDK 默认走 POST;GET 等价) 这是整套 API 的**中心接口**。它同时承担: - 首次创建:`mapid` 未存在时结合 `fileid + uploadname` 新建 - 再次打开:`mapid` 已存在时忽略 `fileid/uploadname` - 切换模式:`mapopenway` 决定是否拉起 渲染子进程 ### 入参(JSON body) | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `mapid` | string | 是 | 业务 ID | | `version` | string | 是 | 版本号,默认 `v1` | | `fileid` | string(12) | **首次建图必填** | `upload_map` 返回的 fileid | | `uploadname` | string | **首次建图必填** | `upload_map` 返回的 uploadname | | `mapopenway` | `Memory` / `GeomRender` | 是 | 见 [`../02-conventions.md`](../02-conventions.md) | | `style` | object | 否 | 预设样式;详情见 § 2 `create_map_style` | | `secretKey` | string | 否 | 图级密钥 | | `accessKey` | string | 否 | 全局 ak | | `mapbounds` | array / string | 否 | 限制的图面范围 | ### 出参(节选,完整见 snapshot) | 字段 | 含义 | |------|------| | `mapid / version / fileid / uploadname / filename` | 基本定位 | | `bounds / dbBounds / drawBounds / initBounds / viewBounds` | 各种范围 | | `layers` *(JSON string)* | 图层数组 | | `styles` *(JSON string)* | 预置样式数组 | | `format` | 瓦片格式(典型 `png`) | | `mapopenway` | 实际使用的打开方式 | | `pid` | 子进程 PID(GeomRender 时) | | `renderAccuracy` | 渲染精度 | | `isnewmap` | 是否刚创建 | #### 真实响应片段 ```json { "mapid": "nb_roundtrip_1776829368", "version": "v1", "fileid": "caf27de8a225", "uploadname": "caf27de8a225.dwg", "filename": "caf27de8a225.dwg", "mapopenway": "Memory", "bounds": "[67108.08576259,-10253.62403246,73355.58576721,-4006.12402784]", "isnewmap": true, "format": "png", "layers": "[{\"color\":\"#ffffff\",\"index\":0,...}]", "styles": "[{\"backcolor\":0,\"isgeom\":false,...}]" } ``` ### 示例 ```python svc.open_map( mapid="demo_dwg", version="v1", mapopenway="Memory", fileid=up["fileid"], uploadname=up["uploadname"], ) ``` ```javascript await svc.openMap({ mapid: 'demo_dwg', version: 'v1', mapopenway: 'Memory', fileid: up.fileid, uploadname: up.uploadname, }); ``` --- ## 4. 修改地图 `update_map` **URL**:`POST /api/v1/map/updatemap/{mapid}?version={version}` 用法:修改 **已存在**图的属性(描述、可见性、部分字段等)。实现上等价于"带新参数再打开一次",因此 body 通常和 `open_map` 类似,加上要改的字段。 ### 入参(JSON body) | 字段 | 必填 | 说明 | |------|------|------| | `mapid` | 是 | 要更新的图 | | `fileid` | 是 | 必须和打开时一致 | | `uploadname` | 是 | 同上 | | `mapopenway` | 是 | 同上 | | `description` | 否 | 描述 | | `deleteOldVersion` | 否 | 删除旧版本再建 | | …… | | 其余字段参考 `ServiceInterface.ts::IUpdateMapParam` | ### 出参 结构与 `open_map` 返回一致。 ### 示例 ```python svc.update_map("demo_dwg", "v1", body={ "mapid": "demo_dwg", "fileid": up["fileid"], "uploadname": up["uploadname"], "mapopenway": "Memory", "description": "添加二次开发示例描述", }) ``` --- ## 5. 关闭地图 `close_map` **URL**:`GET /api/v1/map/cmd/closemap/{mapid}/{version}` 仅断开 图形服务 中的内存/渲染引用;不删除数据库文件,也不删图。 ### 出参 ```json {"status": true} ``` ### 注意:POST 无 body 会 500 ```bash # ✗ 500 param error curl -X POST ".../closemap/sys_zp/v1?token=$T" # ✓ 200 curl ".../closemap/sys_zp/v1?token=$T" # ✓ 200(POST 可以,但 body 必须有至少一个字段) curl -X POST ".../closemap/sys_zp/v1?token=$T" -d '{"fileid":"caf27de8a225"}' -H 'content-type: application/json' ``` SDK 内部统一使用 GET 规避该陷阱。 --- ## 6. 删除地图 `delete_map` **URL**:`GET /api/v1/map/cmd/deletemap/{mapid}/{version}?keepSource={bool}` | 参数 | 默认 | 说明 | |------|------|------| | `keepSource` | `true` | `true` 保留上传的源文件(只清元数据/DB),`false` 同时删源文件 | ### 出参 ```json {"status": true} ``` ### 示例 ```python svc.delete_map("demo_dwg", "v1", keep_source=True) ``` --- ## 7. 重命名地图 `rename_map`(**已知不稳定**) **URL**:`GET /api/v1/map/cmd/renamemap/{old}/_?newmapid={new}` ### 响应样例(实测) ```json { "status": false, "error": true, "reason": "mapid is empty", "newmapid": "xxx_new", "AuthorityId": "root" } ``` --- ## 8. 删除源文件 `delete_map_file` **URL**:`GET /api/v1/map/cmd/deletemapfile/_/v1?files=a.dwg;b.dwg` 删除上传到 `data/mapfiles/` 目录下的物理文件。被删的文件**必须没有图在引用**。 ### 出参(实测) ```json { "deleted": [], "deletedCount": 0, "notFound": ["__nb_nonexistent__.dwg"], "status": false } ``` ### 示例 ```python svc.delete_map_file(["caf27de8a225.dwg"]) # 或字符串 svc.delete_map_file("a.dwg;b.dwg") ``` --- ## 9. 列出地图 `list_maps` **URL**:`GET /api/v1/map/cmd/listmaps/_/v1` ### 入参 | Query | 说明 | |-------|------| | `keyword` | 按 mapid 过滤(可选) | | `page` / `pageSize` | 分页(可选,多数版本支持) | ### 出参(实测) ```json { "sys_zp": [ { "db": "c47be46a2ad2.db", "filename": "c47be46a2ad2.dwg", "geom": true, "mtime": "2026-04-10 11:31:00", "status": "finish", "uploadname": "sys_zp.dwg", "version": "v1" } ] } ``` - 顶层 key 是 `mapid`,value 是该图所有版本的数组 - `geom:true` 表示该图已有切片缓存 - `status:"finish"` 表示打开流程完成 ### 示例 ```python maps = svc.list_maps() for mapid, versions in maps.items(): print(mapid, [v["version"] for v in versions]) ``` ```javascript const maps = await svc.listMaps(); ``` --- ## 10. 读元数据 `metadata` **URL**:`GET /api/v1/map/cmd/metadata/{mapid}/{version}` 图必须先 `open_map` 打开才能读到完整元数据。 ### 入参(Query) | 字段 | 说明 | |------|------| | `enableEditMeta` | `true` 时返回可编辑字段;默认 `false` | | `fileid` | 罕见用法:读源文件级元数据 | ### 出参(节选) 字段非常多,以下是二次开发最常用的子集: | 字段 | 类型 | 含义 | |------|------|------| | `mapid / name / version / fileid / uploadname / filename` | string | 定位 | | `bounds` | string("[minx,miny,maxx,maxy]") | 整图范围 | | `dbBounds` / `drawBounds` / `initBounds` / `viewBounds` | string | 各阶段范围 | | `createTime` / `time` / `mtime` | string | 时间戳 | | `layers` | JSON 字符串(数组) | 图层列表 | | `styles` | JSON 字符串(数组) | 样式列表 | | `layouts` | string,逗号分隔 | 布局名 | | `findFonts` / `preferableFonts` | JSON/字符串 | 字体字典 | | `geomRecordCount` | string | 几何记录总数(估值) | | `ucsorg` | string | UCS 原点矩阵 | | `pid` | string | 进程 PID(仅 GeomRender) | > ⚠ 注意:`layers` / `styles` / `findFonts` 等被序列化成**字符串**存在顶层;SDK 不会自动解嵌套 JSON,请按需 `json.loads()`。 ### 示例 ```python svc.open_map("sys_zp", "v1", mapopenway="Memory", fileid="c47be46a2ad2", uploadname="sys_zp.dwg") meta = svc.metadata("sys_zp", "v1") layers = json.loads(meta["layers"]) ``` --- ## 11. 修改元数据 `update_metadata` **URL**:`POST /api/v1/map/cmd/updateMetadata/{mapid}/{version}` 通过 POST body 修改图的"用户可编辑字段"(通常是 description、layerOn 状态、业务标签等)。 ### 入参(JSON body) ```json { "fields": [ { "name": "description", "value": "我是新的描述" }, { "name": "tags", "value": "building,sample" } ] } ``` 具体可编辑字段清单取决于后端版本;二次开发前可用 `metadata?enableEditMeta=true` 先探测。 ### 出参 ```json {"status": true} ``` ### 示例 ```python svc.update_metadata("demo_dwg", "v1", body={ "fields": [{"name": "description", "value": "hello"}] }) ``` --- ## 12. 缩略图 `thumbnail` **URL**:`GET /api/v1/map/cmd/thumbnail/{mapid}/{version}?width=W&height=H[&layers=...][&backcolor=...]` 返回 PNG 图片字节流。 ### 常用参数 | Query | 说明 | |-------|------| | `width` / `height` | 缩略图像素大小 | | `layers` | 仅渲染指定图层;逗号分隔 | | `backcolor` | 背景色(`0xRRGGBB` 数字或 `#RRGGBB`) | ### SDK ```python svc.open_map("sys_zp", "v1", mapopenway="GeomRender", fileid="c47be46a2ad2", uploadname="sys_zp.dwg") png = svc.thumbnail_bytes("sys_zp", "v1", width=256, height=160) open("thumb.png","wb").write(png) ``` ```javascript const buf = await svc.thumbnailBytes('sys_zp', 'v1', { width: 256, height: 160 }); fs.writeFileSync('thumb.png', buf); ``` 也可以只构造 URL 直接嵌到 ``: ```python url = svc.thumbnail_url("sys_zp", "v1", width=128, height=96) # => http://127.0.0.1:27660/api/v1/map/cmd/thumbnail/sys_zp/v1?width=128&height=96&token=... ``` --- ## 13. 支持的格式 `support_format` **URL**:`GET /api/v1/map/cmd/supportFormat/_/v1` ### 实测响应 ```json { "cad": "dwg;dxf", "image": "webp;heic;zip;png;pdf;jpg;jpe;jpc;heif;exif;avif;bmp;dib;tiff;hif;hdr;jfif;gif;tif;jpeg" } ``` --- ## 14. 系统常量 `const_data` **URL**:`GET /api/v1/map/cmd/constData/_/v1` 返回系统级字典,最常用的是 **CAD 实体类型号 → AcDb 类名** 的映射。 ### 实测响应(节选) ```json { "entTypeIdMap": { "1": "AcDbLine", "2": "AcDbPolyline", "5": "AcDbSpline", "6": "AcDbArc", "7": "AcDbCircle", "10": "AcDbBlockReference", "11": "AcDbHatch", "12": "AcDbMText", "13": "AcDbText", "34": "AcDb3dSolid" } } ``` 搭配 `query_features` 的 `entType` 过滤参数使用。 --- ## 15. 数据范围 `get_data_bounds` **URL**:`GET /api/v1/map/cmd/getDataBounds/{mapid}/{version}` 返回图上所有 **几何实体**的紧凑包围盒,比 `metadata.bounds`(可能含空图框)更贴合可见数据。 ### 实测响应 ```json { "bounds": [ 587464241.0047014, 3103794303.1750364, 587769759.9828022, 3104004303.209077 ], "status": true } ``` ### 示例 ```python b = svc.get_data_bounds("sys_zp", "v1")["bounds"] # 用来设置 vjmap 初始 fitBounds ``` --- - > 下一分类:[`02-tiles-and-styles.md`](02-tiles-and-styles.md) --- ## api/02-tiles-styles.md # 瓦片 / 样式 > 分类代号:C.2 本章共 **10 个接口**(3 个纯 URL 拼装 + 7 个 REST) vjmap 的瓦片出图有两套机制: 1. **按地图内置默认 layer 出图** `open_map` 之后,可直接用 `raster_tile_url()` / `vector_tile_url()` 拿到 `{z}/{x}/{y}` 占位符 URL,喂给 vjmap。 2. **按自定义 style 出图** 先 `create_map_style()` 拿到 `stylename`,把它作为 `layer` 段传给 tile URL,即可得到"按该 style 渲染"的瓦片。 后续可用 `cmd_update_style()` 原地修改;用 `cmd_switch_layers()` 临时切换可见图层;用 `slice_layer()` 触发预切片缓存;用 `get_slice_cache_zoom()` 查询当前已缓存到几级;用 `delete_style()` 清理。 WMS 出图走的是独立管线,用 `wms_tile_url()` 构造即可,单图走 `cmd/wms/{mapid}/{version}`,多图叠加走 `/service/wms`。 --- ## 快速索引 | 序号 | SDK 方法 | HTTP | URL | |----:|---------|------|-----| | 16 | `raster_tile_url` / `rasterTileUrl` | GET | `/api/v1/map/tile/{mapid}/{version}/{layer}/{z}/{x}/{y}` | | 17 | `vector_tile_url` / `vectorTileUrl` | GET | `/api/v1/map/tile/{mapid}/{version}/{layer}/{z}/{x}/{y}.mvt` | | 18 | `wms_tile_url` / `wmsTileUrl`(单图) | GET | `/api/v1/map/cmd/wms/{mapid}/{version}` | | 19 | `wms_tile_url` / `wmsTileUrl`(多图) | GET | `/api/v1/service/wms` | | 20 | `create_map_style` / `createMapStyle` | POST | `/api/v1/map/cmd/createMapStyle/{mapid}/{version}` | | 21 | `cmd_switch_layers` / `cmdSwitchLayers` | POST | `/api/v1/map/cmd/switchlayer/{mapid}/{version}` | | 22 | `cmd_update_style` / `cmdUpdateStyle` | POST | `/api/v1/map/cmd/updateStyle/{mapid}/{version}` | | 23 | `slice_layer` / `sliceLayer` | POST | `/api/v1/map/cmd/slicelayer/{mapid}/{version}` | | 24 | `get_slice_cache_zoom` / `getSliceCacheZoom` | POST | `/api/v1/map/cmd/getSliceCacheZoom/{mapid}/{version}` | | 25 | `delete_style` / `deleteStyle` | POST | `/api/v1/map/cmd/deletestyle/{mapid}/{version}` | > **注意**:10/21/22/23/24/25 这 6 个是 `cmd` 路由,**POST 必须带非空 JSON body**(即使是 `{"noop":true}`),否则会返回 `500 param error`。SDK 已经替你处理好这点,直接调方法即可。 --- ## 16 · 栅格瓦片 URL **SDK**:`svc.raster_tile_url(mapid?, version?, layer?, fileid?)` ### 请求参数 | 字段 | 类型 | 说明 | |------|------|------| | `mapid` | string | 可省,默认为当前 `_cur_map.mapid` | | `version` | string | 可省,默认 `v1` | | `layer` | string | 可省,默认 `_`(即图档内置默认 style)。若先调用 `create_map_style`,把返回的 `stylename` 传进来即可 | | `fileid` | string | 可省。主要用于 tile 缓存破坏。后端不校验,只要 `{mapid,version,layer}` 对,tile 就出得来 | ### 实采示例 ```json "http://127.0.0.1:27660/api/v1/map/tile/sys_zp/v1/_/{z}/{x}/{y}?tag=c47be46a2ad2&token=" ``` ### Python ```python from vjmap import Vjmap, MapOpenWay svc = Vjmap("http://127.0.0.1:27660/api/v1", TOKEN) svc.open_map(mapid="sys_zp", version="v1", mapopenway=MapOpenWay.GeomRender) print(svc.raster_tile_url()) ``` ### Node.js ```javascript import { Vjmap, MapOpenWay } from './sdk/nodejs/vjmap.js'; const svc = new Vjmap('http://127.0.0.1:27660/api/v1', TOKEN); await svc.openMap({ mapid: 'sys_zp', version: 'v1', mapopenway: MapOpenWay.GeomRender }); console.log(svc.rasterTileUrl()); ``` --- ## 17 · 矢量瓦片 URL(MVT) **SDK**:`svc.vector_tile_url(...)`,参数与 `raster_tile_url` 完全一致,区别是 URL 末尾多了 `.mvt` 后缀。 ### 实采示例 ```json "http://127.0.0.1:27660/api/v1/map/tile/sys_zp/v1/main/{z}/{x}/{y}.mvt?tag=c47be46a2ad2&token=" ``` `{layer}` 默认是 `main`(内置 MVT 默认名),不是 `_`。 ### 用法 MVT 非常适合作为 vjmap `vector` source: ```javascript map.addSource('vj', { type: 'vector', tiles: [svc.vectorTileUrl()], minzoom: 0, maxzoom: 22, }); map.addLayer({ id: 'vj-line', source: 'vj', 'source-layer': 'default', type: 'line' }); ``` --- ## 18 / 19 · WMS 瓦片 URL **SDK**:`svc.wms_tile_url(mapid, version?, *, layer?, width?, height?, bbox?, srs?, backcolor?, mvt?)` ### 参数规则 - `mapid`: 可以是字符串,也可以是 `list[str]` - **单图** → 走 `/api/v1/map/cmd/wms/{mapid}/{version}` - **多图** → 走 `/api/v1/service/wms?mapid=a;b;c&version=v1;v1;v1` - `bbox`: 默认 `{bbox-epsg-3857}` 占位符。如果你直接用 如Leaflet,就保留占位符交给前端去替换。SDK 刻意**不对 `bbox` 做 percent-encode**。 - `srs`: 坐标系,默认 `EPSG:3857`。 - `mvt=True`: URL 末尾加 `.mvt`,输出矢量。 - `backcolor`: 背景色,十进制 RGB(0 = 黑/透明)。 - `layer`: 可选,指定某个 stylename 渲染。 ### 实采示例 单图: ```json "http://127.0.0.1:27660/api/v1/map/cmd/wms/sys_zp/v1?bbox={bbox-epsg-3857}&srs=EPSG%3A3857&width=512&height=512&token=" ``` 多图: ```json "http://127.0.0.1:27660/api/v1/service/wms?bbox={bbox-epsg-3857}&mapid=sys_zp&version=v1&srs=EPSG%3A3857&width=256&height=256&token=" ``` ### Python ```python url = svc.wms_tile_url("sys_zp", "v1", width=512, height=512) url_multi = svc.wms_tile_url(["sys_zp", "other"], ["v1", "v1"]) ``` ### Node.js ```javascript const url = svc.wmsTileUrl({ mapid: 'sys_zp', version: 'v1', width: 512, height: 512 }); const urlMulti = svc.wmsTileUrl({ mapid: ['sys_zp', 'other'], version: ['v1', 'v1'] }); ``` --- ## 20 · 创建地图样式 `createMapStyle` **URL**:`POST /api/v1/map/cmd/createMapStyle/{mapid}/{version}` **SDK**:`svc.create_map_style(mapid, version, *, layeron, layeroff, backcolor, clipbounds, expression, lineweight, is_geom_layer=True, name)` ### 请求参数 | 字段 | 类型 | 说明 | |------|------|------| | `geom` | bool | `true` 几何渲染(默认);`false` 栅格渲染 | | `name` | string | style 名字,可自动生成 | | `layeron` | string / 数组 | 可见图层;数组传入时 SDK 会自动转成 `(0,1,2)` 格式 | | `layeroff` | string / 数组 | 隐藏图层 | | `backcolor` | int | 背景色(0=黑色,常用于暗色底图) | | `clipbounds` | string | 裁剪范围 JSON 字符串 | | `expression` | string | Cesium 风格的颜色表达式 | | `lineweight` | int[] | 线宽缩放 `[默认,最小,最大]` | ### 响应(实采) ```json { "stylename": "sfbd35705c" } ``` 返回的 `stylename` 就是后续作为瓦片 URL 中 `{layer}` 段的值。 ### Python ```python style = svc.create_map_style("sys_zp", "v1", layeron="*", backcolor=0) tile_url = svc.raster_tile_url(layer=style["stylename"]) ``` ### Node.js ```javascript const style = await svc.createMapStyle({ mapid: 'sys_zp', version: 'v1', layeron: '*', backcolor: 0 }); const tileUrl = svc.rasterTileUrl({ layer: style.stylename }); ``` ### curl ```bash curl -X POST "http://127.0.0.1:27660/api/v1/map/cmd/createMapStyle/sys_zp/v1?token=$TOKEN" \ -H 'Content-Type: application/json' \ -d '{"geom":true,"layeron":"*","backcolor":0}' ``` --- ## 21 · 切换可见图层 `switchlayer` **URL**:`POST /api/v1/map/cmd/switchlayer/{mapid}/{version}` **SDK**:`svc.cmd_switch_layers(visible_layers, *, expression?, dark_mode?, mapid?, version?)` ### 请求参数 | 字段 | 类型 | 说明 | |------|------|------| | `visibleLayer` | string[] | 要可见的图层 id 列表 | | `expression` | string | 颜色表达式(与 createMapStyle 同) | | `darkMode` | bool | 是否暗色(可在 openMap 时也指定) | ### 响应(实采) ```json { "layerid": "s24d720029" } ``` 与 `createMapStyle` 的 `stylename` 相似,`layerid` 指向新的内部 style(后端按参数 hash 复用,不会无限增长)。 ### Python ```python res = svc.cmd_switch_layers(["0"]) print(res["layerid"]) ``` ### Node.js ```javascript const res = await svc.cmdSwitchLayers(["0"]); console.log(res.layerid); ``` --- ## 22 · 原地修改样式 `updateStyle` **URL**:`POST /api/v1/map/cmd/updateStyle/{mapid}/{version}` **SDK**:`svc.cmd_update_style(*, name, layeron, layeroff, backcolor, clipbounds, expression, lineweight, mapid?, version?)` 与 `createMapStyle` 最大的区别:**它可以修改已有 style(传同名 name)**,而不是每次新建。 ### 响应(实采) ```json { "bounds": "[587227463.27, 3103509765.63, 588006538.39, 3104288840.75]", "boundsChange": false, "layerid": "m765111cb4" } ``` `boundsChange` = false 表示裁剪范围没变;若 `clipbounds` 修改了,`bounds` 与 `boundsChange` 会反映出来。 ### Python ```python svc.cmd_update_style( mapid="sys_zp", version="v1", name="nb_style_1", layeron="*", backcolor=0, ) ``` ### Node.js ```javascript await svc.cmdUpdateStyle({ mapid: 'sys_zp', version: 'v1', name: 'nb_style_1', layeron: '*', backcolor: 0, }); ``` --- ## 23 · 触发预切片缓存 `slicelayer` **URL**:`POST /api/v1/map/cmd/slicelayer/{mapid}/{version}` **SDK**:`svc.slice_layer(*, layer, zoom, ismvt, iscancel, is_all_cancel, batch_num?, idle_batch_sleep_ms?, busy_batch_sleep_ms?, mapid?, version?)` ### 请求参数 | 字段 | 类型 | 说明 | |------|------|------| | `layer` | string | 要预切的 layer id(即 stylename) | | `zoom` | int / int[] / string | 要切的 zoom 层级。单个或数组 | | `ismvt` | bool | true 切 MVT,false 切栅格 | | `iscancel` | bool | 取消本次任务 | | `isAllCancel` | bool | 取消该地图所有正在进行的切片任务 | | `batchnum` | int | 批处理大小 | | `idle_batch_sleepms` | int | 空闲批间隔毫秒 | | `busy_batch_sleepms` | int | 忙时批间隔毫秒 | ### 响应(实采) 取消切片: ```json {} ``` 即空对象。真实触发切片时后端会流式推进度(`{progress:0.5,total:...}`),这里我们的 SDK 只取第一响应。 ### Python ```python svc.slice_layer(layer="s24d720029", zoom=[0, 1, 2, 3], ismvt=False) ``` ### Node.js ```javascript await svc.sliceLayer({ layer: 's24d720029', zoom: [0,1,2,3], ismvt: false }); ``` --- ## 24 · 查询已缓存的切片级别 `getSliceCacheZoom` **URL**:`POST /api/v1/map/cmd/getSliceCacheZoom/{mapid}/{version}` **SDK**:`svc.get_slice_cache_zoom(*, layer, ismvt, mapid?, version?)` ### 响应(实采) ```json { "sliceCacheZoom": 0 } ``` `sliceCacheZoom` = 当前已稳定缓存到的最大 zoom。若 = 0 通常表示还没跑过预切片。 ### Python / Node ```python print(svc.get_slice_cache_zoom(layer="s24d720029", ismvt=False)) ``` ```javascript console.log(await svc.getSliceCacheZoom({ layer: 's24d720029', ismvt: false })); ``` --- ## 25 · 删除自定义样式 `deletestyle` **URL**:`POST /api/v1/map/cmd/deletestyle/{mapid}/{version}` **SDK**:`svc.delete_style(stylename, *, mapid?, version?)` ### 请求参数 | 字段 | 类型 | 说明 | |------|------|------| | `stylename` | string | 要删除的 style 名(即 `create_map_style` 返回的 `stylename`) | ### 响应(实采) 即便 stylename 不存在也返回空对象(幂等): ```json {} ``` ### Python / Node ```python svc.delete_style("sfbd35705c", mapid="sys_zp", version="v1") ``` ```javascript await svc.deleteStyle('sfbd35705c', { mapid: 'sys_zp', version: 'v1' }); ``` --- ## 组合示例:从开图到看地图 ```python from vjmap import Vjmap, MapOpenWay svc = Vjmap("http://127.0.0.1:27660/api/v1", TOKEN) svc.open_map(mapid="sys_zp", version="v1", mapopenway=MapOpenWay.GeomRender) # 1. 自定义一个黑底白线风格 style = svc.create_map_style( "sys_zp", "v1", layeron="*", backcolor=0, expression="gOutColorRed:=255;gOutColorGreen:=255;gOutColorBlue:=255;gOutColorAlpha:=255;", ) # 2. 拿 tile URL 给 vjmap raster = svc.raster_tile_url(layer=style["stylename"]) # 3. 预切前 4 级,加快首屏 svc.slice_layer(layer=style["stylename"], zoom=[0,1,2,3], ismvt=False) # 4. 不用了,清掉 svc.delete_style(style["stylename"], mapid="sys_zp", version="v1") ``` --- ## api/03-query.md # 要素查询 / 几何 > 分类代号:C.3 本章共 **4 个接口** 本章汇集所有"拿到几何 / 属性 / 投影"的基础操作。其中 `queryFeatures` 是最常用的接口,根据 `querytype` 可覆盖点/矩形/表达式/条件 4 种查询场景,因此 SDK 只暴露一个 `query_features(querytype=...)` 统一入口。 --- ## 快速索引 | 序号 | SDK 方法 | HTTP | URL | |----:|---------|------|-----| | 26 | `query_features` / `queryFeatures` | POST | `/api/v1/map/cmd/queryFeatures/{mapid}/{version}` | | 27 | `create_entity_geo_data` / `createEntityGeoData` | POST | `/api/v1/map/cmd/createEntityGeoData/_null/v1` | | 28 | `coord_transform` / `coordTransform` | POST | `/api/v1/map/cmd/coordtransform/_/_` | | 29 | `prj_wkt_to_prj4_str` / `prjWktToPrj4Str` | POST | `/api/v1/map/cmd/prjWktToPrj4Str/_/_` | --- ## 26 · 要素查询 `queryFeatures` **URL**:`POST /api/v1/map/cmd/queryFeatures/{mapid}/{version}` **SDK**:`svc.query_features(querytype, ...)` / `svc.queryFeatures({ querytype, ... })` ### `querytype` 枚举 | 值 | 含义 | 必填参数 | |----|------|---------| | `point` | 点查询:在屏幕像素范围内找要素 | `x`, `y`, `pixelsize`, `zoom`, `pixelToGeoLength` | | `rect` | 矩形查询:几何 bounds 内找要素 | `x1`, `y1`, `x2`, `y2` | | `expresion` | 表达式查询:按 DWG 属性字段过滤 | `expr` | | `condition` | 条件查询:按后端预定义条件 | `condition`(可空),可选 `bounds` | > ⚠️ `expresion` 是后端拼写,**不是** `expression`。 ### 可选参数(所有 querytype 通用) | 字段 | 说明 | |------|------| | `limit` | 最大返回条数(同时会写入 `maxReturnCount` 和 `limit` 两个字段) | | `fields` | 要返回的属性字段,逗号分隔,空串 = 全部 | | `geom` | 是否返回几何(默认 true,false 则只返回 envelop/attrs) | | `simplifyTolerance` | 几何简化容差 | | `useCache` | 开启查询缓存(condition/expresion 常用) | | `toMapCoordinate` | 是否把返回坐标转成地图坐标系 | | `maxGeomBytesSize` | 单条几何字节大小上限(超过则降级为 envelop) | ### 响应结构 ```json { "recordCount": 5, "result": [ { "id": 1, "objectid": "EA_ED__EE_#", "layerindex": 13, "name": "AcDbArc", "geojson": "...", "envelop": "POLYGON(...)", "bounds": "[minx,miny,maxx,maxy]", "isEnvelop": true, "color": -65281, "linetype": "BYLAYER", "xdata": "", "center": "x,y,z", "points": "x1,y1,z1;x2,y2,z2;...", "colorIndex": 6 } ] } ``` - `recordCount`:**后端实际扫到的全量数量**,不是本次返回条数。想分页时根据 `recordCount` + `beginpos` 自行控制。 - `geojson` 是字符串而非对象,调用方需要再 `JSON.parse()`。 - `isEnvelop=true` 说明 geojson 被替换成了 envelop(单条太大或命中 `maxGeomBytesSize`)。 ### 实采(rect 查询 sys_zp/v1,limit=5) ```json { "recordCount": 5, "result": [{ "id": 1, "objectid": "EA_ED__EE_#", "layerindex": 13, "name": "AcDbArc", "geojson": "{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"Polygon\",\"coordinates\":[[[2271440.45,-685837.25],[2271440.45,-679783.61],[2276451.33,-679783.61],[2276451.33,-685837.25],[2271440.45,-685837.25]]]}]}", "envelop": "POLYGON((2271440.45 -685837.25, ... ))", "bounds": "[587661158.58,3103885970.22,587661256.00,3103886087.91]", "isEnvelop": true, "color": -65281, "linetype": "BYLAYER" }] } ``` ### 实采(condition 查询,limit=1) ```json { "recordCount": 6982, "result": [{ "id": 1, "name": "AcDbArc", "geojson": "", "envelop": "POLYGON(...)", "bounds": "[...]", "isEnvelop": false, "color": -65281, "center": "587661197.39,3103886029.19,-152738.04", "points": "x1,y1,z1;x2,y2,z2;x3,y3,z3", "colorIndex": 6 }] } ``` `recordCount=6982` 表示该地图共 6982 个要素满足条件;`limit=1` 只取第一条。 ### Python ```python # 矩形查询:sys_zp/v1 全图前 5 条 meta = svc.open_map(mapid="sys_zp", version="v1", mapopenway=MapOpenWay.GeomRender) x1, y1, x2, y2 = [float(x) for x in meta["bounds"][1:-1].split(",")][:4] res = svc.query_features( querytype="rect", mapid="sys_zp", version="v1", x1=x1, y1=y1, x2=x2, y2=y2, limit=5, ) for item in res["result"]: print(item["id"], item["name"]) # 点查询:屏幕 5 像素范围 res = svc.query_features( querytype="point", x=587661197.39, y=3103886029.19, zoom=10, pixelsize=5, pixel_to_geo_length=100.0, limit=3, ) ``` ### Node.js ```javascript const res = await svc.queryFeatures({ querytype: 'rect', mapid: 'sys_zp', version: 'v1', x1, y1, x2, y2, limit: 5, }); const byExpr = await svc.queryFeatures({ querytype: 'expresion', expr: "layerindex=13", limit: 10, }); ``` ### curl ```bash curl -X POST "http://127.0.0.1:27660/api/v1/map/cmd/queryFeatures/sys_zp/v1?token=$TOKEN" \ -H 'Content-Type: application/json' \ -d '{"querytype":"condition","layername":"","zoom":1,"limit":1}' ``` --- ## 27 · 构造实体几何 `createEntityGeoData` **URL**:`POST /api/v1/map/cmd/createEntityGeoData/_null/v1` **SDK**:`svc.create_entity_geo_data(mapdata=..., map_extent=..., render_accuracy=..., exclude_attribute=..., use_zip=...)` > `mapdata` 是前端 `@vj/draw` 构造的 DWG 结构体(JSON 字符串)。独立使用场景较少,通常和前端绘图配合。 ### 请求参数 | 字段 | 类型 | 说明 | |------|------|------| | `mapdata` | string | 前端传入的 DWG JSON 字符串 | | `mapExtent` | string | 地图范围 JSON:`[minx,miny,maxx,maxy]` | | `renderAccuracy` | number | 渲染精度(像素) | | `excludeAttribute` | bool | 是否排除属性(默认 true) | | `useZip` | bool | 是否对 mapdata 做 gzip 压缩(默认 true) | ### 响应 - 合法输入:`{ "result": [...], "bounds": "...", ... }`(与 queryFeatures 类似) --- ## 28 · 坐标转换 `coordtransform` **URL**:`POST /api/v1/map/cmd/coordtransform/_/_` **SDK**:`svc.coord_transform(srs=..., crs=..., points=..., four_parameter=..., is_inverse_four_parameter=...)` ### 请求参数 | 字段 | 类型 | 说明 | |------|------|------| | `srs` | string | 源坐标系,例如 `EPSG:4326` | | `crs` | string | 目标坐标系,例如 `EPSG:3857` | | `points` | string | 多点串:`x1,y1;x2,y2` | | `fourParameter` | string | 可选四参数:`xOff,yOff,scale,rotRad` | | `isInverseFourParamter` | bool | 是否反向应用四参数(注意后端拼写是 `Paramter`) | SDK 的 `points` 接受: - 单点 `(x,y)` 或 `{"x":1,"y":2}` - 多点 `[(x1,y1),(x2,y2)]` - `fourParameter` 支持 `"x,y,s,r"` 或 `[x,y,s,r]` ### 响应(实采:4326 → 3857 两点) ```json { "points": [ [12957254.769864663, 4852582.082864688], [13522423.824622115, 3662655.1833045874] ] } ``` ### Python ```python res = svc.coord_transform( srs="EPSG:4326", crs="EPSG:3857", points=[(116.397, 39.908), (121.474, 31.23)], ) print(res["points"]) # [[12957254.77, 4852582.08], [13522423.82, 3662655.18]] ``` ### Node.js ```javascript const res = await svc.coordTransform({ srs: 'EPSG:4326', crs: 'EPSG:3857', points: [[116.397, 39.908], [121.474, 31.23]], }); console.log(res.points); ``` --- ## 29 · WKT → PROJ4 `prjWktToPrj4Str` **URL**:`POST /api/v1/map/cmd/prjWktToPrj4Str/_/_` **SDK**:`svc.prj_wkt_to_prj4_str(wkt, from_="")` ### 请求参数 | 字段 | 类型 | 说明 | |------|------|------| | `wkt` | string | ESRI prj 文件内容或 WKT 字符串 | | `from` | string | 数据来源。空串 = WKT;其他可选 `wmsauto / xml / urn / crsurl / url / micoordsys / pci` | ### 响应(实采:WGS84 WKT) ```json { "proj4": "+proj=longlat +datum=WGS84 +no_defs" } ``` ### Python ```python wkt = open("sample.prj", encoding="utf-8").read() res = svc.prj_wkt_to_prj4_str(wkt) print(res["proj4"]) ``` ### Node.js ```javascript import { readFileSync } from 'node:fs'; const wkt = readFileSync('sample.prj', 'utf8'); const res = await svc.prjWktToPrj4Str(wkt); console.log(res.proj4); ``` --- --- ## api/04-analysis.md # 图形分析 > 分类代号:C.4 本章共 **6 个接口** 这一组接口都是 vjmap 的"增值分析"能力,参数差异大、且对输入的 DWG/图像质量要求很高,因此 SDK 采用 **pass-through** 风格:你按下面文档组参数,SDK 负责 POST 与错误处理,**不做参数字段级强校验**。 全部走 `POST /api/v1/map/cmd/{cmd}/_null/v1`(`_null` 表示不绑定具体 mapid,参数里自带)。 --- ## 快速索引 | 序号 | SDK 方法 | cmd | |----:|---------|------| | 30 | `cmd_map_diff` / `cmdMapDiff` | `mapDiff` | | 31 | `cmd_map_compare` / `cmdMapCompare` | `mapCompare` | | 32 | `cmd_extract_table` / `cmdExtractTable` | `extractTable` | | 33 | `cmd_extract_center_lines` / `cmdExtractCenterLines` | `extractCenterLines` | | 34 | `cmd_match_object` / `cmdMatchObject` | `objectMatch` | | 35 | `cmd_image_svg_to_dwg` / `cmdImageSvgToDwg` | `imageSvgToDwg` | --- ## 30 · 像素差异 `mapDiff` 比较两张 DWG **渲染成图片后的像素差异**,返回增删改的像素块外包矩形中心点。 ### 请求参数 | 字段 | 类型 | 说明 | |------|------|------| | `mapid1` / `version1` | string | 旧图 | | `mapid2` / `version2` | string | 新图 | | `darkMode` | bool | 是否使用暗色底渲染 | | `bufferSize` | int | 像素容差,相同像素在此距离内不判为"改" | | `mergeClose` | bool | 合并靠得很近的差异块 | ### 响应(实采:同图 vs 同图) ```json { "del": "", "modify": "", "new": "", "status": true } ``` 有差异时 `del` / `modify` / `new` 是 `x1,y1,x2,y2;x3,y3,x4,y4` 格式的字符串;`svc.cmd_map_diff()` 不做 parse,调用方按需切分。 ### Python ```python res = svc.cmd_map_diff( mapid1="sys_zp", version1="v1", mapid2="sys_zp", version2="v2", bufferSize=5, ) ``` --- ## 31 · 实体对比 `mapCompare` 和 `mapDiff` 不同:`mapCompare` 比较的是 **DWG 实体**,不是渲染像素。 ### 请求参数 | 字段 | 类型 | 说明 | |------|------|------| | `mapid1` / `version1`, `mapid2` / `version2` | string | 两张图 | | `tolerancePos` | number | 坐标容差 | | `toleranceAngle` | number | 角度容差(度) | 后端会先做一次快速 hash 去重。真实对比时返回: ```json { "status": true, "add": [...], // 仅在新图存在的实体 id "del": [...], // 仅在旧图存在 "modify": [...] // 两边都有但有差异 } ``` ### Python ```python svc.cmd_map_compare(mapid1="a", version1="v1", mapid2="a", version2="v2") ``` --- ## 32 · 表格提取 `extractTable` 自动从 DWG 里识别"线条围出来的表格",返回单元格网格与每格文本。**最常用**于检图、清单导出。 ### 请求参数 | 字段 | 说明 | |------|------| | `mapid` / `version` | 要识别的图 | | `layer` | 可选,只在某 style / layer 上跑 | | `bbox` | 可选,限定在地图坐标系下的 bbox | | `minCellWidth` / `minCellHeight` | 最小单元格尺寸阈值 | ### 响应(实采:sys_zp/v1,无额外参数) ```json { "tables": [{ "attr": { "cellEmptyRatio": 45, "tableCellMaxCount": 70, "tableTextCount": 39, "unLinkLineRatio": 0 }, "colCount": 4, "cols": ["587693680.21", "587698180.22", "587717259.64", "..."], "datas": [["类目", "数量", "价格", "说明"], [...]], "rows": ["...", "..."] }] } ``` - `cols` / `rows`:单元格网格线的坐标; - `datas[i][j]`:每格文本(可能含编码乱码,视 DWG 字体而定)。 ### Python ```python res = svc.cmd_extract_table(mapid="sys_zp", version="v1") for t in res.get("tables", []): print(t["colCount"], "x", len(t["rows"])) ``` ### Node.js ```javascript const res = await svc.cmdExtractTable({ mapid: 'sys_zp', version: 'v1' }); console.log(res.tables?.length); ``` --- ## 33 · 中心线提取 `extractCenterLines` 对闭合多边形(如道路、管线)抽取中心线。 ### 请求参数 | 字段 | 说明 | |------|------| | `mapid` / `version` | 源图 | | `layer` | 可选,限定 layer | | `expr` | 可选,按属性过滤实体(与 queryFeatures 的 expr 同义) | ### 响应(实采:sys_zp/v1) ```json { "centerlines": [ [[587562497.79, 3103857885.77], [587556239.69, 3103860401.38]], [[587603886.22, 3103921932.46], [587602627.19, 3103925333.77]] ] } ``` `centerlines` 是"多段折线数组";每条线由若干 `[x, y]` 顶点构成。 ### Python ```python res = svc.cmd_extract_center_lines(mapid="sys_zp", version="v1") for line in res["centerlines"]: print("line with", len(line), "verts") ``` --- ## 34 · 目标匹配 `objectMatch` 在底图中找与模板图形相似的子图(样式 + 尺寸)。常用于批量识别图章、标注块、设备。 ### 请求参数 | 字段 | 类型 | 默认值 | 说明 | |------|------|-------|------| | `mapid` / `version` | string | — | 底图 | | `templateMapid` / `templateVersion` | string | — | 模板图 | | `objectBounds` | string / number[] | — | **必填**:模板所在的 bbox(地图坐标) `[x1,y1,x2,y2]` | | `score` | number | 0.6 | 相似度阈值 | | `canOverlap` | bool | false | 命中框是否允许重叠 | | `maxOverlap` | number | 0.3 | 允许的最大重叠比例 | | `toleranceAngle` | number | 180 | 角度容差(度),180 = 任意旋转 | | `minReduceArea` | number | 256 | 小于此面积的命中框被忽略 | ### 响应 成功时返回: ```json { "status": true, "matches": [ { "bounds":"[x1,y1,x2,y2]", "angle": 0.0, "score": 0.92 }, ... ] } ``` ### Python ```python res = svc.cmd_match_object( mapid="sys_zp", version="v1", templateMapid="sys_zp", templateVersion="v1", objectBounds="[587661000,3103886000,587661200,3103886200]", score=0.7, toleranceAngle=90, ) ``` --- ## 35 · SVG / 图像转 DWG `imageSvgToDwg` 把 SVG 字符串或位图转换成 DWG 实体。对位图会先做线条检测 + 矢量化。 ### 请求参数 | 字段 | 说明 | |------|------| | `svgData` | SVG 文本 | | `imageUrl` | 远程图片 URL(与 svgData 二选一) | | `threshold` | 位图二值化阈值(0–255) | | `mapid` / `version` | 可选:把结果直接写入该地图;为空则只返回 DWG JSON | ### 响应(实采:svgData="``",空内容) ```json { "error": "image or svg content is empty", "status": false } ``` 合法输入时返回: ```json { "status": true, "mapdata": "" } ``` 可以把 `mapdata` 传给 `create_entity_geo_data`(见 [03-query.md](./03-query.md))渲染。 --- ## 通用注意 1. 这组接口都可能 **耗时 >10s**,SDK 默认 timeout=60s,建议在业务层再做重试与超时兜底。 2. 后端遇到参数非法时常返回 `{"error": "...", "status": false}`——不是 HTTP 4xx/5xx。SDK 会把它当成 2xx 正常响应返回给调用方。 3. 返回 `"status": false` 时 `error` 字段的语言**依系统 locale 而定**(我们这里抓到了中/英混合的错误串)。 --- --- ## api/05-compose-export.md # 组合 / 拆分 / 导出 > 分类代号:C.5 本章共 **6 个接口** 把多张 DWG 组合成一张、把一张大图按图框 / 图层拆成多张、把图纸导出为 PDF / DXF 或离线瓦片包——这一章就是"图纸经纪人"。 **共同特点**:这 6 个接口都相对重,后端会真实创建新图档 / 新文件。测试环境请用临时 mapid,不要对生产图做实验。 --- ## 快速索引 | 序号 | SDK 方法 | URL | |----:|---------|-----| | 36 | `cmd_compose_new_map` / `cmdComposeNewMap` | `POST /api/v1/map/cmd/composeNewMap/_null/v1` | | 37 | `cmd_split_map` / `cmdSplitMap` | `POST /api/v1/map/cmd/cmdSplitMap/_null/v1` | | 38 | `cmd_export_layout` / `cmdExportLayout` | `POST /api/v1/map/cmd/exportLayout/_null/v1` | | 39 | `cmd_export_pdf` / `cmdExportPdf` | `POST /api/v1/map/cmd/exportPdf/{mapid}/{version}` | | 40 | `cmd_export_dxf` / `cmdExportDxf` | `POST /api/v1/map/cmd/exportDxf/{mapid}/{version}` | | 41 | `cmd_save_offline_tile_db` / `cmdSaveOfflineTileDb` | `POST /api/v1/map/cmd/saveOfflineTileDb/_null/v1` | --- ## 36 · 组合新地图 `composeNewMap` 把多张已有 DWG 按几何变换(偏移 + 旋转)拼成一张。 ### 请求参数 | 字段 | 类型 | 说明 | |------|------|------| | `maps` | array | 组合清单:`[{mapid, version, dx?, dy?, angle?}]` | | `newMapid` | string | 新图 mapid | | `newVersion` | string | 新图 version(可省) | | `bounds` | string | 可选:组合后的 bbox 限定 | ### 响应 成功:`{ "status": true, "mapid": "...", "fileid": "...", "version": "v1" }` 失败(实采:源文件不存在): ```json { "error": "file is not exist, ", "status": false } ``` ### Python ```python svc.cmd_compose_new_map( maps=[ {"mapid": "part_a", "version": "v1", "dx": 0, "dy": 0}, {"mapid": "part_b", "version": "v1", "dx": 1000.0, "dy": 0, "angle": 0}, ], newMapid="assembled_map", ) ``` ### Node.js ```javascript await svc.cmdComposeNewMap({ maps: [ { mapid: 'part_a', version: 'v1', dx: 0, dy: 0 }, { mapid: 'part_b', version: 'v1', dx: 1000, dy: 0, angle: 0 }, ], newMapid: 'assembled_map', }); ``` --- ## 37 · 拆分子图 `cmdSplitMap` 按图框线 / 按图层把一张大图拆成多张小图。 ### 请求参数 | 字段 | 类型 | 说明 | |------|------|------| | `mapid` / `version` | string | 源图 | | `splitType` | string | `frame`(按图框)/ `layer`(按图层) | | `framelayer` | string | 仅 splitType=frame 时:图框线所在图层 | | `clipBounds` | string | 也可以直接给一系列 bbox 列表拆 | | `outNamePrefix` | string | 新图 mapid 前缀,默认用源 mapid + 序号 | ### 响应 成功:`{ "status": true, "maps": [{mapid, version, bounds}, ...] }` 失败(实采:frame 拆分未给 clipBounds): ```json { "error": "clipBounds is empty" } ``` --- ## 38 · 导出布局为 DWG `exportLayout` 从 DWG 的**布局空间**(paper space)单独导出一张新 DWG。CAD 里经常一个文件多个布局(A3、A2、首页),这个接口能一次性把某个布局变成独立图档。 ### 请求参数 | 字段 | 类型 | 说明 | |------|------|------| | `mapid` / `version` | string | 源图 | | `layoutIndex` | int | 布局序号(0 起) | | `layoutName` | string | 或直接用布局名,优先级高于 `layoutIndex` | | `newMapid` | string | 可选:直接导入为新地图 | | `newVersion` | string | 可选:新地图 version | ### 响应(实采:sys_zp/v1, layoutIndex=0) ```json { "fileid": "pf68817b9655", "status": true } ``` `fileid` 指向后端缓存里的新 DWG 文件,可以接着调用 `open_map(mapid=...)` 打开,或用 `exportDxf / exportPdf` 进一步出图。 ### Python ```python res = svc.cmd_export_layout(mapid="sys_zp", version="v1", layoutIndex=0) assert res["status"] print(res["fileid"]) # 'pf68817b9655' ``` --- ## 39 · 导出 PDF `exportPdf` 把指定图纸输出为 PDF。支持按地图 bbox 截图 + 指定纸张。 ### 请求参数 | 字段 | 类型 | 说明 | |------|------|------| | `bounds` / `bbox` | string | 要导出的 bbox:`[minx,miny,maxx,maxy]` | | `width` / `height` | number | 纸张尺寸(mm),常用 A4=`297×210` | | `scale` | number | 比例尺(可选) | | `darkMode` | bool | 是否暗色渲染 | | `outName` | string | 自定义输出文件名 | | `layer` | string | 仅导出某 style / layer | ### 响应(实采:sys_zp/v1, 297×210 mm) ```json { "path": "download/d5f939154c.pdf", "status": true } ``` 完整下载 URL 是 `http://127.0.0.1:27660/{path}?token=`。 ### Python ```python res = svc.cmd_export_pdf( mapid="sys_zp", version="v1", width=297, height=210, ) url = svc.base_gateway() + res["path"] + f"?token={svc.token}" ``` ### Node.js ```javascript const res = await svc.cmdExportPdf({ mapid: 'sys_zp', version: 'v1', width: 297, height: 210, }); const url = `${svc.baseGateway()}${res.path}?token=${svc.token}`; ``` --- ## 40 · 导出 DXF `exportDxf` 把当前图档(或其子集)导出为 ASCII DXF 文件。 ### 请求参数 | 字段 | 类型 | 说明 | |------|------|------| | `bounds` / `bbox` | string | 裁剪 bbox | | `layer` | string | 限定 style / layer | | `dxfVersion` | string | DXF 版本 tag:`AC1009` (R12), `AC1018` (R2004) 等 | | `outName` | string | 自定义输出文件名 | ### 响应 成功:`{ "status": true, "path": "download/xxx.dxf" }` 失败(实采:当前地图没有可导出图元): ```json { "error": "Invalid Symbol Table name", "status": false } ``` ### Python ```python res = svc.cmd_export_dxf( mapid="sys_zp", version="v1", dxfVersion="AC1018", bounds="[587661158,3103885970,587661256,3103886087]", ) ``` --- ## 41 · 离线瓦片包 `saveOfflineTileDb` 把指定地图范围预切并打成 `.mbtiles` / `.sqlitedb` 离线包,方便前端离线展示。 ### 请求参数 | 字段 | 类型 | 说明 | |------|------|------| | `mapid` / `version` | string | 源图 | | `minzoom` / `maxzoom` | int | zoom 范围 | | `bounds` | string | 要切的 bbox | | `ismvt` | bool | true 切 MVT(.mbtiles),false 切栅格 PNG | | `outName` | string | 输出文件名 | ### 响应 成功:`{ "status": true, "path": "download/xxx.mbtiles" }` 失败(实采:缺 `bounds`): ```json { "error": "param error" } ``` ### Python ```python res = svc.cmd_save_offline_tile_db( mapid="sys_zp", version="v1", minzoom=0, maxzoom=6, bounds="[587661158,3103885970,587661256,3103886087]", ismvt=True, outName="sys_zp_offline.mbtiles", ) ``` --- ## 组合示例:DWG → PDF 一把梭 ```python # 1. 上传并打开 DWG up = svc.upload_map("project.dwg", mapid="proj", version="v1") svc.open_map( mapid="proj", version="v1", fileid=up["fileid"], uploadname=up["uploadname"], mapopenway=vjmap.MapOpenWay.GeomRender, ) # 2. 导出第 0 号布局 lay = svc.cmd_export_layout(mapid="proj", version="v1", layoutIndex=0, newMapid="proj_layout0", newVersion="v1") # 3. 打开刚生成的布局图并导 PDF svc.open_map(mapid="proj_layout0", version="v1", fileid=lay["fileid"], uploadname="proj_layout0.dwg", mapopenway=vjmap.MapOpenWay.GeomRender) pdf = svc.cmd_export_pdf(mapid="proj_layout0", version="v1", width=297, height=210) print(svc.base_gateway() + pdf["path"]) ``` --- --- ## api/06-webcad.md # WebCAD(条件启用) > 分类代号:C.6 本章共 **6 个接口** --- ## 启用判定 最推荐的方式: ```python svc = vjmap.Vjmap(API, TOKEN) if svc.webcad_enabled(): draws = svc.list_webcad_draws() else: print("本服务未启用 WebCAD") ``` `svc.webcad_enabled()` 的判定规则: 1. `GET /api/v1/service/webcad` 若返回 2xx 且 **没有** `enabled=false` / `error` 字段,视为"可用"; 2. 若返回 4xx/5xx 或业务错误(如 `Token is invalid`),视为"不可用"。 ### 本仓库实采 ```json // /api/v1/service/webcad?token=... { "status": 403, "body": { "code": 7, "data": { "reload": true }, "msg": "Token is invalid" } } ``` --- ## 快速索引 | 序号 | SDK 方法 | URL | |----:|---------|-----| | 42 | `list_webcad_draws` / `listWebcadDraws` | `GET /api/v1/map/cmd/listWebcadDraws/_/v1` | | 43 | `get_webcad_data` / `getWebcadData` | `POST /api/v1/map/cmd/getWebcadData/{mapid}/{version}` | | 44 | `save_webcad_patch` / `saveWebcadPatch` | `POST /api/v1/map/cmd/saveWebcadPatch/{mapid}/{version}` | | 45 | `delete_webcad_draw` / `deleteWebcadDraw` | `POST /api/v1/map/cmd/deleteWebcadDraw/{mapid}/{version}` | | 46 | `convert_webcad` / `convertWebcad` | `POST /api/v1/map/cmd/convertWebCAD/{mapid}/{version}` | | 47 | `export_webcad` / `exportWebcad` | `POST /api/v1/map/cmd/exportWebCAD/_null/v1` | --- ## 42 · 列出 WebCAD 图 `listWebcadDraws` **URL**:`GET /api/v1/map/cmd/listWebcadDraws/_/v1` **响应**(启用时): ```json { "draws": [ { "mapid": "proj_wc", "version": "v1", "updatedAt": "2024-..." }, ... ] } ``` ### Python / Node ```python svc.list_webcad_draws() ``` ```javascript await svc.listWebcadDraws(); ``` --- ## 43 · 读取 WebCAD 数据 `getWebcadData` **URL**:`POST /api/v1/map/cmd/getWebcadData/{mapid}/{version}` ### 请求参数 | 字段 | 说明 | |------|------| | `branch` | 分支名 | | `includePatches` | 是否把历次补丁一并返回 | ### 响应(启用时) ```json { "mapdata": "", "branch": "main", "headVersion": 12, "patches": [] } ``` ### Python ```python svc.get_webcad_data("proj_wc", "v1", branch="main") ``` --- ## 44 · 保存 WebCAD 补丁 `saveWebcadPatch` **URL**:`POST /api/v1/map/cmd/saveWebcadPatch/{mapid}/{version}` ### 请求参数 | 字段 | 说明 | |------|------| | `patch` | 一段 JSON/字符串形式的差异描述 | | `branch` | 分支名 | | `author` | 作者标识(可选) | | `comment` | 提交说明 | | `baseVersion` | 基于哪个补丁号提交,用于冲突检测 | ### 响应(启用时) ```json { "status": true, "version": 13 } ``` ### Python ```python svc.save_webcad_patch( "proj_wc", "v1", patch='[{"op":"add","entity":{...}}]', branch="main", author="alice", comment="add hatch", baseVersion=12, ) ``` --- ## 45 · 删除 WebCAD 图 `deleteWebcadDraw` **URL**:`POST /api/v1/map/cmd/deleteWebcadDraw/{mapid}/{version}` ### 请求参数 | 字段 | 说明 | |------|------| | `branch` | 只删某个分支;为空则删整图 | ### 响应(启用时) ```json { "status": true } ``` --- ## 46 · 转为 WebCAD `convertWebCAD` 把一张已上传的 DWG 转成 WebCAD 可用的结构(初始化分支、预计算可编辑实体)。 **URL**:`POST /api/v1/map/cmd/convertWebCAD/{mapid}/{version}` ### 响应(启用时) ```json { "status": true, "branch": "main", "entityCount": 12345 } ``` --- ## 47 · 导出 WebCAD 为 DWG `exportWebCAD` **URL**:`POST /api/v1/map/cmd/exportWebCAD/_null/v1` ### 请求参数 | 字段 | 说明 | |------|------| | `mapid` / `version` | 要导出的 WebCAD 图 | | `branch` | 导出哪个分支(默认 main) | | `newMapid` / `newVersion` | 新 DWG 的 mapid / version | ### 响应(启用时) ```json { "status": true, "path": "download/xxx.dwg" } ``` --- ## api/07-workspace.md # 工作区 > 分类代号:C.7 本章共 **5 个接口** > 如果还没读过,建议先看 [入门 · 工作区](../04-workspace.md),了解 vjmap 的命名空间模型。 工作区用来把地图 / KV / 全文搜索按"项目"隔开。默认所有地图都在"匿名工作区";创建命名工作区后,URL 前缀由 `/api/v1/map/...` 变成 `/api/v1/workspace/{name}/...`。 --- ## 快速索引 | 序号 | SDK 方法 | URL | |----:|---------|-----| | 48 | `get_workspaces` / `getWorkspaces` | `GET /api/v1/service/getWorkspace` | | 49 | `workspace_create` / `workspaceCreate` | `POST /api/v1/service/workspaceCreate` | | 50 | `workspace_modify` / `workspaceModify` | `POST /api/v1/service/workspaceModify` | | 51 | `workspace_delete` / `workspaceDelete` | `DELETE /api/v1/service/workspaceDelete/{name}` | | 52 | `workspace_status` / `workspaceStatus` | `GET /api/v1/service/workspaceStatus` | --- ## 48 · 列出工作区 `getWorkspace` **URL**:`GET /api/v1/service/getWorkspace` - 非 root 账户仅看到 **自己创建的** + **isPublic=true** 的工作区 - root(`AuthorityId:root`)账户看全部 ### 响应(实采:创建 1 个后) ```json [ { "name": "nb_ws_1776831982", "alias": "nb 临时测试", "isPublic": false, "workDir": "nb_ws_1776831982", "idleCloseTime": 0, "disableFullSearch": false, "createTime": "2026-04-22 12:26:22" } ] ``` 一个工作区都没有时返回 `null`(而不是空数组 `[]`)。 ### Python ```python ws_list = svc.get_workspaces() or [] for w in ws_list: print(w["name"], w["alias"]) ``` ### Node.js ```javascript const ws = (await svc.getWorkspaces()) || []; for (const w of ws) console.log(w.name, w.alias); ``` --- ## 49 · 创建工作区 `workspaceCreate` **URL**:`POST /api/v1/service/workspaceCreate` ### 请求 body | 字段 | 类型 | 说明 | |------|------|------| | `name` | string | 唯一工作区名(**会出现在 URL 路径**里,注意字符集) | | `alias` | string | 显示名,任意字符 | | `workDir` | string | 工作区数据目录名;空串表示与 `name` 一致 | | `isPublic` | bool | 是否对所有登录用户可见 | | `disableFullSearch` | bool | 是否禁用该工作区的全文搜索索引 | ### 响应(实采) ```json { "data": "nb_ws_1776831982", "status": "ok" } ``` ### Python ```python svc.workspace_create( name="myproj", alias="我的项目", is_public=False, ) ``` ### Node.js ```javascript await svc.workspaceCreate({ name: 'myproj', alias: '我的项目' }); ``` --- ## 50 · 修改工作区 `workspaceModify` **URL**:`POST /api/v1/service/workspaceModify` body 必须是 `getWorkspaces()` 返回的**完整 obj**(包含 `createTime` 在内所有字段)。常见用法:先拿再改再 POST。 ### 响应(实采) ```json { "name": "nb_ws_1776831982", "status": "success" } ``` ### Python ```python ws_list = svc.get_workspaces() or [] me = next(w for w in ws_list if w["name"] == "myproj") me["alias"] = "新别名" me["disableFullSearch"] = True svc.workspace_modify(me) ``` ### Node.js ```javascript const ws = (await svc.getWorkspaces()) || []; const me = ws.find(w => w.name === 'myproj'); me.alias = '新别名'; await svc.workspaceModify(me); ``` --- ## 51 · 删除工作区 `workspaceDelete` **URL**:`DELETE /api/v1/service/workspaceDelete/{name}` ### 响应(实采) ```json { "name": "nb_ws_1776831982", "status": "success" } ``` > ⚠️ 删除**不会**自动清理工作区内的地图数据;如需彻底清除,先在该工作区内删完地图再删工作区。SDK 切换到目标工作区调 `delete_map / delete_map_file` 即可(见 [01-map-management](./01-map-management.md))。 ### Python ```python svc.workspace_delete("myproj") ``` ### Node.js ```javascript await svc.workspaceDelete('myproj'); ``` --- ## 52 · 工作区状态 `workspaceStatus` **URL**:`GET /api/v1/service/workspaceStatus` 查询当前**正在服务中的工作区**(已启用 fork 进程 / 加载了地图)。若无工作区处于活跃状态,返回 `null`。 ### 响应(实采:无活跃工作区) ```json null ``` 活跃时形如: ```json [ { "name": "myproj", "status": "running", "pid": 12345, "memory": 128_000_000 } ] ``` --- ## 在 SDK 中切换工作区 SDK 构造后,可以用 `svc.use_workspace("myproj")` 把后续所有 `map/...` / `cmd/...` 调用都前缀到 `/workspace/myproj/`: ```python svc = vjmap.Vjmap(API, TOKEN) svc.use_workspace("myproj") svc.list_maps() # -> /api/v1/workspace/myproj/cmd/listmaps/... svc.use_workspace(None) # 切回匿名 ``` Node.js 同名方法 `svc.useWorkspace("myproj")`。 --- --- ## api/08-data-storage.md # 键值数据存储 (KV) > 分类代号:C.8 本章共 **5 个接口** vjmap 内置了一个按工作区隔离的 KV 数据表(SQLite),用来存用户配置、缩略图、缓存数据等零碎东西。"零碎"并不代表"不好用"——二开里非常常用,比如存每个用户的图层可见性、自定义地图标注等。 **存储位置**:后端 `data/customData.db`。 **自动前缀**:所有用户可见 key 都加了隐形 `data_` 前缀,本 SDK 已屏蔽此细节。 **TTL**:可选过期时间(秒),到期自动清理。 **批量 key 分隔符**:路径里 `;;`(双分号)分隔多个 key。 --- ## 快速索引 | 序号 | SDK 方法 | URL | |----:|---------|-----| | 53 | `get_custom_data` / `getCustomData` | `GET /api/v1/service/data/{key}` | | 54 | `save_custom_data` / `saveCustomData` | `POST /api/v1/service/data/{key}` | | 55 | `delete_custom_data` / `deleteCustomData` | `DELETE /api/v1/service/data/{key}` | | 56 | `save_multiple_custom_data` / `saveMultipleCustomData` | `POST /api/v1/service/datas` | | 57 | `get_custom_data_keys` / `getCustomDataKeys` | `GET /api/v1/service/datakeys?prefix=` | --- ## 53 · 获取值 `data/{key}` GET ### 查询参数 | 字段 | 取值 | 说明 | |------|------|------| | `retDataType` | `""` / `"value"` / `"prop"` | 默认返回 `{data, prop}`;`value` 只要 `data`;`prop` 只要 `prop` | | `contentType` | `""` / `"image"` | `image` 时后端把 base64 dataURL 解出,直接返回 PNG 二进制(不是 JSON)| 批量:`key` 路径段用 `;;` 拼接多 key,SDK 传字符串列表即可。 ### 响应 - 存在:`{ "status": "ok", "data": , "prop": }` - 不存在:`{ "status": "keyNoExist" }`(**不是 404**) - 批量:`{ "values": [...], "props": [...], "status": [...] }` ### 实采 ```json // get_custom_data.json { "data": "hello-vjmap", "prop": "text/plain", "status": "ok" } ``` ```json // get_missing.json —— 一定要判断 status { "status": "keyNoExist" } ``` ### Python ```python d = svc.get_custom_data("myproj/theme") if d["status"] == "keyNoExist": ... else: theme = d["data"] ``` ### Node.js ```javascript const d = await svc.getCustomData('myproj/theme'); if (d.status === 'keyNoExist') { /* ... */ } ``` ### 批量读 ```python svc.get_custom_data(["a", "b"]) # { "values": ["va","vb"], "props":["pa","pb"], "status":["ok","ok"] } ``` --- ## 54 · 保存值 `data/{key}` POST ### 查询参数 | 字段 | 说明 | |------|------| | `ttl` | 秒;到点清除。省略或 `0` = 长期 | ### Body 两种写法 1. **JSON**:`{"value": "...", "prop": "..."}`;`prop` 可存元数据(MIME、扩展字段) 2. **Raw**:直接把文件二进制 POST 上去,作为 value 字段存(SDK 里叫 `raw_body` / `rawBody`) ### 响应(实采) ```json { "data": "nb_kv_a_1776832185", "status": "ok" } ``` ### Python ```python svc.save_custom_data("myproj/theme", value='{"mode":"dark"}', prop="application/json") # 带 TTL svc.save_custom_data("tmp_token", value="eyJ...", ttl=3600) # Raw bytes svc.save_custom_data("user_avatar_42", raw_body=open("avatar.png","rb").read()) ``` ### Node.js ```javascript await svc.saveCustomData('myproj/theme', { value: JSON.stringify({ mode: 'dark' }), prop: 'application/json', }); await svc.saveCustomData('tmp_token', { value: tokenStr, ttl: 3600 }); ``` --- ## 55 · 删除值 `data/{key}` DELETE ### 查询参数 | 字段 | 说明 | |------|------| | `isPrefix` | `"true"` 时把 `key` 当前缀,批量清除;否则只删单条 | ### 响应 - 单条(实采):`{"data":"nb_kv_a_...","status":"ok"}` - 前缀(实采):`{"data":"nb_prefix_...","isPrefix":true,"status":"ok"}` ### Python ```python svc.delete_custom_data("myproj/theme") # 前缀批量 svc.delete_custom_data("myproj/user42/", is_prefix=True) # 列表批量(走 ;; 分隔) svc.delete_custom_data(["a", "b", "c"]) ``` --- ## 56 · 批量存值 `datas` POST **URL**:`POST /api/v1/service/datas` ### Body `[{"key":..,"value":..,"prop":..,"ttl":"60"}]`,注意 `ttl` 是**字符串**秒数。 ### 响应(实采) ```json { "status": ["ok"] } ``` ### Python ```python svc.save_multiple_custom_data([ {"key": "cfg/a", "value": "1"}, {"key": "cfg/b", "value": "2", "ttl": "3600"}, ]) ``` ### Node.js ```javascript await svc.saveMultipleCustomData([ { key: 'cfg/a', value: '1' }, { key: 'cfg/b', value: '2', ttl: '3600' }, ]); ``` --- ## 57 · 按前缀列键 `datakeys` GET ### 查询参数 | 字段 | 说明 | |------|------| | `prefix` | 前缀(自动加 `data_`) | | `retDataType` | `value` / `prop` / `valueprop` —— 同时取出值,省一次往返 | | `isSys` | `true` 时返回带 `data_` 内部前缀的系统键(仅 root 场景) | ### 响应 - 仅 keys(实采): ```json { "keys": ["nb_kv_a_1776832185", "nb_kv_b_1776832185"] } ``` - 带值(`retDataType=valueprop`,实采): ```json { "keys": ["nb_kv_a_1776832185", "nb_kv_b_1776832185"], "props": ["text/plain", "bp-1"], "values": ["hello-vjmap", "batch-1"], "status": ["ok", "ok"] } ``` ### Python ```python keys = svc.get_custom_data_keys(prefix="myproj/")["keys"] # 同时取值 bundle = svc.get_custom_data_keys(prefix="myproj/", ret_data_type="valueprop") for k, v, p in zip(bundle["keys"], bundle["values"], bundle["props"]): print(k, v, p) ``` --- ## 最佳实践 1. **前缀分层**:用 `/`/`:` 建立命名空间,如 `myproj/user42/tool_state`。这样前缀批量删除、列出都方便。 2. **大文件不要存 KV**:value 单条一般 < 1 MB;大图/DWG 用 `/map/uploadmap` 或独立对象存储。 3. **TTL 清理**:用 `ttl` 让后端帮忙过期;别自己写 crontab。 4. **`keyNoExist` ≠ 错误**:200 正常响应里 `status == "keyNoExist"` 表示没值,而不是失败。 --- --- ## api/09-fullsearch.md # 全文搜索 (Full-text Search) > 分类代号:C.9 本章共 **3 个接口** > **可关闭特性**:`config.yaml` 里 `fullsearch.disable: true` 会让这组路由完全不注册;当 `full_search` 返回 `VjmapError(404)` 时即判定为"关闭"。 vjmap 内置 [bleve](https://github.com/blevesearch/bleve) 作为 CAD 图纸全文索引,主要用于搜"图上某段文字在哪儿"。典型使用场景: - 用户在前端搜索框里输入"消防栓",后端返回所有匹配到的文字 + bbox,前端画框高亮 - 按图元类型(单行/多行/属性注记/图层名/块名…)过滤 - 按工作区 / map_ver 隔离 --- ## 快速索引 | 序号 | SDK 方法 | URL | |----:|---------|-----| | 58 | `full_search` / `fullSearch` | `POST /api/v1/service/fullsearch/search`(也支持 GET) | | 59 | `full_search_add` / `fullSearchAdd` | `POST /api/v1/service/fullsearch/add` | | 60 | `full_search_delete` / `fullSearchDelete` | `DELETE /api/v1/service/fullsearch/delete` | --- ## Document 数据结构 ```jsonc { "id": "unique-id", // 必填,全库唯一 "map_ver": "sys_zp_v1", // 建议填,便于按图过滤 "content": "消防栓", // 被索引的文本 "type": 1, // 见下方 type 表 "min_x": 0.0, // bbox "min_y": 0.0, "max_x": 1.0, "max_y": 1.0, "props": {...}, // 任意附加属性 "workspace":"default", // 为空 = 匿名工作区 "data": "..." // 原始业务载荷(由你自己定义) } ``` ### `type` 常用值 | type | 说明 | |-----:|------| | 1 | 单行文字 | | 2 | 多行文本 MText | | 3 | 属性注记 Attribute | | 4 | 块属性注记 | | 5 | 图层名 | | 6 | 块名 | | 7 | 线型名 | | 8 | 填充符号名 | | 9 | 用户自定义 | --- ## 58 · 搜索 `fullsearch/search` 两种调用方式: - **GET**:所有参数走 query,**适合简单关键词**。 - **POST**:所有参数走 JSON body,**支持复杂结构**(尤其 `bounding_box`)。 ### 请求字段 | 字段 | 类型 | 说明 | |------|------|------| | `query` | string | 搜索关键词 | | `map_ver` | string | 多个用 `,` 分隔;纯 mapid 自动取最新版本 | | `type` | string | 类型过滤:`"1"` / `"1,2,5"` / `"1-3"` / `"1-3,6"` | | `workspace` | string | 工作区,多个逗号分隔 | | `limit` | int | 每页条数,默认 10 | | `offset` | int | 分页偏移 | | `fields` | string | 返回字段过滤,逗号分隔 | | `facet` | bool | 是否按 `map_ver` 聚合 facet | | `bounding_box` | string | `"minx,miny,maxx,maxy"` 或 `"[..]"` | | `time_range` | string | `"2023-01-01~2023-12-31"` 或 `"7"`(最近 N 天) | ### 响应(实采) ```json { "total": 0, "hits": [], "took": 2 } ``` `total` 是命中数量,`hits` 是文档数组(含 `id` / `content` / `score` / `fragments` 等 bleve 字段),`took` 是耗时毫秒。 ### Python ```python res = svc.full_search( query="消防栓", map_ver="proj_v1", type_="1,2", bounding_box="[0,0,1000,1000]", limit=20, ) for hit in res.get("hits", []): print(hit["id"], hit.get("content")) ``` 用 GET 走查询串: ```python svc.full_search(query="消防栓", limit=5, use_get=True) ``` ### Node.js ```javascript const res = await svc.fullSearch('消防栓', { mapVer: 'proj_v1', type: '1,2', boundingBox: '[0,0,1000,1000]', limit: 20, }); ``` --- ## 59 · 新增文档 `fullsearch/add` ### Body ```json { "document": { "id":"...","map_ver":"...","content":"..." } } ``` ### 响应(实采) ```json { "id": "nb_fs_1776832372", "message": "Document added successfully" } ``` ### Python ```python svc.full_search_add({ "id": "firehydrant_01", "map_ver": "proj_v1", "content": "消防栓", "type": 1, "min_x": 100, "min_y": 200, "max_x": 110, "max_y": 210, }) ``` > **批量导入**:本 SDK 只封装了单条 `full_search_add`。接口 `/fullsearch/batchAdd` 用同样的 POST JSON(`{"documents":[...]}`);本次不做默认封装,可按需自行 `svc._request("POST", svc.service_url("fullsearch/batchAdd"), json_body={"documents":[...]})`。 --- ## 60 · 删除文档 `fullsearch/delete` 注意是 **DELETE 带 JSON body**,而不是 `?id=`(gin 用 `ShouldBindJSON`)。 ### Body ```json { "id": "firehydrant_01" } ``` ### 响应(实采) ```json { "id": "nb_fs_1776832372", "message": "Document deleted successfully" } ``` ### Python ```python svc.full_search_delete("firehydrant_01") ``` --- ## 常见错误 | HTTP 状态 | body 片段 | 含义 | |-----------|-----------|------| | 400 | `"error":"Invalid request parameters"` | JSON 反序列化失败 | | 400 | `"error":"Document ID cannot be empty"` | 未填 `id` | | 400 | `"error":"Invalid bounding box format"` | `bounding_box` 解析失败 | | 404 | 整条路由不存在 | `fullsearch.disable:true`,功能关闭 | | 500 | `"error":"Failed to initialize service"` | 后端索引损坏,看服务器日志 | --- ## 使用建议 1. **用 `map_ver` 隔离**:别把所有图纸的文字都塞一个索引里查全局,会拖慢。 2. **先 bbox 后全文**:多数场景只需搜当前视口,给 `bounding_box` 可大幅缩小候选集。 3. **批量构建走后台 job**:把"从 DWG 抽文字 → 批量 add"放到离线任务里;前端只做搜索。 4. **关闭索引也没关系**:SDK 的 `full_search` 会在 404 时抛 `VjmapError(404)`,应用层 catch 一下退化到普通查询即可。 --- --- ## api/10-symbols.md # 符号库 > 分类代号:C.10 本章共 **3 个接口(只读)** vjmap 自带一个"常用图块/图符"库,按分类组织。二开场景里最常见的用法是: - 前端工具条展示"符号面板",用户选一个图符,拖到图上 - 拖放后前端本地生成图元并调后端 `updatemap` 持久化 - 导出 DWG 时把符号实例替换为 DXF BlockRef > 本章只覆盖**只读**三接口(分类列表 / 符号列表 / 符号详情)。写操作(增 / 改 / 删分类与符号)SDK 暂不封装;如果你用 root token,可按 `POST /symbol/category`、`PUT /symbol/:id` 等直接 `svc._request(...)` 调。 --- ## 快速索引 | 序号 | SDK 方法 | URL | |----:|---------|-----| | 61 | `symbol_categories` / `symbolCategories` | `GET /api/v1/service/symbol/categories` | | 62 | `symbol_list` / `symbolList` | `GET /api/v1/service/symbol/list/{categoryId}` | | 63 | `symbol_get` / `symbolGet` | `GET /api/v1/service/symbol/{id}` | --- ## 61 · 分类列表 `symbol/categories` ### 响应(实采) ```json { "data": [ { "id": "default", "name": "默认分类", "createTime": "2026-04-10 11:29:15" }, { "id": "user_custom", "name": "用户自定义", "createTime": "2026-04-10 11:29:15" } ], "status": "ok" } ``` ### Python ```python cats = svc.symbol_categories()["data"] for c in cats: print(c["id"], c["name"]) ``` ### Node.js ```javascript const cats = (await svc.symbolCategories()).data; ``` --- ## 62 · 符号列表 `symbol/list/{categoryId}` 按分类分页列出符号。 ### 查询参数 | 字段 | 默认 | 说明 | |------|-----:|------| | `page` | 1 | 分页起始(1-based) | | `pageSize` | 20 | 每页 | | `keyword` | - | 名称模糊匹配 | ### 响应(实采:分类为空) ```json { "data": { "list": [], "total": 0, "page": 1, "pageSize": 3 }, "status": "ok" } ``` 有数据时 `list` 元素形如: ```jsonc { "id": "symbol_abc", "categoryId": "default", "name": "消防栓", "basePoint": [0, 0], "thumbnail": "data:image/png;base64,...", "data": "...DWG/SVG 原始数据..." } ``` ### Python ```python page = svc.symbol_list("default", page=1, page_size=20, keyword="栓") for it in page["data"]["list"]: print(it["id"], it["name"]) ``` ### Node.js ```javascript const page = await svc.symbolList('default', { page: 1, pageSize: 20 }); ``` --- ## 63 · 符号详情 `symbol/{id}` ### 响应 - 成功:`{ "status": "ok", "data": { ...同上"有数据时"结构... } }` - 未找到:HTTP **404** + `{ "status": "error", "message": "not found" }` ### Python ```python try: sym = svc.symbol_get("symbol_abc")["data"] except vjmap.VjmapError as e: if e.status == 404: ... # 符号不存在 else: raise ``` --- ## 使用范式 下面是一个"前端面板"级别的整合: ```python def list_all_symbols(): for cat in svc.symbol_categories()["data"]: page = 1 while True: bundle = svc.symbol_list(cat["id"], page=page, page_size=50)["data"] for sym in bundle.get("list", []): yield cat, sym if page * bundle["pageSize"] >= bundle["total"]: break page += 1 ``` --- --- ## api/11-system.md # 系统 > 分类代号:C.11 本章共 **4 个接口** 这几个接口是运维和监控最常看的: - **heart**:心跳 - **version**:版本与构建信息 - **runstatus**:当前进程 / 连接 / 切片状态 - **viewlogs**:读最近 N 条日志 生产环境完全可以周期性采集 `runstatus` 做监控告警,**不需要登进后端机器**。 --- ## 快速索引 | 序号 | SDK 方法 | URL | |----:|---------|-----| | 64 | `heart` / `heart` | `GET /heart` | | 65 | `version` / `version` | `GET /version` | | 66 | `run_status` / `runStatus` | `GET /api/v1/map/cmd/runstatus/_/v1?detail=true` | | 67 | `view_logs` / `viewLogs` | `GET /api/v1/map/cmd/viewlogs/_/v1?record=N` | --- ## 64 · 心跳 `/heart` 直接挂 Gateway 上(**不走 `/api/v1/...` 前缀**),用来判断进程是否活着。 ### 响应(实采) ```json { "status": "ok", "timestamp": "2026-04-22T12:37:54.501381+08:00" } ``` ### Python / Node.js ```python svc.heart() ``` ```javascript await svc.heart(); ``` ### 用作健康检查 ```yaml # docker-compose healthcheck 片段 healthcheck: test: ["CMD", "curl", "-f", "http://127.0.0.1:27660/heart"] interval: 30s timeout: 3s retries: 3 ``` --- ## 65 · 版本 `/version` ### 响应(实采) ```json { "arch": "amd64", "os": "windows", "status": "ok", "time": "2026-04-22 12:37:54", "version": "2026041001" } ``` - `version` 是 service 的 build id(yyyyMMDD + 计数) - `time` 是服务器当前时间,可用来校时 - 图形服务的版本在 `runstatus → application.version` 里(本例是 `"20260305"`) --- ## 66 · 运行状态 `runstatus` ### 查询参数 | 字段 | 默认 | 说明 | |------|-----:|------| | `detail` | `false` | `true` 时额外返回 `activeRequests` 列表 | ### 响应(实采 `detail=true`) ```json { "application": { "pid": "11996", "runtime": 24063, "taskActiveThreadCount": 0, "threadId": "12000", "version": "20260305" }, "process": { "activeProcesss": {}, "activeRequestCount": 0, "activeRequests": [], "curParallelProcessGeomNum": 0, "maxParallelProcessGeomNum": 2, "recycleProcesss": [], "reqTimeout": 600000, "waitWorkMapIds": [] }, "slice": {}, "store": { "connectionNames": "_cache_map", "storeLastAccessTime": [], "storeNoActiveCloseTime": 60, "storeStatus": [] } } ``` ### 字段速查 | 字段 | 含义 | |------|------| | `application.runtime` | 进程启动毫秒数 | | `process.activeRequestCount` | 当前在处理的请求数 | | `process.activeRequests` | `detail=true` 时的 URL 列表,排查"卡在什么请求上" | | `process.curParallelProcessGeomNum` | 当前并行几何任务 | | `process.maxParallelProcessGeomNum` | 并发上限 | | `process.recycleProcesss` | 被回收的子进程 | | `slice` | 切片缓存状态(按 layer) | | `store` | KV 存储连接 | ### Python ```python status = svc.run_status(detail=True) assert status["application"]["pid"] print(status["process"]["activeRequestCount"], "个并发请求") ``` ### Node.js ```javascript const st = await svc.runStatus(true); ``` --- ## 67 · 查看日志 `viewlogs` ### 查询参数 | 字段 | 默认 | 说明 | |------|-----:|------| | `record` | 100 | 读取最近多少条记录 | ### 响应(实采,部分片段) ```json { "logs": [ " *** log260422_0x115f4_2026-04-22.log *** ", "[2026-04-22 11:58:01.625] [daily_logger] [info] File:() Line:(0) Msg: (\"process app started..._null_@>v1_1571__||__11996__||__28011__||__D:/Program Files/vjmapserver/data\")\n", "[2026-04-22 11:58:01.697] [daily_logger] [info] File:() Line:(0) Msg: (openMap begin... \"_null_@>v1\")\n", ... ] } ``` 每一条都是一行日志(带行尾 `\n`);多子进程时会用 `*** log....log ***` 分隔不同文件。 ### Python ```python lines = svc.view_logs(record=200)["logs"] for line in lines[-20:]: print(line.rstrip()) ``` ### Node.js ```javascript const { logs } = await svc.viewLogs(200); ``` > ⚠️ 这是**最近 N 条**,不是整个日志文件;想做审计请定时拉并本地存档。 --- ## 监控样例:Python 拉取 → Prometheus ```python from time import sleep def poll(): while True: try: s = svc.run_status(detail=False) metrics = { "vjmap_up": 1, "vjmap_active_reqs": s["process"]["activeRequestCount"], "vjmap_runtime_ms": s["application"]["runtime"], "vjmap_store_count": len(s["store"].get("storeStatus") or []), } except Exception: metrics = {"vjmap_up": 0} push_to_prometheus(metrics) sleep(15) ``` --- --- ## api/12-service-misc.md # /service 杂项 > 分类代号:C.12 本章共 **3 个接口** 这里汇集了"不方便归到其它分类、但二开很常用"的三个能力: - **QR Code 生成**:把字符串直接渲染成 PNG 二维码 - **字体字形 (PBF)**:vjmap地图渲染依赖的字体文件 - **图片上传**:把任意图片文件塞到服务器指定路径(配合前端水印/图床) --- ## 快速索引 | 序号 | SDK 方法 | URL | |----:|---------|-----| | 68 | `qrcode_url` / `qrcode_bytes` / `qrcodeUrl` / `qrcodeBytes` | `GET /api/v1/service/qrcode?content=...&size=` | | 69 | `glyphs_url` / `glyphs_bytes` / `glyphsUrl` / `glyphsBytes` | `GET /api/v1/service/fonts/{fontstack}/{range}.pbf` | | 70 | `upload_img` / `uploadImg` | `POST /api/v1/service/uploadImg` (multipart) | --- ## 68 · 二维码 `qrcode` ### 查询参数 | 字段 | 默认 | 说明 | |------|-----:|------| | `content` | -(必填) | 要编码的字符串;后端会 `decodeURIComponent`,所以**不用双重编码** | | `size` | 256 | 图片边长(像素) | ### 响应 直接返回 `image/png` 字节,**不是 JSON**。 ### SDK 两种用法 1. **URL 构造**(适合前端直接 ``): ```python url = svc.qrcode_url("https://example.com/project/42", size=512) # http://127.0.0.1:27660/api/v1/service/qrcode?content=https%3A%2F%2Fexample.com%2Fproject%2F42&size=512&token= ``` 2. **下载字节**(适合服务端生成后转发/保存): ```python png = svc.qrcode_bytes("VJMAP", size=128) open("qr.png", "wb").write(png) ``` Node.js: ```javascript const url = svc.qrcodeUrl('VJMAP', 512); const buf = await svc.qrcodeBytes('VJMAP', 128); ``` ### 实采信息 ```json // qrcode_bytes_info.json { "len": 284, "md5": "2577b1157cf93feefce8c6b180a0ff42", "magic_ok": true } ``` --- ## 69 · 字体 `fonts/{fontstack}/{range}.pbf` vjmap 矢量渲染在画文字时,需要按需下载对应字体、对应 Unicode 区间的 PBF 字形文件。这个接口就是字形的分发端。 ### 路径参数 | 字段 | 说明 | |------|------| | `fontstack` | 字体名,可用 `,` 分隔多个**回退栈**。后端会依次尝试,并**自动在末尾追加 `Arial Unicode MS Regular`** 作为兜底 | | `range` | Unicode 字形区间,形如 `0-255`、`256-511`。通常是 256 个字形一包 | ### 响应 `application/x-protobuf` 字节(PBF)。 ### SDK ```python url = svc.glyphs_url("Arial Unicode MS Regular", "0-255") pbf = svc.glyphs_bytes("Arial Unicode MS Regular", "0-255") ``` ```javascript const url = svc.glyphsUrl('Arial Unicode MS Regular', '0-255'); ``` ### 实采 ```json // glyphs_bytes_info.json { "len": 75282, "md5": "638faf3bf9a90424d23513cfab7e9ce1" } ``` ### 用在 vjmap 里 ```js map.setStyle({ glyphs: 'http://127.0.0.1:27660/api/v1/service/fonts/{fontstack}/{range}.pbf?token=' + TOKEN, // ...其它 }); ``` 注意 `{fontstack}`/`{range}` 是 **vjmap 自己替换**的占位符,别让 URL 编码器把 `{` / `}` percent-encode 掉。 --- ## 70 · 上传图片 `uploadImg` ### 特殊鉴权 这个接口**不走**普通 token,而是检查 HTTP header: ``` Authorization: Basic X3ZqbWFwQF8= ``` 即 Basic base64("`_vjmap@_`")。后端硬编码。SDK 默认已经带上;你也能传自定义 `authorization` 参数覆盖。 ### Headers | Header | 说明 | |--------|------| | `Authorization` | 上述固定值(SDK 默认) | | `Uri` | **必填**:服务器端保存目录(以 `/` 结尾)。SDK 参数名 `uri` | | `watermark` | `"1"` 时后端自动叠加水印(`utils.NewWatermarkBase64` 实现) | ### multipart 字段 | 字段 | 说明 | |------|------| | `upload` | 文件内容。**注意 field name 是 `upload` 不是 `file`** | ### 响应 成功(实采): ```json { "code": 0, "path": "./data/test_upload/nb_upload_smoke.png", "url": "nb_upload_smoke.png" } ``` 失败(实采:错误 Authorization): ```json { "code": 7, "data": {}, "msg": "no Authorization" } ``` ### Python ```python # 从本地文件上传 svc.upload_img( "./avatar.png", uri="./data/upload/avatars/", filename="user_42.png", watermark=True, ) # 从字节上传 svc.upload_img( ("user_42.png", png_bytes), uri="./data/upload/avatars/", ) ``` ### Node.js ```javascript // Node 18+ await svc.uploadImg('./avatar.png', { uri: './data/upload/avatars/', filename: 'user_42.png', watermark: true, }); // Buffer await svc.uploadImg(buf, { uri: './data/upload/avatars/', filename: 'user_42.png', }); ``` --- ## api/13-ai.md # 13. AI(向量检索 / FAQ / 文档) > **条件启用**:这一组接口依赖 `vjmap` 服务端的嵌入(embedding)模型与向量库。 > 若未正确配置,调用会返回 `500`(例如缺少 `bge-large-zh-v1.5-f16.gguf` 模型文件)。 > 在你的环境若未开启 AI 子服务,直接跳过本章。 本章覆盖 3 个接口: | No. | 接口 | URL | Method | 说明 | |-----|------|-----|--------|------| | 13.1 | vectorSearch | `/api/v1/service/vectorSearch` | GET / POST | 相似性检索 | | 13.2 | chatFaq | `/api/v1/service/chatFaq` | GET | 获取 FAQ 知识库 | | 13.3 | vectorDocs | `/api/v1/service/vectorDocs` | GET | 获取向量库已有文档元数据 | --- ## 13.0 启用探针 SDK 提供 `aiEnabled()` / `ai_enabled()`(内部调用 `chatFaq`)判断 AI 子服务是否可用; 失败(抛 `VjmapError`)则视为未启用,跳过后续调用即可。 ```python if not svc.ai_enabled(): raise SystemExit("AI 未启用,略") ``` --- ## 13.1 vectorSearch — 相似性检索 按给定自然语言 `query` 在向量库中检索最相似的若干条文档。 ### 请求 两种请求方式: | 方式 | URL | Body | 备注 | |------|-----|------|------| | GET | `GET /api/v1/service/vectorSearch?query=...&numDocuments=...` | 无 | 简单场景用 | | POST(推荐) | `POST /api/v1/service/vectorSearch` | JSON | 支持 `documents` 等复杂字段 | ### 主要参数 | 字段 | 类型 | 默认 | 说明 | |------|------|------|------| | `query` | string | — | **必填** 查询文本 | | `numDocuments` | int | 20 | 返回条数 | | `collection` | string | `"vj"` | 向量库集合名 | | `where` | string | — | metadata 过滤表达式([chromem-go](https://github.com/philippgille/chromem-go) 语法) | | `whereDocument` | string | — | document 内容过滤表达式 | | `searchDescription` | string | — | 附加搜索上下文,帮助模型更精准 | | `disableSplitSentence` | bool | false | 关闭"先分句再分别 embed" | | `disableSearchKnowledge` | bool | false | 仅搜 `documents`,不搜知识库 | | `documents` | array | — | 临时附带的用户文档(不会持久化)。每项:`{content:"...", metadata:{...}}` | ### 响应 ```json { "status": "ok", "data": [ { "id": "__createPolygon", "content": "根据给定的点坐标数组直接绘制一个多边形", "metadata": { "...": "..." }, "similarity_score": 0.8273 }, { "...": "..." } ] } ``` ### SDK 示例 ```python res = svc.vector_search("如何画多边形", num_documents=5) for doc in res.get("data", []): print(doc["id"], doc.get("similarity_score")) ``` ```js const res = await svc.vectorSearch('如何画多边形', { numDocuments: 5 }); res.data.forEach(d => console.log(d.id, d.similarity_score)); ``` ### 本地实测(失败样例) 当前测试环境缺少 embedding 模型文件,真实请求返回: ```json { "status": 500, "body": { "code": 8, "msg": "D:/Program Files/vjmapserver/data/ai/models/bge-large-zh-v1.5-f16.gguf is not exist" } } ``` > **部署提示**:部署方需根据官方文档下载 embedding 模型文件,放置于 > 服务端的 `data/ai/models/` 目录下,并在配置中指定模型名。未配置时, > `vectorSearch` 会失败但不影响其他子系统运行。 --- ## 13.2 chatFaq — FAQ 知识库 ### 请求 ``` GET /api/v1/service/chatFaq ``` ### 响应 返回一个按"主题"分组的字符串数组字典。 ```json { "status": "ok", "data": { "UI交互": [ "弹出学生表单输入对话框,包括姓名、年龄(默认18)、性别", "获取地图的当前中心点坐标X和Y、地图级别,生成json对象,根据这数据弹出数据信息框..." ], "信息窗口": [ "在坐标[30,50]处添加一个信息窗口,内容为\"Hello,World\"" ], "几何算法": [ "..." ] } } ``` 数据源来自服务端 `./data/ai/faq/*.md`,并开启 5 分钟 HTTP 缓存。 ### SDK 示例 ```python faq = svc.chat_faq() for topic, items in faq["data"].items(): print(f"- {topic}: {len(items)} 项") ``` ```js const { data } = await svc.chatFaq(); for (const [topic, items] of Object.entries(data)) { console.log(`- ${topic}: ${items.length} 项`); } ``` --- ## 13.3 vectorDocs — 向量库文档 ### 请求 ``` GET /api/v1/service/vectorDocs ``` ### 响应 返回一个数组,每项是知识库里的一条文档元数据: ```json { "status": "ok", "data": [ { "id": "__createPolygon", "content": "根据给定的点坐标数组直接绘制一个多边形", "metadata": { "category": "draw", "code": "...", "comments": "...", "function": "__createPolygon", "param": "..." }, "similarity_score": 0 } ] } ``` `similarity_score` 恒为 0(此接口不做相似度计算,仅导出文档)。 ### SDK 示例 ```python docs = svc.vector_docs()["data"] print("知识库条目数:", len(docs)) print("分类统计:", { c: sum(1 for d in docs if d["metadata"].get("category") == c) for c in sorted({d["metadata"].get("category", "") for d in docs}) }) ``` ```js const { data: docs } = await svc.vectorDocs(); console.log('条目数:', docs.length); ``` --- ## 使用建议 1. **部署前先跑探针**:初始化客户端后先调 `chat_faq()`,失败则关闭 AI UI 入口。 2. **前端搜索**:优先用 POST `vectorSearch`,把 `searchDescription` 带上业务上下文能显著提升命中率。 3. **冷启动**:第一次调用 `vectorSearch` 会加载 embedding 模型,响应可能秒级; 第二次起基本在毫秒级。 4. **安全**:`vectorDocs` 直接返回所有文档内容(含 `code` 字段),不要把它暴露给未鉴权用户。 --- --- # 全文搜索 在制造业、建筑设计和工程管理等相关领域,`CAD图纸`是核心资产,但海量图纸的管理与检索长期存在三大痛点: - 全文检索难:传统方式需逐张打开图纸人工查找,耗时耗力;- - 相似图纸匹配低效:设计师常需复用历史图纸,但人工比对相似度误差率高; - 精准搜索依赖经验:非专业人员难以通过模糊描述快速定位图纸内容。 为此,[唯杰地图](https://vjmap.com/app/cloud) https://vjmap.com 推出三项智能搜索方案: - `CAD图纸秒搜`:基于现代化全文搜索和索引引擎,秒级检索图纸内所有文字、图层、块名称、线型名称等,支持多条件组合查询; - `相似度搜索`:采用SIFT算法和深度学习模型,提取几何特征与拓扑结构,精准匹配相似图纸; - `AI语义搜索`:通过大模型解析自然语言指令(如“找查找半径为10的红色的圆”),直接查出具体图中符合条件的圆。 通过这三项技术,让图纸从“沉睡的档案”变为“可对话的数据资产”。 ## 一、CAD图纸秒搜 进入[唯杰地图云端管理平台](https://vjmap.com/app/cloud/#/) 在搜索框输入要查询的内容,即可秒搜所有相关的图纸,实现`图文并茂`的方式呈现。 ![](https://vjmap.com/blogimages/fullsearch.gif) ### 1.1 搜索入口 - 在顶部导航栏的搜索框中输入要搜索的内容 - 支持搜索`图中文字`、`图层名称`、`块名称`、`线型名称`、`填充符号名`等 ### 1.2 搜索建议 - 输入过程中会实时显示搜索建议 - 点击建议项可以快速填充搜索内容 ### 1.3 高级过滤 - 点击搜索框右侧的过滤图标 - 在下拉菜单中选择"高级过滤选项" ### 1.4 过滤条件说明 1. **地图版本** - 可以输入mapid或mapid_version - 多个版本用逗号分隔 - 点击"选择"按钮可以从列表中选择地图 2. **文档类型** - 支持多种类型的选择: - 单行文字 (1) - 多行文本 (2) - 属性注记 (3) - 块属性注记 (4) - 图层 (5) - 块名称 (6) - 线型名称 (7) - 填充符号名称 (8) - 用户自定义 (9) - 可以多选 3. **时间范围** - 支持两种格式: - 天数:如"7"表示最近7天 - 日期范围:如"2024-01-01~2024-01-31" 4. **边界框** - 格式:minX,minY,maxX,maxY - 点击"选择"按钮可以从地图上选择范围, 通过范围可以查询所有图纸中相同范围内的文字 5. **结果限制** - 可以设置返回结果的最大数量 - 范围:1-10000 - 默认值:1000 ![](https://vjmap.com/blogimages/202505061925112.png) ### 1.5 刷新索引 - 在过滤下拉菜单中选择"刷新全文索引数据" - 用于更新搜索索引,确保搜索结果最新 ### 1.6 清除过滤 - 在过滤下拉菜单中选择"清除所有过滤" - 快速清除所有已设置的过滤条件 ### 1.7 使用技巧 - 多个关键词用空格分隔可以进行组合搜索 - 点击某个结果项的"排序"按钮,可以将其设为参照点, 其他结果项会显示到该点的距离,并可以按距离排序 ### 1.8 后台配置 图纸全文搜索配置 可通过搜索关键词对所有图纸进行全文搜索,在 `config.json` 中的默认配置如下: ```json "fullsearch": { "disable": false, "disable_index_hidden_maps": false, "disable_index_password_protected_maps": true, "disable_default_workspace": false, "batch_size": 1000, "sync_delay_minutes": 0, "cron_fullsearch_hour": 5, "linetype_max_num": 50, "pattern_max_num": 50 } ``` 每项说明: - disable 是否禁止全文搜索,默认false. (注:默认开启全文搜索) - disable_index_hidden_maps 是否禁止索引隐藏的图形,默认false - disable_index_password_protected_maps 是否禁止索引有密码保护的图形,默认true - disable_default_workspace 是否禁止对默认工作区的图纸进行索引,默认false。注:可在工作区管理里面对其工作区是否允许索引进行设置 - batch_size 批处理条数,默认1000 - sync_delay_minutes 当图有更新时,延迟几分钟后自动建立索引。如为0表示图有更新时不更新索引。如设置为3表示,当图更新时,2分钟后自动更新索引。默认为0 - cron_fullsearch_hour 定时同步更新索引的时间,默认为5,表示早上五点全部定时更新所有图纸索引。如果设置为-1表示不定时同步更新。 - linetype_max_num 线型符号大于多少时不建立索引,默认50 - pattern_max_num 填充符号大于多少时不建立索引,默认50 注:默认全文索引的策略为:允许全文搜索,并且每天早上五点自动同步更新索引。如果有实时更新索引需求请设置sync_delay_minutes为2(官网示例为2)。或当有图纸更新时,在云端管理平台全文搜索菜单中选择“刷新全文索引数据”来同步索引。 ## 二、CAD图相似度匹配 ![](https://vjmap.com/blogimages/mapSimilarity.gif) 图像相似度匹配通过向量化的方法,主要分为特征提取、向量表示和相似度计算三个步骤。 - 1、特征提取 将图像转换为高维特征向量是匹配的基础,常用方法包括: 传统算法:如SIFT(尺度不变特征变换)、SURF(加速稳健特征)、ORB(定向FAST)等,提取局部关键点及其描述子。 深度学习方法:使用CNN(卷积神经网络)提取全局或局部特征,生成更具语义信息的向量 - 2、 向量表示 向量化:将提取的特征整合为固定长度的向量 - 3、相似度计算 通过距离或相似度度量比较向量: 欧氏距离:计算向量间的直线距离,适用于空间分布相似性 余弦相似度:衡量向量方向一致性。 汉明距离:用于二进制哈希向量(如感知哈希),统计不同比特数 ![image-20250506194555461](https://vjmap.com/blogimages/202505061945560.png) 在[唯杰地图云端管理平台](https://vjmap.com/app/cloud/#/) 中,在要匹配的图的操作菜单下面,点击`地图相似度排序`, 返回的结果会按与此图的`相似度`进行排序。 ## 三、AI智能查询 对于具体的CAD图,可通过`AI语义的方式`对图中内容进行查询。 可在 [唯杰地图云端管理平台](https://vjmap.com/app/cloud/#/)中,打开地图。点击工具栏上面的`AI数据查询` 进入 `AI数据查询可视化查询工具` 示例地址: [https://vjmap.com/app/visual/#/query?mapid=sys_zp&version=v1&mapopenway=GeomRender&theme=dark](https://vjmap.com/app/visual/#/query?mapid=sys_zp&version=v1&mapopenway=GeomRender&theme=dark) ![image-20250303161206288](https://vjmap.com/blogimages/202503031612318.png) ![image-20250303161516621](https://vjmap.com/blogimages/202503031615674.png) 演示教程 ![](https://vjmap.com/blogimages/aiquery.gif) --- # 图像 ## 前言 前端加载超大图片时,一般可以采取以下措施实现加速: 1. 图片压缩:将图片进行压缩可以大幅减小图片的大小,从而缩短加载时间。压缩图片时需要注意保持图片质量,以免影响图片显示效果。 2. 图片分割:将超大图片分割成多个小图块进行加载,可以避免一次性加载整个图片,从而加快加载速度。这种方式需要在前端实现图片拼接,需要确保拼接后的图片无缝衔接。 3. CDN 加速:使用 CDN(内容分发网络)可以将图片缓存在离用户更近的节点上,从而加速图片加载速度。如果需要加载的图片是静态资源,可以将其存储在 CDN 上,以便快速访问。 4. 懒加载:懒加载是一种图片延迟加载的方式,即当用户浏览到需要加载的图片时才进行加载,可以有效避免一次性加载大量图片而导致页面加载速度缓慢。 5. WebP 格式:使用 WebP 格式可以将图片大小减小到 JPEG 和 PNG 的一半以下,从而加快图片加载速度。 6. HTTP/2:使用 HTTP/2 协议可以并行加载多个图片,从而加快页面加载速度。 7. 预加载:预加载是在页面加载完毕后,提前加载下一步所需要的资源。在图片加载方面,可以在页面加载完毕后提前加载下一个需要显示的图片,以便用户快速浏览。 而对于几百M或上G的大图而言,不管对图片进行怎么优化或加速处理,要实现秒开也是不太可能的事情。而上面介绍的第二条“图像分割切片”是最佳解决方案。 `唯杰地图`支持常用的图像格式如`tif;png;jpeg;tiff;hdr;avif;webp;jpe;jpc;jpg;heif;bmp;dib;heic;jfif;gif;zip;hif;pdf;`等格式,通过上传图像,在后台进行图像处理后切片完成后,在前端实现秒开展示。 ## 图像切片原理介绍 图像切片是指将一张大图分割成若干个小图的过程,以便于存储和处理。图像切片常用于网络地图、瓦片地图、图像拼接等应用中。 切片原理主要包括以下几个步骤: 1. 定义切片大小:首先需要定义每个小图的大小,一般情况下是正方形或矩形。 2. 计算切片数量:根据定义的切片大小,计算原始图像需要被切成多少个小图。计算公式为:切片数量 = 原始图像宽度 / 切片宽度 × 原始图像高度 / 切片高度。 3. 切割图像:按照计算出的切片数量,将原始图像分割成相应数量的小图。可以使用图像处理库或自己编写代码实现。 4. 存储切片:将切割后的小图存储到磁盘上,可以使用常见的图片格式,如JPEG、PNG等。 5. 加载切片:在需要显示切片的地方,根据需要加载相应的小图,组合成完整的图像。 使用图像切片可以降低处理大图像的复杂度,同时也能够提高图像的加载速度,使得用户可以更快地查看图像的细节。图像切片广泛应用于需要处理大图像的场景,能够提高图像处理和显示效率,同时也能够提高用户的体验。 ## 图像地图范围设置 一些影像地图通常会有图像对应的地图范围。在上传图像的同时,或上传图像后需要设置相应的地理范围。 如tiff图像,通常会有相应的地理范围tfw文件. tiff/tfw, jpg/jpgw坐标文件的格式(6个参数) 0.030000 0.0000000000 0.0000000000 -0.030000 451510.875000 3358045.000000 以上每行对应的含义: 1 地图单元中的一个象素在X方向上的X分辨率尺度。 2 平移量。 3 旋转量。 4 地图单元中的一个象素在Y方向上的Y分辨率尺度的负值。 5 象素1,1(左上方)的X地坐标。 6 象素1,1(左上方)的Y地坐标。 在上传图时需要根据文件中的第一个,第五个和第六个值设置地图范围 ![image-20230319205612982](https://vjmap.com/blogimages/image-20230319205612982.png) 或者上传完后,操作菜单中点击设置地图范围进行设置 ![image-20230319205732187](https://vjmap.com/blogimages/image-20230319205732187.png) ## 图像处理 如果需要对上传的图像进行旋转,拼接等处理后,才进行切片的话,可以新建一个cmd.txt文件,把处理的命令写进文件中,然后和图像一起打包成zip上传。 如需要把1.jpg,2.jpg拼接成一个新的图片m1.png再打开,cmd.txt的写法如下: ``` join 1.jpg 2.jpg m1.png horizontal ``` 再把1.jpg,2.jpg,cmd.txt三个文件打包成zip文件上传即可。 如原始图片为 ![image-20230319204505566](https://vjmap.com/blogimages/image-20230319204505566.png) 如需要把这三个文件拼接成一个文件,如进行如下操作 同目录下面新建 cmd.txt,内容为 ``` join 0.jpg 1.jpg m1.png horizontal join m1.png 2.jpg m3.png horizontal ``` 再把0.jpg、1.jpg、2.jpg、cmd.txt 打包压缩成 image.zip (image可定义命名) 上传 image.zip文件即可。 ![image-20230319204822323](https://vjmap.com/blogimages/image-20230319204822323.png) cmd.txt 格式为: ``` 图像处理命令名1 图像处理命令名1参数1 图像处理命令名1参数2 图像处理命令名2 图像处理命令名2参数1 图像处理命令名2参数2 图像处理命令名3参数3 图像处理命令名3 图像处理命令名3参数1 ``` 多个命令名之间用空白行分开 命名名和参数之间换行分开 ## 图像处理常用命令及说明 ### 旋转图像 ``` rot in - 输入图像,输入 图像文件名 out - 输出图像,输出 图像文件名 angle - 旋转图像的角度,默认值:d90 允许:d0、d90、d180、d270 ``` 示例,把图像1.jpg旋转90度。再进行切片处理 cmd.txt中可输入 ``` rot 1.jpg result.jpg d90 ``` 其中result.jpg名称可随意命名(只能英文或数字) 把1.jpg和cmd.txt压缩成zip文件上传即可 ### 裁剪图像,可以指定裁剪的左上角坐标、裁剪区域的宽度和高度等参数。 ``` crop in - 输入图像,输入 图像文件名 out - 输出图像,输出 图像文件名 int left int top int width int height ``` ### 缩放图像 ``` resize in - 输入图像,输入 图像文件名 out - 输出图像,输出 图像文件名 scale - 缩放的倍数 如2表示把图像放大2倍,0.5表示缩小一倍 ``` ### 图像翻转 ``` flip in - 输入图像,输入 图像文件名 out - 输出图像,输出 图像文件名 direction - 方向 horizontal 或 vertical ``` ### 图像变换 ``` affine in - 输入图像,输入 图像文件名 out - 输出图像,输出 图像文件名 "2 0 0 1" 2x2 变换矩阵需要一个包含四个数字的数组。将数组作为空格分隔的列表传递 ``` ### 图像拼接 ``` join in - 输入图像,输入 图像文件名 in - 输入图像,输入 图像文件名 out - 输出图像,输出 图像文件名 direction - 拼接的方向 horizontal 或 vertical ``` ### 图像阵列 用于将多个文件或通配符指定的文件合并成一个数组。该工具通常用于将多个图像拼接成一个大图像或将多个通道的图像合并成一个多通道图像。 ``` arrayjoin image1.jpg image2.jpg image3.jpg ...可以多个输入文件 output.jpg ``` ## 图像格式支持后台配置 在config.json中`map`项中增加`supportImageFormat` ```json { "map" : { "supportImageFormat": "jpc;tif;tiff;png;jpg;jpeg;jpe;jfif;bmp;dib;gif;pdf;hdr;webp;heic;hif;heif;avif;zip", "supportImageFileMinSize": 0, "supportImageFileMaxSize": 0 } } ``` `supportImageFormat`为支持的文件类型,可修改或增加删除格式 `supportImageFileMinSize`和`supportImageFileMaxSize`为上传允许的最小和最大尺寸。为0表示不限制 修改完参数重启服务即可. # 实现 ## 先上效果图 ![imageslice.gif](https://vjmap.com/blogimages/imageslice.gif) ## 上传打开图形 先上传大图,至后台进行切片处理, 上传相关代码为: ```js async onChangeFile(file) { try { message.info('文件上传中,请稍候...') this.isSelectFile = false; this.uploadMapResult = await svc.uploadMap(file.raw); if (this.uploadMapResult.error) { message.error('上传图形失败!' + this.uploadMapResult.error) return } this.form.mapid = this.uploadMapResult.mapid; this.form.uploadname = this.uploadMapResult.uploadname; this.maptype = this.uploadMapResult.maptype || ''; this.dialogVisible = true; } catch (error) { console.error(error); message.error('上传图形失败!', error) } } ``` 如果需要上传后对图像进行处理,可以新建一个cmd.txt文件,把处理的命令写进文件中,然后和图像一起打包成zip上传。 如需要把1.jpg,2.jpg拼接成一个新的图片m1.png再打开,cmd.txt的写法如下: ``` join 1.jpg 2.jpg m1.png horizontal ``` 再把1.jpg,2.jpg,cmd.txt三个文件打包成zip文件上传即可 打开图像相关代码 ```js async onOpenMap() { try { let mapid = this.form.mapid; let param = { ...this.uploadMapResult, // 图名称 mapid: this.form.mapid, // 上传完返回的fileid fileid: this.uploadMapResult.fileid, // 上传完返回的文件名 uploadname: this.form.uploadname, // 地图打开方式 mapopenway: this.form.openway === "直接打开图形" ? vjmap.MapOpenWay.Memory : vjmap.MapOpenWay.GeomRender, // 如果要密码访问的话,设置秘钥值 secretKey: this.form.isPasswordProtection ? svc.pwdToSecretKey(this.form.password) : undefined, style: vjmap.openMapDarkStyle(),// div为深色背景颜色时,这里也传深色背景样式 // 图像类型设置地图左上角坐标和分辨率 imageLeft: this.form.imageLeft ? +this.form.imageLeft : undefined, imageTop: this.form.imageTop ? +this.form.imageTop : undefined, imageResolution: this.form.imageResolution ? +this.form.imageResolution : undefined, } let isVectorStyle = this.form.openway === "存储后渲染矢量"; await openMap(param, isVectorStyle); } catch (error) { console.error(error); message.error('打开图形失败!', error) } } ``` # 应用案例 ## 应用一 对图像进行拼接前端查看 原始图片为 ![image-20230319204505566](https://vjmap.com/blogimages/image-20230319204505566.png) ![image-20230319204822323](https://vjmap.com/blogimages/image-20230319204822323.png) 最终效果为: ![image-20230319204924823](https://vjmap.com/blogimages/image-20230319204924823.png) 体验地址: [https://vjmap.com/app/cloud/#/map/t3c93d14ff09?version=v1&mapopenway=GeomRender&vector=false](https://vjmap.com/app/cloud/#/map/t3c93d14ff09?version=v1&mapopenway=GeomRender&vector=false) ## 应用二 对tiff影像进行切片并与CAD图叠加校准 对tiff影像上传时可设置地理坐标范围。 tiff/tfw, jpg/jpgw坐标文件的格式(6个参数) 0.030000 0.0000000000 0.0000000000 -0.030000 451510.875000 3358045.000000 以上每行对应的含义: 1 地图单元中的一个象素在X方向上的X分辨率尺度。 2 平移量。 3 旋转量。 4 地图单元中的一个象素在Y方向上的Y分辨率尺度的负值。 5 象素1,1(左上方)的X地坐标。 6 象素1,1(左上方)的Y地坐标。 在上传图时需要根据文件中的第一个,第五个和第六个值设置地图范围 ![image-20230319205612982](https://vjmap.com/blogimages/image-20230319205612982.png) 或者上传完后,操作菜单中点击设置地图范围进行设置 ![image-20230319205732187](https://vjmap.com/blogimages/image-20230319205732187.png) 影像地图切片完成后,可与CAD图进行叠加校准。效果如下 ![image-20230319205945507](https://vjmap.com/blogimages/image-20230319205945507.png) 体验地址: [https://vjmap.com/demo/#/demo/map/comprehensive/04imagecadmap](https://vjmap.com/demo/#/demo/map/comprehensive/04imagecadmap) ## imageproc代码开源 main.go ```go package main import ( "fmt" "os" "os/exec" ) func main() { // 要执行的exe程序的路径 cmdPath := os.Args[1] // 传递给程序的参数 args := os.Args[2:] // 执行命令 cmd := exec.Command(cmdPath, args...) output, err := cmd.CombinedOutput() if err != nil { fmt.Println("ERROR_MSG:", err.Error(), string(output)) } else { // 输出结果 fmt.Println(string(output)) } } ``` --- # 导出 ## 导出pdf [导出pdf在线体验地址](https://vjmap.com/demo/#/demo/map/service/31mapexportpdf) ```js let mapid; // 不填的话,用上面打开默认的 let version; // 不填的话,用上面打开默认的 let param; // 不填的话,用默认的 /* param = {   lineWeight: false, // 导出时是否包含线宽 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, // 是否导出为灰色模式 }*/ // 导出pdf const exportPdf = async () => { message.info("正在导出pdf,请稍等...") const result = await svc.execCommand("exportPdf", param, mapid, version, true); if (result.error) { message.error(result.error) } else { let pdfUrl = svc.baseUrl() + result.path + "?token=" + svc.accessToken; window.open(pdfUrl, ) } } // 也可以对图纸进行处理后,再导出如旋转90度后再导出 /* // 导出pdf const exportPdf = async () => {     message.info("正在导出pdf,请稍等...")     let composeRes = await svc.composeNewMap({         mapid: 'cbf527ed3ad1',         version: 'v1',         fourParameter: [0, 0, 1, -Math.PI / 2]     })     let res = await svc.updateMap({         // 获取一个临时的图id(临时图形只会用临时查看,过期会自动删除)         mapid: vjmap.getTempMapId(1), // 临时图形不浏览情况下过期自动删除时间,单位分钟。默认30         fileid: composeRes.fileid, // 生成的fileid         mapopenway: vjmap.MapOpenWay.Memory,         style: {             backcolor: 0 // 如果div背景色是浅色,则设置为oxFFFFFF         }     })     const result = await svc.execCommand("exportPdf", param, res.mapid, res.version, true);     if (result.error) {         message.error(result.error)     } else {         let pdfUrl = svc.baseUrl() + result.path + "?token=" + svc.accessToken;         window.open(pdfUrl, )     } } ```
// 地图服务对象
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: env.exampleMapId, // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    message.error(res.error)
}
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(res.bounds);
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(prj.getMapExtent().center()), // 设置地图中心点
    zoom: 1, // 设置地图缩放级别
    renderWorldCopies: false
});
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();
const exportPdf = async () => {
    map.logInfo("正在导出pdf,请稍等...")
    let param;
    const result = await svc.execCommand("exportPdf", param, env.exampleMapId, "v1", true);
    if (result.error) {
        map.logInfo(result.error, "error")
    } else {
        let pdfUrl = svc.baseUrl() + result.path + "?token=" + svc.accessToken;
        window.open(pdfUrl, )
    }
}
// 增加一个按钮
let options = {};
options.buttons = [
    {
        id: "pdf",
        title: "导出成PDF",
        text: "导出Pdf",
        onActivate: (ctx, e) => {
            exportPdf()
        }
    }
];
let btnGroupCtrl = new vjmap.ButtonGroupControl(options);
map.addControl(btnGroupCtrl, 'top-right');


## 导出图片 导出图片可用地图服务的`WMS`瓦片服务导出图片 ```ts /** * wms服务url地址接口 */ export interface IWmsTileUrl { /** 地图ID(为空时采用当前打开的mapid), 为数组时表时同时请求多个. */ mapid?: string | string[]; /** 地图版本(为空时采用当前打开的地图版本). */ version?: string | string[]; /** 图层名称(为空时采用当前打开的地图图层名称). */ layers?: string | string[]; /** 范围,缺省{bbox-epsg-3857}. (如果要获取地图cad一个范围的wms数据无需任何坐标转换,将此范围填cad范围,srs,crs,mapbounds填为空).*/ bbox?: string; /** 当前坐标系,缺省(EPSG:3857). */ srs?: string; /** cad图的坐标系,为空的时候由元数据坐标系决定. 也可直接输入proj4字符串*/ crs?: string | string[]; /** 地理真实范围,如有值时,srs将不起作用 */ mapbounds?: string; /** 宽. */ width?: number; /** 高. */ height?: number; /** 是否透明. */ transparent?: boolean; /** 不透明时的背景颜色,默认为白色。格式必须为rgb(r,g,b)或rgba(r,g,b,a),a不透明应该是255. */ backgroundColor?: string; /** 四参数(x偏移,y偏移,缩放,旋转弧度),可选,对坐标最后进行修正*/ fourParameter?: string | string[]; /** 是否用上面的四参数进行反算,默认false*/ isInverseFourParamter?: boolean | boolean[]; /** 是否是矢量瓦片. */ mvt?: boolean; /** 是否考虑旋转,在不同坐标系中转换是需要考虑。默认自动考虑是否需要旋转. */ useImageRotate?: boolean; /** 旋转时图像处理算法. 1或2,默认自动选择(旋转时有用)*/ imageProcessAlg?: number; /** 当前互联网底图地图类型 WGS84(84坐标,如天地图,osm), GCJ02(火星坐标,如高德,腾讯地图), BD09LL(百度经纬度坐标,如百度地图), BD09MC(百度墨卡托米制坐标,如百度地图)*/ webMapType?: "WGS84" | "GCJ02" | "BD09LL" | "BD09MC"; } ``` 示例代码 ```js const exportMapPngByBoundsUrl = points => { let bounds = vjmap.GeoBounds.fromDataExtent(points); bounds = map.fromLngLat(bounds); // bounds = bounds.square(); // 保证为正方形 bounds = bounds.scale(1.01); // 稍大点,要不边框线有可能刚好看不见了 let pictureWidth = 1024 ;// 要导出的图片宽高 let wmsUrl = svc.wmsTileUrl({ width: pictureWidth, height: Math.round(pictureWidth * bounds.height() / bounds.width()), srs: "", bbox: bounds.toString(), transparent: false, backgroundColor: 'rgb(0,0,0)' }); window.open(wmsUrl); }; ```
// 地图服务对象
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: env.exampleMapId, // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    message.error(res.error)
}
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(res.bounds);
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(prj.getMapExtent().center()), // 设置地图中心点
    zoom: 1, // 设置地图缩放级别
    renderWorldCopies: false
});
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();
const exportPng = async () => {
    let bounds = await svc.cmdGetDrawBounds(); // CAD图的可视范围(有图的部分的范围)
    bounds = bounds.scale(1.01); // 稍大点,要不边框线有可能刚好看不见了
    let pictureWidth = 1024 ;// 要导出的图片宽高
    let wmsUrl = svc.wmsTileUrl({
        width: pictureWidth,
        height: Math.round(pictureWidth * bounds.height() / bounds.width()),
        srs: "",
        bbox: bounds.toString(),
        transparent: true,
        backgroundColor: 'rgb(0,0,0)'
    });
    window.open(wmsUrl);
}
// 增加一个按钮
let options = {};
options.buttons = [
    {
        id: "pdf",
        title: "导出图片",
        text: "导出图片",
        onActivate: (ctx, e) => {
            exportPng()
        }
    }
];
let btnGroupCtrl = new vjmap.ButtonGroupControl(options);
map.addControl(btnGroupCtrl, 'top-right');


## 导出dxf ```js const result = await svc.execCommand("exportDxf", {}, mapid, version, true); ``` :::tip 提示 只是由于数据安全的问题,试用版本不能下载导出的dxf文件。正式版本可以后台设置下载, 根据返回的fileid (位于mapfiles目录下面)和配置的下载地址去下载 ::: --- # Map ## 参数 |方法名|参数|说明| |:---|:---|:---| |`container` |**( `HTMLElement`| `string`)** |将在其中呈现地图的 HTML 元素,或元素的 string `id`。指定的元素必须没有子元素。| |`minZoom` |`number`|地图的最小缩放级别 (0-24)。(可选,默认`0`)| |`maxZoom` |`number`|地图的最大缩放级别 (0-24)。(可选,默认`22`)| |`minPitch` |`number`|地图的最小间距 (0-85)。(可选,默认`0`)| |`maxPitch` |`number`|地图的最大间距(0-85)。(可选,默认`85`)| |`style` |( `Object`| `string`)?|地图的样式。| |`hash` |( `boolean`| `string`)|如果`true`,则地图的位置(缩放、中心纬度、中心经度、方位角和俯仰角)将与页面 URL 的哈希片段同步。例如,`http://path/to/my/page.html#2.59/39.26/53.07/-24.1/60`。可以选择提供附加字符串来指示参数样式的哈希,例如`http://path/to/my/page.html#map=2.59/39.26/53.07/-24.1/60&foo=bar `,其中 foo 是自定义参数, bar 是与地图哈希不同的任意哈希。(可选,默认`false`)| |`interactive` |`boolean`|如果`false`,则不会将鼠标、触摸或键盘侦听器附加到地图,因此它不会响应交互。(可选,默认`true`)| |`bearingSnap` |`number`|阈值(以度为单位),用于确定地图的方位何时向北对齐。例如,当 `bearingSnap`为 7 时,如果用户在北纬 7 度范围内旋转地图,地图将自动对齐到正北。(可选,默认`7`)| |`pitchWithRotate` |`boolean`|如果`false`,地图的俯仰(倾斜)控制与“拖动旋转”交互将被禁用。(可选,默认`true`)| |`clickTolerance` |`number`|用户可以在单击期间移动鼠标指针以将其视为有效单击(与鼠标拖动相反)的最大像素数。(可选,默认`3`)| |`attributionControl` |`boolean`|如果`true`,一个AttributionControl将被添加到地图中。(可选,默认`true`)| |`customAttribution` |( `string`| `Array`< `string`\>)?|在`AttributionControl 中`显示的一个或多个字符串。仅适用于采用`attributionControl`是`true`。| |`logoPosition` |`string`|一个字符串,表示文字标记在地图上的位置。有效选项为`top-left`、`top-right`、`bottom-left`、`bottom-right`。(可选,默认`'bottom-left'`)| |`failIfMajorPerformanceCaveat` |`boolean`|如果`true`,如果性能比预期的差很多(即使用软件渲染器),地图创建将失败。(可选,默认`false`)| |`preserveDrawingBuffer` |`boolean`|如果`true`,则可以使用 将地图的画布导出为 PNG `map.getCanvas().toDataURL()`。这是`false`默认的性能优化。(可选,默认`false`)| |`antialias` |`boolean`?| 如果`true`,将使用 MSAA 抗锯齿创建上下文,这对于抗锯齿自定义图层很有用。这是`false`默认的性能优化。| |`refreshExpiredTiles` |`boolean`|如果`false`,地图将不会尝试重新请求图块,一旦它们根据其 HTTP `cacheControl`/`expires`标头过期。(可选,默认`true`)| |`maxBounds` |LngLatBoundsLike ?| 如果设置,地图将被限制在给定的范围内。| |`scrollZoom` |( `boolean`| `Object`)|如果`true`,则启用“滚动缩放”交互。一个`Object`值作为选项传递给`ScrollZoomHandler#enable`。(可选,默认`true`)| |`boxZoom` |`boolean`|如果`true`,则启用“框缩放”交互(请参阅BoxZoomHandler)。(可选,默认`true`)| |`dragRotate` |`boolean`|如果`true`,则启用“拖动旋转”交互(请参阅DragRotateHandler)。(可选,默认`true`)| |`dragPan` |( `boolean`| `Object`)|如果`true`,则启用“拖动平移”交互。一个`Object`值作为选项传递给`DragPanHandler#enable`。(可选,默认`true`)| |`keyboard` |`boolean`|如果`true`,则启用键盘快捷键(请参阅KeyboardHandler)。(可选,默认`true`)| |`doubleClickZoom` |`boolean`|如果`true`,则启用“双击缩放”交互(请参阅DoubleClickZoomHandler)。(可选,默认`true`)| |`touchZoomRotate` |( `boolean`| `Object`)|如果`true`,则启用“捏合旋转和缩放”交互。一个`Object`值作为选项传递给`TouchZoomRotateHandler#enable`。(可选,默认`true`)| |`touchPitch` |( `boolean`| `Object`)|如果`true`,则启用“拖动到音调”交互。一个`Object`值作为选项传递给`TouchPitchHandler#enable`。(可选,默认`true`)| |`trackResize` |`boolean`|如果`true`,则当浏览器窗口调整大小时,地图将自动调整大小。(可选,默认`true`)| |`center` |LngLatLike|地图的初始地理中心点。如果`center`未在构造函数选项中指定, 将在地图的样式对象中查找它。如果样式中也没有指定,则默认为`0, 0`注意:地图使用经度、纬度坐标顺序(相对于纬度、经度)来匹配 GeoJSON。(可选,默认`0,0`)| |`zoom` |`number`|地图的初始缩放级别。如果`zoom`未在构造函数选项中指定,将在地图的样式对象中查找它。如果样式中也没有指定,则默认为`0`. (可选,默认`0`)| |`bearing` |`number`|地图的初始方位(旋转),以北为逆时针方向测量的度数。如果`bearing`未在构造函数选项中指定,将在地图的样式对象中查找它。如果样式中也没有指定,则默认为`0`. (可选,默认`0`)| |`pitch` |`number`|地图的初始倾斜度(倾斜度),以距屏幕平面 (0-85) 的度数为单位。如果`pitch`未在构造函数选项中指定,将在地图的样式对象中查找它。如果样式中也没有指定,则默认为`0`. (可选,默认`0`)| |`bounds` |LngLatBoundsLike ?| 地图的初始边界。如果`bounds`指定,它将覆盖`center`和`zoom`构造函数选项。 |`fitBoundsOptions` |`Object`?| 甲`地图#fitBounds`选项对象使用_仅_拟合初始时`bounds`的上方。| |`optimizeForTerrain` |`boolean`|启用地形时,如果`true`,地图将呈现性能优先级,这可能会导致图层重新排序以最大化性能(将首先绘制覆盖在地形上的图层,包括填充、线条、背景、山体阴影和栅格)。否则,如果设置为`false`,地图将始终按图层顺序优先级绘制。(可选,默认`true`)| |`renderWorldCopies` |`boolean`|如果`true`,世界的多个副本将在超过 -180 度和 180 度经度的范围内并排呈现。如果设置为`false`:|当地图缩小到足以使世界的单个表示无法填满地图的整个容器时,超过 180 度和 -180 度经度将出现空白。在每个缩放级别,跨越 180 度和 -180 度经度的要素将被切成两部分(一部分位于地图的右边缘,另一部分位于地图的左边缘)。(可选,默认`true`)| |`maxTileCacheSize` |`number`|存储在给定源的切片缓存中的最大切片数。如果省略,缓存将根据当前视口动态调整大小。(可选,默认`null`)| |`localIdeographFontFamily` |`string`|定义一个 CSS 字体系列,用于在“CJK 统一表意文字”、“平假名”、“片假名”和“韩文音节”范围内本地覆盖生成字形。在这些范围内,地图样式的字体设置将被忽略,除了字体粗细关键字(light/regular/medium/bold)。设置为`false`, 以针对这些字形范围启用地图样式的字体设置。此选项的目的是避免带宽密集型字形服务器请求。(请参阅`使用本地生成的表意文字 `.)(可选,默认`'sans-serif'`)| |`localFontFamily` |`string`|定义一个 CSS 字体系列,用于本地覆盖所有字形的生成。除了字体粗细关键字(light/regular/medium/bold)之外,地图样式中的字体设置将被忽略。如果设置,此选项将覆盖 localIdeographFontFamily 中的设置(可选,默认`false`)| |`transformRequest` |RequestTransformFunction|在 Map 请求外部 URL 之前运行的回调。回调可用于修改 url、设置标头或设置跨域请求的凭据属性。预计返回`requestParameters的`对象与`url`属性和可选`headers`和`credentials`性能。(可选,默认`null`)| |`collectResourceTiming` |`boolean`|如果`true`,将为 GeoJSON 和 Vector Tile Web Worker 发出的请求收集资源计时 API 信息(此信息通常无法从主 Javascript 线程访问)。信息将在`resourceTiming`相关`data`事件的属性中返回。(可选,默认`false`) |`fadeDuration` |`number`|控制标签碰撞的淡入/淡出动画的持续时间,以毫秒为单位。此设置会影响所有符号图层。此设置不会影响运行时样式转换或光栅图块淡入淡出的持续时间。(可选,默认`300`)| |`crossSourceCollisions` |`boolean`|如果`true`,来自多个源的符号可以在碰撞检测期间相互碰撞。如果`false`,则对每个源中的符号单独运行碰撞检测。(可选,默认`true`)| |`locale` |`Object`|应用于 UI 字符串的默认本地化表的补丁,例如控件工具提示。该`locale`对象将命名空间的 UI 字符串 ID 映射到目标语言的翻译字符串;`src/ui/default_locale.js`有关所有支持的字符串 ID 的示例,请参见。该对象可以指定所有 UI 字符串(从而添加对新翻译的支持)或仅字符串的子集(从而修补默认翻译表)。(可选,默认`null`)| |`testMode` |`boolean`|静默由于无效的访问令牌而生成的错误和警告,这在使用库编写单元测试时很有用。(可选,默认`false`)| ## 事件 |事件 | 兼容`layerId`| |-|-|- |``mousedown`` | 是 | | ``mouseup`` | 是 | | ``mouseover`` | 是 | | ``mouseout`` | 是 | | ``mousemove`` | 是 | | ``mouseenter`` | 是(必填) | | ``mouseleave`` | 是(必填) | | ``click`` | 是 | | ``dblclick`` | 是 | | ``contextmenu`` | 是 | | ``contextMenuPreOpen`` | 是 | | ``contextMenuOpened`` | 是 | | ``contextMenuNoContent`` | 是 | | ``contextMenuCancel`` | 是 | | ``touchstart`` | 是 | | ``touchend`` | 是 | | ``touchcancel`` | 是 | | ``wheel`` | | ``resize`` | | ``remove`` | | ``touchmove`` | | ``movestart`` | | ``move`` | | ``moveend`` | | ``dragstart`` | | ``drag`` | | ``dragend`` | | ``zoomstart`` | | ``zoom`` | | ``zoomend`` | | ``rotatestart`` | | ``rotate`` | | ``rotateend`` | | ``pitchstart`` | | ``pitch`` | | ``pitchend`` | | ``boxzoomstart`` | | ``boxzoomend`` | | ``boxzoomcancel`` | | ``webglcontextlost`` | | ``webglcontextrestored``| | ``load`` | | ``render`` | | ``idle`` | | ``error`` | | ``data`` | | ``styledata`` | | ``sourcedata`` | | ``dataloading`` | | ``styledataloading`` | | ``sourcedataloading`` | | ``styleimagemissing`` | ::: tip 提示 当地图绑定的`容器div`大小发生变化时,要主动监听`容器div`大小发生变化事件,调用 `map.resize()` 触发底层重绘、否则有时会出现`绘制不全`的问题。 ::: ## 常用方法 ::: tip 提示 具体用法请参考文档 [Map](/doc/#map) ::: |方法名|说明| |:---|:---| |addControl|将`IControl`添加到地图,调用`control.onAdd(this)`.| |removeControl|图中删除控件| |hasControl|控件是否在地图上| |resize|其`container`元素的尺寸调整地图的大小| |getBounds|地图的地理边界| |getMaxBounds|地图被限制到的最大地理边界,或者`null`如果没有设置| |setMaxBounds|或清除地图的地理边界| |setMinZoom|或清除地图的最小缩放级别| |getMinZoom|地图的最小允许缩放级别| |setMaxZoom|或清除地图的最大缩放级别| |getMaxZoom|地图的最大允许缩放级别| |setMinPitch|或清除地图的最小间距| |getMinPitch|地图的最小允许间距| |setMaxPitch|或清除地图的最大间距| |getMaxPitch|地图的最大允许间距| |getRenderWorldCopies|返回的状态`renderWorldCopies`| |setRenderWorldCopies|设置的状态`renderWorldCopies`| |project|一个`Point`表示像素坐标,相对于地图的`container`,对应于指定的地理位置| |unproject|表示与指定像素坐标对应的地理坐标的`LngLat`| |isMoving|地图由于相机动画或用户手势而平移、缩放、旋转或俯仰,则返回true| |isZooming|地图由于相机动画或用户手势而缩放,则返回true| |isRotating|地图由于相机动画或用户手势而旋转,则返回true| |on|为指定类型的事件添加侦听器,可选择限制为指定样式层中的功能| |once|添加一个仅对指定事件类型调用一次的侦听器,可选择仅限于在指定样式图层中的要素上发生的事件| |off|删除先前使用`Map| |queryRenderedFeatures|返回一个`GeoJSON`数组```特征对象`表示满足查询参数的可见特征| |querySourceFeatures|返回一个`GeoJSON`数组```特征对象`表示满足查询参数的指定矢量切片或GeoJSON源中的要素| |setStyle|使用新值更新地图的样式对象| |getStyle|地图的`样式`对象,一个JSON对象,可用于重新创建地图的样式| |isStyleLoaded|一个布尔值,指示地图的样式是否已完全加载| |addSource|将源添加到地图的样式| |isSourceLoaded|一个布尔值,指示源是否已加载| |areTilesLoaded|一个布尔值,指示是否加载了来自样式上所有源的视口中的所有图块| |removeSource|图的样式中删除源| |getSource|返回具有地图样式中指定ID的源| |addImage|向样式添加图像| |updateImage|样式中的现有图像| |hasImage|样式中是否存在具有特定ID的图像| |removeImage|删除图像| |loadImage|从外部URL加载图像以与`Map| |listImages|一个字符串数组,其中包含地图中当前可用的所有图像的ID| |addLayer|添加`样式图层`到地图的样式| |moveLayer|层移动到不同的z位置| |removeLayer|图的样式中删除具有给定ID的图层| |getLayer|返回地图样式中具有指定ID的图层| |setLayerZoomRange|指定样式图层的缩放范围| |setFilter|为指定的样式层设置过滤器| |getFilter|应用于指定样式层的过滤器| |setPaintProperty|设置指定样式层中绘制属性的值| |getPaintProperty|指定样式层中绘制属性的值| |setLayoutProperty|设置指定样式层中布局属性的值| |getLayoutProperty|指定样式层中布局属性的值| |setLight|设置灯光值的任意组合| |getLight|灯光对象的值| |setTerrain|样式的地形属性| |getTerrain|地形规范或`null`地形未在地图上设置| |setFog|样式的雾属性| |getFog|雾规范或`null`地图上是否未设置雾| |queryTerrainElevation|当前加载的某个地理位置的高程数据| |setFeatureState|设置`state`特征的| |removeFeatureState|`state`功能的,将其设置回默认行为| |getFeatureState|`state`功能的| |getContainer|地图的包含HTML元素| |getCanvasContainer|返回包含地图`<canvas>`元素的HTML元素| |getCanvas|返回地图的`<canvas>`元素| |loaded|一个布尔值,指示地图是否已完全加载| |remove|并释放与此地图关联的所有内部资源| |triggerRepaint|触发单帧渲染| |showTileBoundaries|和设置一个布尔值,指示地图是否将围绕每个图块和图块ID呈现轮廓| |showTerrainWireframe|并设置一个布尔值,指示地图是否将在显示的地形顶部呈现线框| |showPadding|并设置一个布尔值,指示地图是否将填充偏移可视化| |showCollisionBoxes|和设置一个布尔值,指示地图是否将围绕数据源中的所有符号呈现框,显示哪些符号已渲染或由于碰撞而隐藏| |repaint|和设置一个布尔值,指示地图是否会连续重绘| |version|使用的版本| |getCenter|返回地图的地理中心点| |setCenter|地图的地理中心点| |panBy|按指定的偏移量平移地图| |panTo|使用动画过渡将地图平移到指定位置| |getZoom|地图的当前缩放级别| |setZoom|地图的缩放级别| |zoomTo|动画过渡将地图缩放到指定的缩放级别| |zoomIn|图的缩放级别增加1| |zoomOut|图的缩放级别降低1| |getBearing|返回地图的当前方位| |setBearing|地图的方位(旋转)| |getPadding|在地图视口周围应用的当前填充| |setPadding|视口周围的填充(以像素为单位)| |rotateTo|动画过渡将地图旋转到指定的方位| |resetNorth|地图,使北方向上(0°方位角),并带有动画过渡| |resetNorthPitch|和倾斜地图,使北朝上(0°方位角)且俯仰角为0°,并带有动画过渡| |snapToNorth|当前方位足够接近它(即在`bearingSnap`阈值内),则捕捉地图使北朝上(0°方位)| |getPitch|地图的当前倾斜度(倾斜度)| |setPitch|地图的倾斜度(倾斜度)| |cameraForBounds|计算视口中这些边界的中心,并使用最高缩放级别,以将其可见区域包含在指定的地理边界内| |fitBounds|平移和缩放地图以将其可见区域包含在指定的地理边界内| |fitScreenCoordinates|一旦地图旋转到指定的方位,平移、旋转和缩放地图以适应由点p0和p1构成的框| |jumpTo|更改中心、缩放、方位和俯仰的任意组合,无需动画过渡| |getFreeCameraOptions|相机实体的位置和方向| |setFreeCameraOptions|reeCameraOptions`提供对底层相机实体的更直接访问| |easeTo|更改的任意组合`center`,`zoom`,`bearing`,`pitch`,并`padding`与新老值之间动画转换| |flyTo|改变中心、缩放、方位和俯仰的任意组合,沿着一条唤起飞行的曲线动画过渡| |stop|任何正在进行的动画过渡| |resize|整地图大小后立即触发| |changeSourceTiles|更改地图栅格瓦片地址| |attach|关联服务对象和投影对象| |getService|得到服务对象接口| |getAttachOptions|得到关联的选项| |setMapProjection|设置投影接口| |getMapProjection|得到投影接口| |toLngLat|地图地理坐标转经纬度| |fromLngLat|经纬度转地图地理坐标| |switchRasterToVectorStyle|切换至矢量瓦片地址| |switchVectorToRasterStyle|切换至栅格瓦片地址| |enableVectorLayerHoverHighlight|使矢量图层悬浮高亮| |disableVectorLayerHoverHighlight|不使矢量图层悬浮高亮| |enableLayerClickHighlight|使图层点击高亮| |disableLayerClickHighlight|不使图层点击高亮| |hasVectorLayer|是否是矢量图层| |waitLoaded|等待地图加载完成| |switchLayers|切换图层| |updateStyle|更新样式| |updateMapExtent|更新地图范围,重新生成投影对象| |fitMapBounds|缩放至地图范围| |getGeoBounds|得到地图的地理范围| |getMarkers|得到地图的所有marker| |projectEx|经纬度坐标转像素坐标,不会返回无效值| |createThreeJsContext|创建Threejs上下文,请确保已加载其插件| |hoverPointer|每当鼠标悬停在这些图层上时,将地图的光标设置为“指针”。| |hoverFeatureState|每当将鼠标悬停在这些图层中的某个特征上时,更新连接源[s]中特征的特征状态。| |hoverPopup|将鼠标悬停在这些图层中的某个要素上时,会显示一个弹出窗口。| |clickPopup|每当单击这些图层中的要素时都会显示一个弹出窗口。| |clickLayer|每当单击这些图层中的要素时都会触发回调。| |clickOneLayer|在给定的一系列图层中的第一个中检测单击,并触发回调。| |hoverLayer|当鼠标悬停在这些图层中的要素上时触发回调。| |mapAddLayerBefore|增加一个图层,位于某图层之前| |addLayerEx|添加一个图层,给定id、来源、类型和属性。| |addLayerBefore|在某个图层之前添加一个图层,给定id、来源、类型和属性。| |removeLayerEx|移除一个或多个图层| |addGeoJSONSource|创建一个GeoJSON层。| |addSourceEx|增加数据源| |layersBySource|获取一个数据源中的所有图层| |addVectorSource|增加矢量数据源| |properties|将pascalCase或kebab-case中的一组属性转换为具有布局和绘制属性的图层对象。| |layerStyle|图层样式| |show|使给定的图层可见。| |hide|使给定的图层不可见。| |toggle|根据参数使给定的图层隐藏或可见。| |showSource|使取决于给定源的所有图层可见。| |hideSource|使取决于给定源的所有图层不可见。| |toggleSource|根据参数,使连接到给定源的给定层隐藏或可见.| |removeSourceEx|移除一个或多个源,首先移除所有依赖于它们的层。如果源不存在,则不会提示错误| |onLoad|地图加载时触发的回调,或者如果地图已经加载则立即触发| |loadImageEx|从URL添加用作符号图层的图像。| |lockOrientation|锁定方向| |fontsInUse|获取正在使用的字体名称数组,由遍历样式决定。不会在所有可能的情况下检测字体。| |addLineLayer|添加一个类型为“line”的图层。| |addFillLayer|添加一个类型为“fill”的图层。| |addCircleLayer|添加一个类型为“circle”的图层。| |addSymbolLayer|添加一个类型为“符号”的图层。| |addVideoLayer|添加一个类型为`video`的层。| |addRasterLayer|加一个类型为`raster`的图层。| |addFillExtrusionLayer|添加一个类型为“FillExtrusion”的层。| |addHeatmapLayer|添加一个类型为“Heatmap”的图层。| |addHillshadeLayer|添加一个类型为“hillshade”的图层。| |addRasterSource|添加一个`raster`源| |addRasterDemSource|添加一个`raster-dem`源| |addImageSource|添加一个`image`源| |addVideoSource|添加“Video”源| |setLayer|设置图层属性| |setProperty|在一个或多个图层上设置绘制或布局属性。| |getLayerStyle|根据样式规范,获取给定图层ID的图层定义。| |setLayerStyle|设置图层样式| |setData|替换GeoJSON图层的当前数据。| |setFilterEx|替换一个图层层或多图层的过滤器。| |setLayerSource|设置图层至数据源| |setRootProperty|在样式的根上设置一个属性,例如`light`或`transition`.| |setTransition|设置过渡属性。| |setFillAntialias|为一个或多个图层设置`fill-antialias`绘制属性。| |setFillOpacity|为一个或多个图层设置`fill-opacity`绘画属性。| |setFillColor|为一个或多个图层设置`fill-color`绘画属性。| |setFillOutlineColor|为一个或多个图层设置`fill-outline-color`绘画属性。| |setFillTranslate|为一个或多个图层设置`fill-translate`绘制属性。| |setFillTranslateAnchor|为一个或多个图层设置`fill-translate-anchor`绘制属性。| |setFillPattern|为一个或多个图层设置`fill-pattern`绘制属性。| |setFillExtrusionOpacity|为一层或多层设置`fill-extrusion-opacity`绘画属性。| |setFillExtrusionColor|为一层或多层设置`fill-extrusion-color`绘制属性。| |setFillExtrusionTranslate|为一层或多层设置`fill-extrusion-translate`绘制属性。| |setFillExtrusionTranslateAnchor|为一层或多层设置`fill-extrusion-translate-anchor`绘制属性。| |setFillExtrusionPattern|为一层或多层设置`fill-extrusion-pattern`绘制属性。| |setFillExtrusionHeight|为一层或多层设置`fill-extrusion-height`绘制属性。| |setFillExtrusionBase|为一层或多层设置`fill-extrusion-base`绘制属性。| |setFillExtrusionVerticalGradient|为一层或多层设置`fill-extrusion-vertical-gradient`绘画属性。| |setLineOpacity|为一个或多个图层设置`line-opacity`绘制属性。| |setLineColor|为一个或多个图层设置`line-color`绘制属性。| |setLineTranslate|为一个或多个图层设置`line-translate`绘制属性。| |setLineTranslateAnchor|为一个或多个图层设置`line-translate-anchor`绘制属性。| |setLineWidth|设置一个或多个图层的“线宽”绘制属性。| |setLineGapWidth|为一层或多层设置`line-gap-width`绘制属性。| |setLineOffset|为一个或多个图层设置`line-offset`绘制属性。| |setLineBlur|为一个或多个图层设置`line-blur`绘制属性。| |setLineDasharray|为一个或多个图层设置`line-dasharray`绘制属性。| |setLinePattern|为一个或多个图层设置`line-pattern`绘制属性。| |setLineGradient|为一个或多个图层设置`line-gradient`绘制属性。| |setCircleRadius|为一层或多层设置`circle-radius`绘制属性。| |setCircleColor|为一个或多个图层设置`circle-color`绘制属性。| |setCircleBlur|为一层或多层设置`circle-blur`绘画属性。| |setCircleOpacity|为一个或多个图层设置`circle-opacity`绘制属性。| |setCircleTranslate|为一个或多个图层设置`circle-translate`绘制属性。| |setCircleTranslateAnchor|为一个或多个图层设置`circle-translate-anchor`绘制属性。| |setCirclePitchScale|设置一个或多个图层的“circle-pitch-scale”绘制属性。| |setCirclePitchAlignment|为一层或多层设置“circle-pitch-alignment”绘制属性。| |setCircleStrokeWidth|为一层或多层设置`circle-stroke-width`绘制属性。| |setCircleStrokeColor|为一个或多个图层设置`circle-stroke-color`绘画属性。| |setCircleStrokeOpacity|为一层或多层设置`circle-stroke-opacity`绘画属性。| |setHeatmapRadius|为一层或多层设置`heatmap-radius`绘制属性。| |setHeatmapWeight|为一层或多层设置“热图权重”绘制属性。| |setHeatmapIntensity|为一层或多层设置“热图强度”绘制属性。| |setHeatmapColor|为一层或多层设置“热图颜色”绘制属性。| |setHeatmapOpacity|为一层或多层设置`heatmap-opacity`绘制属性。| |setIconOpacity|为一个或多个图层设置`icon-opacity`绘制属性。| |setIconColor|为一个或多个图层设置`icon-color`绘制属性。| |setIconHaloColor|为一层或多层设置`icon-halo-color`绘制属性。| |setIconHaloWidth|为一层或多层设置`icon-halo-width`绘制属性。| |setIconHaloBlur|为一层或多层设置`icon-halo-blur`绘制属性。| |setIconTranslate|为一层或多层设置`icon-translate`绘制属性。| |setIconTranslateAnchor|为一层或多层设置`icon-translate-anchor`绘制属性。| |setTextOpacity|为一个或多个图层设置`text-opacity`绘制属性。| |setTextColor|为一个或多个图层设置`text-color`绘制属性。| |setTextHaloColor|为一个或多个图层设置`text-halo-color`绘制属性。| |setTextHaloWidth|为一个或多个图层设置`text-halo-width`绘制属性。| |setTextHaloBlur|为一个或多个图层设置`text-halo-blur`绘制属性。| |setTextTranslate|为一个或多个图层设置`text-translate`绘制属性。| |setTextTranslateAnchor|为一个或多个图层设置`text-translate-anchor`绘制属性。| |setRasterOpacity|为一层或多层设置`raster-opacity`绘制属性。| |setRasterHueRotate|为一层或多层设置`raster-hue-rotate`绘制属性。| |setRasterBrightnessMin|为一层或多层设置`raster-brightness-min`绘制属性。| |setRasterBrightnessMax|为一层或多层设置`raster-brightness-max`绘制属性。| |setRasterSaturation|为一层或多层设置`raster-saturation`绘制属性。| |setRasterContrast|为一层或多层设置`raster-contrast`绘制属性。| |setRasterResampling|为一个或多个图层设置`raster-resampling`绘制属性。| |setRasterFadeDuration|为一层或多层设置`raster-fade-duration`绘制属性。| |setHillshadeIlluminationDirection|为一层或多层设置`hillshade-illumination-direction`绘制属性。| |setHillshadeIlluminationAnchor|为一层或多层设置`hillshade-illumination-anchor`绘制属性。| |setHillshadeExaggeration|为一层或多层设置`hillshade-exaggeration`绘制属性。| |setHillshadeShadowColor|为一层或多层设置`hillshade-shadow-color`绘制属性。| |setHillshadeHighlightColor|为一层或多层设置`hillshade-highlight-color`绘制属性。| |setHillshadeAccentColor|为一层或多层设置`hillshade-accent-color`绘制属性。| |setBackgroundColor|为一层或多层设置`background-color`绘制属性。| |setBackgroundPattern|为一个或多个图层设置`background-pattern`绘制属性。| |setBackgroundOpacity|为一层或多层设置`background-opacity`绘制属性。| |setVisibility|为一个或多个图层设置“可见性”布局属性。| |setFillSortKey|为一层或多层设置`fill-sort-key`布局属性。| |setCircleSortKey|为一个或多个图层设置`circle-sort-key`布局属性。| |setLineCap|为一个或多个图层设置`line-cap`布局属性。| |setLineJoin|为一个或多个图层设置“line-join”布局属性。| |setLineMiterLimit|为一个或多个图层设置“line-miter-limit”布局属性。| |setLineRoundLimit|为一层或多层设置`line-round-limit`布局属性。| |setLineSortKey|为一层或多层设置`line-sort-key`布局属性。| |setSymbolPlacement|为一个或多个图层设置`symbol-placement`布局属性。| |setSymbolSpacing|为一个或多个图层设置`symbol-spacing`布局属性。| |setSymbolAvoidEdges|为一层或多层设置`symbol-avoid-edges`布局属性。| |setSymbolSortKey|为一个或多个图层设置`symbol-sort-key`布局属性。| |setSymbolZOrder|为一个或多个图层设置`symbol-z-order`布局属性。| |setIconAllowOverlap|为一层或多层设置`icon-allow-overlap`布局属性。| |setIconIgnorePlacement|为一个或多个图层设置`icon-ignore-placement`布局属性。| |setIconOptional|为一个或多个图层设置`icon-optional`布局属性。| |setIconRotationAlignment|为一个或多个图层设置`icon-rotation-alignment`布局属性。| |setIconSize|为一层或多层设置`icon-size`布局属性。| |setIconTextFit|为一层或多层设置`icon-text-fit`布局属性。| |setIconTextFitPadding|为一层或多层设置`icon-text-fit-padding`布局属性。| |setIconImage|为一层或多层设置`icon-image`布局属性。| |setIconRotate|为一层或多层设置`icon-rotate`布局属性。| |setIconPadding|为一层或多层设置`icon-padding`布局属性。| |setIconKeepUpright|为一层或多层设置`icon-keep-upright`布局属性。| |setIconOffset|为一层或多层设置`icon-offset`布局属性。| |setIconAnchor|为一层或多层设置`icon-anchor`布局属性。| |setIconPitchAlignment|为一层或多层设置`icon-pitch-alignment`布局属性。| |setTextPitchAlignment|为一个或多个图层设置`text-pitch-alignment`布局属性。| |setTextRotationAlignment|为一个或多个图层设置`text-rotation-alignment`布局属性。| |setTextField|为一个或多个图层设置`text-field`布局属性。| |setTextFont|为一个或多个图层设置`text-font`布局属性。| |setTextSize|为一个或多个图层设置`text-size`布局属性。| |setTextMaxWidth|为一层或多层设置`text-max-width`布局属性。| |setTextLineHeight|为一层或多层设置`text-line-height`布局属性。| |setTextLetterSpacing|为一层或多层设置`text-letter-spacing`布局属性。| |setTextJustify|为一个或多个图层设置`text-justify`布局属性。| |setTextRadialOffset|为一层或多层设置`text-radial-offset`布局属性。| |setTextVariableAnchor|为一层或多层设置`text-variable-anchor`布局属性。| |setTextAnchor|为一层或多层设置`text-anchor`布局属性。| |setTextMaxAngle|为一层或多层设置`text-max-angle`布局属性。| |setTextWritingMode|为一个或多个图层设置`text-writing-mode`布局属性。| |setTextRotate|为一个或多个图层设置`text-rotate`布局属性。| |setTextPadding|为一层或多层设置`text-padding`布局属性。| |setTextKeepUpright|为一个或多个图层设置`text-keep-upright`布局属性。| |setTextTransform|为一个或多个图层设置`text-transform`布局属性。| |setTextOffset|为一个或多个图层设置`text-offset`布局属性。| |setTextAllowOverlap|为一个或多个图层设置`text-allow-overlap`布局属性。| |setTextIgnorePlacement|为一个或多个图层设置`text-ignore-placement`布局属性。| |setTextOptional|为一个或多个图层设置`text-optional`布局属性。| |getFillAntialias|获取图层的“填充抗锯齿”绘制属性。| |getFillOpacity|获取图层的`fill-opacity`绘制属性。| |getFillColor|获取图层的`fill-color`绘制属性。| |getFillOutlineColor|获取图层的`fill-outline-color`绘制属性。| |getFillTranslate|获取图层的`fill-translate`绘制属性。| |getFillTranslateAnchor|获取图层的`fill-translate-anchor`绘制属性。| |getFillPattern|获取图层的`fill-pattern`绘制属性。| |getFillExtrusionOpacity|获取图层的`fill-extrusion-opacity`绘画属性。| |getFillExtrusionColor|获取图层的`fill-extrusion-color`绘画属性。| |getFillExtrusionTranslate|获取图层的`fill-extrusion-translate`绘制属性。| |getFillExtrusionTranslateAnchor|获取图层的`fill-extrusion-translate-anchor`绘制属性。| |getFillExtrusionPattern|获取图层的`fill-extrusion-pattern`绘制属性。| |getFillExtrusionHeight|获取图层的`fill-extrusion-height`绘制属性。| |getFillExtrusionBase|获取图层的`fill-extrusion-base`绘画属性。| |getFillExtrusionVerticalGradient|获取图层的`fill-extrusion-vertical-gradient`绘制属性。| |getLineOpacity|获取图层的`line-opacity`绘制属性。| |getLineColor|获取图层的`line-color`绘制属性。| |getLineTranslate|获取图层的`line-translate`绘制属性。| |getLineTranslateAnchor|获取图层的`line-translate-anchor`绘制属性。| |getLineWidth|获取图层的“线宽”绘制属性。| |getLineGapWidth|获取图层的`line-gap-width`绘制属性。| |getLineOffset|获取图层的`line-offset`绘制属性。| |getLineBlur|获取图层的`line-blur`绘制属性。| |getLineDasharray|获取图层的`line-dasharray`绘制属性。| |getLinePattern|获取图层的`line-pattern`绘制属性。| |getLineGradient|获取图层的`line-gradient`绘制属性。| |getCircleRadius|获取图层的`circle-radius`绘制属性。| |getCircleColor|获取图层的`circle-color`绘制属性。| |getCircleBlur|获取图层的`circle-blur`绘制属性。| |getCircleOpacity|获取图层的`circle-opacity`绘制属性。| |getCircleTranslate|获取图层的`circle-translate`绘制属性。| |getCircleTranslateAnchor|获取图层的`circle-translate-anchor`绘制属性。| |getCirclePitchScale|获取图层的`circle-pitch-scale`绘制属性。| |getCirclePitchAlignment|获取图层的`circle-pitch-alignment`绘制属性。| |getCircleStrokeWidth|获取图层的`circle-stroke-width`绘制属性。| |getCircleStrokeColor|获取图层的`circle-stroke-color`绘画属性。| |getCircleStrokeOpacity|Getsthe`circle-stroke-opacity`绘图属性.| |getHeatmapRadius|Getsthe`heatmap-radius`绘图属性.| |getHeatmapWeight|Getsthe`heatmap-weight`绘图属性.| |getHeatmapIntensity|Getsthe`heatmap-intensity`绘图属性.| |getHeatmapColor|Getsthe`heatmap-color`绘图属性.| |getHeatmapOpacity|Getsthe`heatmap-opacity`绘图属性.| |getIconOpacity|Getsthe`icon-opacity`绘图属性.| |getIconColor|Getsthe`icon-color`绘图属性.| |getIconHaloColor|Getsthe`icon-halo-color`绘图属性.| |getIconHaloWidth|Getsthe`icon-halo-width`绘图属性.| |getIconHaloBlur|Getsthe`icon-halo-blur`绘图属性.| |getIconTranslate|Getsthe`icon-translate`绘图属性.| |getIconTranslateAnchor|Getsthe`icon-translate-anchor`绘图属性.| |getTextOpacity|Getsthe`text-opacity`绘图属性.| |getTextColor|Getsthe`text-color`绘图属性.| |getTextHaloColor|Getsthe`text-halo-color`绘图属性.| |getTextHaloWidth|Getsthe`text-halo-width`绘图属性.| |getTextHaloBlur|Getsthe`text-halo-blur`绘图属性.| |getTextTranslate|Getsthe`text-translate`绘图属性.| |getTextTranslateAnchor|Getsthe`text-translate-anchor`绘图属性.| |getRasterOpacity|Getsthe`raster-opacity`绘图属性.| |getRasterHueRotate|Getsthe`raster-hue-rotate`绘图属性.| |getRasterBrightnessMin|获取图层的`raster-brightness-min`绘图属性.| |getRasterBrightnessMax|获取图层的`raster-brightness-max`绘图属性.| |getRasterSaturation|获取图层的`raster-saturation`绘图属性.| |getRasterContrast|获取图层的`raster-contrast`绘图属性.| |getRasterResampling|获取图层的`raster-resampling`绘图属性.| |getRasterFadeDuration|获取图层的`raster-fade-duration`绘图属性.| |getHillshadeIlluminationDirection|获取图层的`hillshade-illumination-direction`绘图属性.| |getHillshadeIlluminationAnchor|获取图层的`hillshade-illumination-anchor`绘图属性.| |getHillshadeExaggeration|获取图层的`hillshade-exaggeration`绘图属性.| |getHillshadeShadowColor|获取图层的`hillshade-shadow-color`绘图属性.| |getHillshadeHighlightColor|获取图层的`hillshade-highlight-color`绘图属性.| |getHillshadeAccentColor|获取图层的`hillshade-accent-color`绘图属性.| |getBackgroundColor|获取图层的`background-color`绘图属性.| |getBackgroundPattern|获取图层的`background-pattern`绘图属性.| |getBackgroundOpacity|获取图层的`background-opacity`绘图属性.| |getVisibility|获取图层的`visibility`布局属性.| |getFillSortKey|获取图层的`fill-sort-key`布局属性.| |getCircleSortKey|获取图层的`circle-sort-key`布局属性.| |getLineCap|获取图层的`line-cap`布局属性.| |getLineJoin|获取图层的`line-join`布局属性.| |getLineMiterLimit|获取图层的`line-miter-limit`布局属性.| |getLineRoundLimit|获取图层的`line-round-limit`布局属性.| |getLineSortKey|获取图层的`line-sort-key`布局属性.| |getSymbolPlacement|获取图层的`symbol-placement`布局属性.| |getSymbolSpacing|获取图层的`symbol-spacing`布局属性。| |getSymbolAvoidEdges|获取图层的`symbol-avoid-edges`布局属性。| |getSymbolSortKey|获取图层的`symbol-sort-key`布局属性。| |getSymbolZOrder|获取图层的`symbol-z-order`布局属性。| |getIconAllowOverlap|获取图层的`icon-allow-overlap`布局属性。| |getIconIgnorePlacement|获取图层的`icon-ignore-placement`布局属性。| |getIconOptional|获取图层的`icon-optional`布局属性。| |getIconRotationAlignment|获取图层的`icon-rotation-alignment`布局属性。| |getIconSize|获取图层的`icon-size`布局属性。| |getIconTextFit|获取图层的`icon-text-fit`布局属性。| |getIconTextFitPadding|获取图层的`icon-text-fit-padding`布局属性。| |getIconImage|获取图层的`icon-image`布局属性。| |getIconRotate|获取图层的`icon-rotate`布局属性。| |getIconPadding|获取图层的`icon-padding`布局属性。| |getIconKeepUpright|获取图层的`icon-keep-upright`布局属性。| |getIconOffset|获取图层的`icon-offset`布局属性。| |getIconAnchor|获取图层的`icon-anchor`布局属性。| |getIconPitchAlignment|获取图层的`icon-pitch-alignment`布局属性。| |getTextPitchAlignment|获取图层的`text-pitch-alignment`布局属性。| |getTextRotationAlignment|取图层的`text-rotation-alignment`布局属性。| |getTextField|获取图层的“文本字段”布局属性。| |getTextFont|获取图层的`text-font`布局属性。| |getTextSize|获取图层的`text-size`布局属性。| |getTextMaxWidth|获取图层的`text-max-width`布局属性。| |getTextLineHeight|获取图层的`text-line-height`布局属性。| |getTextLetterSpacing|获取图层的`text-letter-spacing`布局属性。| |getTextJustify|获取图层的`text-justify`布局属性。| |getTextRadialOffset|获取图层的`text-radial-offset`布局属性。| |getTextVariableAnchor|获取图层的`text-variable-anchor`布局属性。| |getTextAnchor|获取图层的`text-anchor`布局属性。| |getTextMaxAngle|获取图层的`text-max-angle`布局属性。| |getTextWritingMode|获取图层的`text-writing-mode`布局属性。| |getTextRotate|获取图层的`text-rotate`布局属性。| |getTextPadding|获取图层的`text-padding`布局属性。| |getTextKeepUpright|获取图层的`text-keep-upright`布局属性。| |getTextTransform|获取图层的`text-transform`布局属性。| |getTextOffset|获取图层的`text-offset`布局属性。| |getTextAllowOverlap|获取图层的`text-allow-overlap`布局属性。| |getTextIgnorePlacement|获取图层的`text-ignore-placement`布局属性。| |getTextOptional|获取图层的`text-optional`布局属性。| --- # 地图常用方法 ## 初始化地图 使用 `vjmap.Map` 对象初始化一个 地图。 ```js // 新建地图服务对象,传入服务地址和token let svc = new vjmap.Service(env.serviceUrl, env.accessToken) // 打开地图 let res = await svc.openMap({ mapid: env.exampleMapId, // 地图ID,(请确保此ID已存在,可上传新图形新建ID) mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开 style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式 }) if (res.error) { console.error(res.error) } // 获取地图的范围 let mapExtent = vjmap.GeoBounds.fromString(res.bounds); // 建立坐标系 let prj = new vjmap.GeoProjection(mapExtent); // 新建地图对象 let map = new vjmap.Map({ container: 'map', // container ID style: svc.rasterStyle(), // 栅格瓦片样式 center: prj.toLngLat(mapExtent.center()), // 中心点 zoom: 2, renderWorldCopies: false }); ``` ## 地图加载完成 创建地图对象后,开始加载地图资源,地图资源加载完成后触发 load 事件。 ```js map.on("load", function() { // 地图资源加载完成后触发 // 增加自定义数据源、自定义图层 }); ``` 或 ```js await map.onLoad(); ``` ## 销毁地图对象 调用 remove 方法来销毁地图,该方法执行后,地图对象被注销,内存释放,地图容器被清空。 ```js // 销毁地图,并清空地图容器 map.remove(); ``` ## 设置视图 ### 基本视图设置 **`setCenter([lng, lat])`** 设置地图中心。 ```js // 使用CAD坐标 map.setCenter(map.toLngLat([1000, 2000])); // 直接使用经纬度 map.setCenter([120.1551, 30.2741]); ``` **`setZoom(zoomLevel)`** 设置地图缩放级别。 ```js map.setZoom(14); ``` **`setZoomAndCenter`** 同时设置地图中心点和缩放级别: ```js // 同时传入缩放级别和中心点经纬度 map.setZoomAndCenter(12, [116.46, 39.92]); ``` **`setWheelZoomRate`** 设置地图整级缩放或非整级缩放: ```js // 设置地图整级缩放 map.scrollZoom.setWheelZoomRate(1); //设置鼠标滚轮滚动一下的缩放比例 map.scrollZoom.setZoomRate(1); //设置触摸屏的缩放比例,最大值为1 // 设置地图非整级缩放 (默认1/450) // map.scrollZoom.setWheelZoomRate(1 / 600); //速率值越小,地图的缩放越细腻 ``` **`setBearing(angle)`** 设置地图的旋转角度。 ```js map.setBearing(90); ``` **`setPitch(pitch)`** 设置地图的倾斜角度。 ```js map.setPitch(45); ``` **`jumpTo(options)`** 瞬间跳转到指定地图状态(无动画)。 ```js map.jumpTo({ center: [20.1551, 30.2741], zoom: 14 }); ``` **`flyTo(options)`** 使用动画飞行过渡到新视图状态。 ```js map.flyTo({ center: [20.1551, 30.2741], zoom: 14 }); ``` **`panBy([x, y], options)`** 通过像素平移地图。 ```js map.panBy([100, 0]); ``` **`fitBounds(bounds, options)`** 自动调整地图视图以适应边界框。 ```js // 根据CAD几何坐标范围 let bounds = vjmap.GeoBounds.fromString("100,200,300,400"); map.fitMapBounds(bounds); ``` ```js // 根据经纬度 map.fitBounds([[120.0, 30.0], [121.0, 31.0]]); ``` **`rotateTo(bearing, options)`** 设置地图旋转到特定角度。 ```js map.rotateTo(90); ``` **`resetNorth(options)`** 将地图旋转恢复到正北方向。 ```js map.resetNorth(); ``` ### 视图限制 **`setMinZoom(minZoom)`** 设置地图允许的最小缩放级别。 ```js map.setMinZoom(5); ``` **`setMaxZoom(maxZoom)`** 设置地图允许的最大缩放级别。 ```js map.setMaxZoom(18); ``` **`setMaxBounds(bounds)`** 限制地图的最大边界,禁止拖动超出此范围。 ```js map.setMaxBounds([[120.0, 30.0], [121.0, 31.0]]); ``` ## 地图事件 **`on(eventType, listener)`** 监听地图事件,如点击、移动、缩放等。 ```js map.on('click', (e) => { console.log('点击位置:', e.lngLat); }); ``` **`off(eventType, listener)`** 移除特定事件的监听器。 ```js map.off('click', listenerFunction); ``` **`once(eventType, listener)`** 只监听一次特定事件。 ```js map.once('click', (e) => { console.log('仅一次点击事件:', e.lngLat); }); ``` **`onLoad(listener)`** 监听地图加载完成事件。 ```js map.on('load', () => { console.log('地图已加载'); }); ``` **`onMove(listener)`** 监听地图平移事件。 ```js map.on('move', () => { console.log('地图正在移动'); }); ``` **`onZoom(listener)`** 监听地图缩放事件。 ```js map.on('zoom', () => { console.log('地图正在缩放'); }); ``` ## 添加控件 **`addControl(control, position)`** 添加控件(缩放、导航等)。 ```js const nav = new vjmap.NavigationControl(); map.addControl(nav, 'top-right'); ``` **`removeControl(control)`** 移除控件。 ```js map.removeControl(nav); ``` **`addScaleControl(options)`** 添加比例尺控件。 ```js map.addControl(new vjmap.ScaleControl()); ``` **`addFullScreenControl()`** 添加全屏控件。 ```js map.addControl(new vjmap.FullscreenControl()); ``` ## 添加图层 **`addLayer(layer)`** 添加图层。 ```js map.addLayer({ id: 'circle-layer', type: 'circle', source: 'points', paint: { 'circle-radius': 10, 'circle-color': '#007cbf' } }); ``` **`addImage(name, image)`** 添加自定义图像,用于图层。 ```js map.addImage('custom-icon', 'path-to-image.png'); ``` **`addSource(sourceId, source)`** 添加自定义数据源。 ```js map.addSource('points', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } }); ``` **`getLayer(layerId)`** 获取指定的图层对象。 ```js const layer = map.getLayer('custom-layer'); ``` ## 更新和移除图层 **`setPaintProperty(layerId, property, value)`** 动态设置图层绘制属性。 ```js map.setPaintProperty('circle-layer', 'circle-radius', 15); ``` **`setLayoutProperty(layerId, property, value)`** 动态设置图层布局属性。 ```js map.setLayoutProperty('circle-layer', 'visibility', 'none'); ``` **`removeLayer(layerId)`** 移除指定的图层。 ```js map.removeLayer('custom-layer'); ``` **`moveLayer(layerId, beforeId)`** 改变图层的绘制顺序,将图层移动到另一个图层之前。 ```js map.moveLayer('circle-layer', 'another-layer'); ``` ## 动态更新数据源 **`getSource(sourceId)`** 获取指定的数据源对象。 ```js const source = map.getSource('points'); ``` **`setData(sourceId, data)`** 更新指定的数据源中的数据。 ```js map.getSource('points').setData(newGeoJsonData); ``` **`removeSource(sourceId)`** 移除指定的数据源。 ```js map.removeSource('points'); ``` **`removeSourceEx(sourceId)`** 移除指定的数据源和数据源相关的图层。 ```js map.removeSource('points'); ``` ## 地图样式管理 **`setStyle(style)`** 设置地图样式。 ```js map.setStyle(style); ``` **`getStyle()`** 获取当前地图样式。 ```js const style = map.getStyle(); ``` 39. **`isStyleLoaded()`** 检查地图样式是否加载完成。 ```js if (map.isStyleLoaded()) { console.log('样式已加载'); } ``` ## 获取地图信息 **`getCenter()`** 获取当前地图中心点。 ```js const center = map.getCenter(); console.log(center); // {lng, lat} ``` **`getZoom()`** 获取当前地图缩放级别。 ```js const zoom = map.getZoom(); ``` **`getBearing()`** 获取当前地图的旋转角度。 ```js const bearing = map.getBearing(); ``` **`getPitch()`** 获取当前地图的倾斜角度。 ```js const pitch = map.getPitch(); ``` **`getBounds()`** 获取当前地图的边界框(地图四个角的坐标)。 ```js const bounds = map.getBounds(); ``` **`getMaxZoom()`** 获取地图允许的最大缩放级别。 ```js const maxZoom = map.getMaxZoom(); ``` **`getMinZoom()`** 获取地图允许的最小缩放级别。 ```js const minZoom = map.getMinZoom(); ``` **`resize()`** 重新调整地图大小,在地图容器大小发生变化时使用。 ```js map.resize(); ``` **`toLngLat(point)`** 将CAD坐标转经纬度坐标。 ```js const lnglat = map.toLngLat([1000, 3000]); ``` **`fromLngLat(point)`** 将经纬度坐标转换为CAD坐标。 ```js const point = map.fromLngLat([120.1551, 30.2741]); ``` **`project(lngLat)`** 将经纬度坐标转换为像素坐标。 ```js const point = map.project([120.1551, 30.2741]); ``` **`unproject(point)`** 将像素坐标转换为经纬度坐标。 ```js const lngLat = map.unproject([200, 300]); ``` **`remove()`** 销毁地图对象并释放资源。 ```js map.remove(); ``` ## 其他常用方法 **`getCanvas()`** 获取地图的 canvas 元素。 ```js const canvas = map.getCanvas(); ``` 将地图导出为png格式的base64图片 ```js let map = new vjmap.Map({ container: 'map', // DIV容器ID preserveDrawingBuffer: true, // 如果true,则可以使用 将地图的画布导出为 PNG map.getCanvas().toDataURL()。这是false默认的性能优化。(可选,默认false) ... }); console.log(map.getCanvas().toDataURL()) ``` **`getContainer()`** 获取地图的容器元素。 ```js const container = map.getContainer(); ``` **`queryRenderedFeatures([point], options)`** 查询指定区域内的所有已渲染的要素。 ```js const features = map.queryRenderedFeatures([200, 300], { layers: ['custom-layer'] }); ``` **`getPadding()`** 获取地图当前的内边距(padding)。 ```js const padding = map.getPadding(); ``` **`setPadding(padding)`** 设置地图内边距。 ```js map.setPadding({ top: 50, bottom: 50 }); ``` **`getFreeCameraOptions()`** 获取地图的自由相机选项。 ```js const cameraOptions = map.getFreeCameraOptions(); ``` **`setFreeCameraOptions(options)`** 设置地图的自由相机选项。 ```js map.setFreeCameraOptions(cameraOptions); ``` **`loadImage(url, callback)`** 从 URL 加载图像并在回调中返回。 ```js map.loadImage('path-to-image.png', (error, image) => { if (error) throw error; map.addImage('custom-icon', image); }); ``` **`removeImage(name)`** 移除已添加的图像。 ```js map.removeImage('custom-icon'); ``` **`listImages()`** 列出所有已添加的图像名称。 ```js const images = map.listImages(); console.log(images); ``` **`hasImage(name)`** 检查地图中是否已存在指定名称的图像。 ```js const hasImage = map.hasImage('custom-icon'); ``` **`addCustomLayer(layer)`** 添加自定义 WebGL 图层。 ```js map.addLayer({ id: 'custom-webgl-layer', type: 'custom', renderingMode: '3d', onAdd: (map, gl) => { // 初始化 WebGL 资源 }, render: (gl, matrix) => { // 绘制 WebGL 图层 } }); ``` **`hasLayer(layerId)`** 检查地图中是否已存在指定 ID 的图层。 ```js const hasLayer = map.hasLayer('custom-layer'); ``` **`getLayerVisibility(layerId)`** 获取图层的可见性。 ```js const visibility = map.getLayoutProperty('custom-layer', 'visibility'); ``` **`getLight()`** 获取当前灯光设置。 ```js const light = map.getLight(); ``` **`setLight(lightOptions)`** 设置灯光。 ```js map.setLight({ anchor: 'viewport', color: '#ffffff', intensity: 0.5 }); ``` **`getFeatureState(featureId)`** 获取特定要素的状态。 ```js const state = map.getFeatureState({ source: 'my-source', id: featureId }); ``` **`setFeatureState(featureId, state)`** 设置特定要素的状态。 ```js map.setFeatureState({ source: 'my-source', id: featureId }, { selected: true }); ``` **`removeFeatureState(featureId)`** 移除特定要素的状态。 ```js map.removeFeatureState({ source: 'my-source', id: featureId }); ``` **`getFilter(layerId)`** 获取图层的过滤条件。 ```js const filter = map.getFilter('my-layer'); ``` **`setFilter(layerId, filter)`** 设置图层的过滤条件。 ```js map.setFilter('my-layer', ['==', 'property', 'value']); ``` **`hasClass(className)`** 检查地图容器是否有指定的 CSS 类。 ```js const hasClass = map.getContainer().classList.contains('my-class'); ``` **`addClass(className)`** 为地图容器添加 CSS 类。 ```js map.getContainer().classList.add('my-class'); ``` **`removeClass(className)`** 从地图容器移除指定的 CSS 类。 ```js map.getContainer().classList.remove('my-class'); ``` **`toggleClass(className)`** 切换地图容器的指定 CSS 类。 ```js map.getContainer().classList.toggle('my-class'); ``` **`setCursor(cursorStyle)`** 设置地图的光标样式。 ```js map.getCanvas().style.cursor = 'pointer'; ``` **`resetCursor()`** 重置地图的光标样式。 ```js map.getCanvas().style.cursor = ''; ``` **`isMoving()`** 检查地图是否正在移动。 ```js const moving = map.isMoving(); ``` **`isZooming()`** 检查地图是否正在缩放。 ```js const zooming = map.isZooming(); ``` --- # 切换地图 ## 切换图层(栅格瓦片) `栅格瓦片切换图层`是通过`后台修改图层样式`来达到切换效果 [切换图层(栅格)](https://vjmap.com/demo/#/demo/map/service/14switchLayerRaster) ```ts // 切换cad图层 export const switchCadLayers = async (map: Map, layers: {name: string; isOff: boolean}[]) => { let svc = map.getService(); // 如果是栅格瓦片 await map.switchLayers(svc, layers.reduce((sum, val) => { if (!val.isOff) { // @ts-ignore sum.push(val.name); } return sum; }, [])) } ``` ## 切换图层(矢量瓦片) `矢量瓦片切换图层`是通过`前端修改样式`来达到切换效果 [切换图层(矢量)](https://vjmap.com/demo/#/demo/map/service/14switchLayerVector) ```js // 切换cad图层 export const switchCadLayers = async (map: Map, layers: {name: string; isOff: boolean}[]) => { let svc = map.getService(); // 如果是矢量瓦片 let onLayers = layers.reduce((sum, val) => { if (!val.isOff) { // @ts-ignore sum.push(val.name); } return sum; }, []); // 要开的图层 switchVectorLayers(map, onLayers) } // 矢量瓦片切换图层 onLayers 要显示的图层名称数组 export const switchVectorLayers = (map: Map, onLayers: string[]) => { // 通过图层名称来查找图层id const getLayerIdByName = (name: string) => { return svc.getMapLayers().findIndex(layer => layer.name === name) } // 增加实体图层判断条件,只要满足任何一种类型即可,如 ['图层一','图层二'] const conditionLayers = (layers: string[], inputIsIndex: boolean) => { if (!Array.isArray(layers)) layers = [layers];// 先转成数组,再统一用数组去算吧 let layerIndexs: number[] = []; if (!inputIsIndex) { // 如果输入的不是图层索引,是图层名称,则需要转成图层索引 layerIndexs = layers.map(layer => getLayerIdByName(layer)); // 把图层名称转为图层索引,在矢量瓦片中图层是用索引来表示的 } if (layerIndexs.length == 0) { layerIndexs = [-1]; // 如果全部关闭,防止数组为空,加一个不存在的 } return [ "match", ['get', 'layer'], layerIndexs, true, false ] } // 获取之前的默认的矢量图层样式,然后在当前样式中,对矢量图层的数据进行图层进行过滤 let style = map.getStyle(); let svc = map.getService(); let vectorStyle = svc.vectorStyle(); let vectorLayerIds = vectorStyle.layers.map(layer => layer.id); let filter = conditionLayers(onLayers, false); for (let i = 0; i < style.layers.length; i++) { if (vectorLayerIds.includes(style.layers[i].id)) { // @ts-ignore style.layers[i].filter = filter; // 设置过滤条件 } } map.setStyle(style); } ``` ## 切换图形(地图对象不变) 通过调用 `switchMap` 来切换图形。 [切换图形(地图对象不变)](https://vjmap.com/demo/#/demo/map/service/14switchMap) ```ts /** * 切换地图,请确保之前打开过地图,开始前触发`mapopenstart`事件,打开后触发`mapopenfinish`事件 * @param param 打开选项 * @param isKeepOldLayers 是否保留之前的图层数据,默认false * @param isVectorStyle 是否为矢量样式 * @param isSetCenter 是否默认设置为地图中心点 * @param isFitBounds 默认是否缩放 * @param source 数据源id,如果修改了默认的栅格或瓦片source,需要传此参数 */ switchMap(param: IOpenMapParam, isKeepOldLayers?:boolean, isVectorStyle?:boolean, isSetCenter?: boolean, isFitBounds?: boolean, source?: string): Promise; ```
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: "sys_hello", // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapLightStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    showError(res.error)
    return;
}
// 获取地图范围
let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapBounds);
//
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapBounds.center()), // 设置地图中心点
    zoom: 1, // 设置地图缩放级别
    pitch: 0,
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();
const mapids = ["sys_hello", "sys_world", "sys_cad2000", "sys_zp"]
let curIdx = 1;
let curMapId = mapids[0]
const switchMap = async () => {
    curMapId = mapids[curIdx++ % mapids.length]
    await map.switchMap({
        mapid: curMapId,
        style: vjmap.openMapDarkStyle()
    });
}
setInterval(() => switchMap(), 3000)


## 切换图形(地图对象改变, 同一个div元素) 这个方法是先通过`map.destory()`销毁之前的`map`对象,再创建一个新的`map`对象关联之前的div元素 [切换图形(地图对象改变,同个div)](https://vjmap.com/demo/#/demo/map/service/14switchMapDestory) ```js // 要切换的mapid数组 const mapids = ["sys_zp", "sys_hello", "sys_world", "sys_cad2000"] let curMapIdIndex = 0; /** * 创建地图对象 * @param serviceUrl 服务地址接口 * @param mapId 地图ID * @param mapOpenWay 打开方式,缺省vjmap.MapOpenWay.GeomRender * @param isRaster true为栅格,false为矢量 * @return {Promise} */ async function newMap(serviceUrl, mapId, mapOpenWay = vjmap.MapOpenWay.GeomRender, isRaster = true) { // 地图服务对象 let svc = new vjmap.Service(serviceUrl, env.accessToken) // 打开地图 let res = await svc.openMap({ mapid: mapids[curMapIdIndex], // 地图ID mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开 style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式 }) if (res.error) { // 如果打开出错 throw Error(res.error) } // 获取地图范围 let mapExtent = vjmap.GeoBounds.fromString(res.bounds); // 根据地图范围建立几何投影坐标系 let prj = new vjmap.GeoProjection(mapExtent); // 如果为内存方式打开,则只能为栅格方式 if (mapOpenWay === vjmap.MapOpenWay.Memory) { isRaster = true; } // 地图样式 let style = isRaster ? svc.rasterStyle() : svc.vectorStyle(); // 地图对象 let map = new vjmap.Map({ container: 'map', // DIV容器ID style: style, // 样式,这里是栅格样式 center: prj.toLngLat(mapExtent.center()), // 设置地图中心点 zoom: 2, // 设置地图缩放级别 renderWorldCopies: false // 不显示多屏地图 }); // 关联服务对象和投影对象 map.attach(svc, prj); // 根据地图本身范围缩放地图至全图显示 map.fitMapBounds(); // 使地图能选择点击拾取高亮 if (isRaster) { map.enableLayerClickHighlight(svc, e => { if (!e) return; let content = `type: ${e.name}, id: ${e.objectid}, layer: ${e.layerindex}` e && map.logInfo(content); }) } else { // 有高亮状态(鼠标在地图元素上时,会高亮) map.enableVectorLayerHoverHighlight((event, feature, layer) => { if (!feature) return; // 点击高亮实体回调事件 let content = `event: ${event}; feature: ${feature.id}; layer: ${layer}` map.logInfo(content) }) } await map.onLoad(); map.logInfo("地图加载完成", "success") // 给地图增加一些数据图层做为测试 await addDataLayer(map); return map; } // 给地图增加一些数据图层做为测试 const addDataLayer = async (map) => { // 导航条控件 map.addControl(new vjmap.NavigationControl()); // 鼠标移动显示坐标位置控件 map.addControl(new vjmap.MousePositionControl({showZoom: true})); let bounds = map.getGeoBounds() ;// 获取地图范围 // 增加marker for(let n = 0; n < 3; n++) { let marker = new vjmap.Marker({ color: vjmap.randomColor(), draggable: true }).setLngLat(map.toLngLat(bounds.randomPoint())) marker.addTo(map); } // 增加一个随机位置的popup let popupPoint = bounds.randomPoint(); let popup = new vjmap.Popup({ closeOnClick: false }) .setLngLat(map.toLngLat(popupPoint)) .setHTML(`

我是Popup

`) .addTo(map); // 创建polyline对象,包含三条随机线 let polylineData = []; for(let i = 0; i < 3; i++) { // 每条线有2-4个随机点 const points = bounds.randomPoints(2, 4); polylineData.push({ points: map.toLngLat(points), properties: { name: `线 ${i+1}`, color: vjmap.randomColor() } }); } // 创建polyline对象 let polylines = new vjmap.Polyline({ data: polylineData, lineColor: ['get', 'color'], lineWidth: 3, lineOpacity: 0.8, isHoverPointer: true }); polylines.addTo(map); // 添加点击事件 polylines.clickLayer(e => { map.logInfo(`您点击了: ${e.features[0].properties.name}`); }); for (let i = 0; i < 4; i++) { // await map.loadImageEx("icon" + i, env.assetsPath + `images/sensor${i + 1}.png`); await map.loadImageEx("icon" + i, `https://vjmap.com/static/assets/images/sensor${i + 1}.png`); } const geoDatas = [] for (let i = 0; i < 10; i++) { const pt = bounds.randomPoint(); const data = { point: map.toLngLat(pt), properties: { name: `ID:${i + 1}`, icon: "icon" + vjmap.randInt(0, 3) } } geoDatas.push(data); } const symbols = new vjmap.Symbol({ data: geoDatas, // 图标名来源于geojson属性中的icon字段 iconImage: ['get', 'icon'], // 二级以下全部用icon1图标,二级以上用自己属性中的图标名 //iconImage: ["step", ["zoom"], "icon1", 2, ['get', 'icon']], iconOffset: [0, -12], // 鼠标hover时设置透明度0.8,不悬浮1.0 iconOpacity: ['case', ['to-boolean', ['feature-state', 'hover']], 0.8, 1.0], textField: ['get', 'name'], textFont: ['Arial Unicode MS Regular'], textSize: 14, textColor: '#FFA0FD', textOffset: [0, 0.5], textAnchor: 'top', iconAllowOverlap: false, textAllowOverlap: false, isHoverPointer: true, isHoverFeatureState: true }); symbols.addTo(map); symbols.clickLayer(e => { if (e.defaultPrevented) return; // 如果事件之前阻止了,则不再执行了 map.logInfo(`您点击了第 ${e.features[0].id} 个,名称为 ${e.features[0].properties.name},的图标`) e.preventDefault(); // 阻止之后的事件执行 }) let geoDatas2 = []; for(let i = 1; i <= 2; i++) { let mapBounds = map.getGeoBounds(i / 10.0); let points = mapBounds.toPointArray(); points.push(points[0]); // 闭合 geoDatas2.push({ points: map.toLngLat(points), id: i, properties: { lineWidth: 8 + i * 2, name: `${i}环` } }); } let arrowAnimateLine = vjmap.createArrowAnimateLineLayer(map, geoDatas2, { arrowFillColor: vjmap.randomColor(), arrowStrokeColor: "#fff", arrowStrokeWidth: 6, canvasWidth: 128, canvasHeight: 32, arrowWidth: 16, frameCount: 4, lineWidth: ['get', 'lineWidth'], lineOpacity: ['case', ['to-boolean', ['feature-state', 'hover']], 0.6, 1.0], isHoverPointer: true, isHoverFeatureState: true }); arrowAnimateLine.polyline.clickLayer(e => map.logInfo(`您点击了第 ${e.features[0].id} 个,名称为 ${e.features[0].properties.name}`)) arrowAnimateLine.polyline.hoverPopup(f => `

ID: ${f.properties.name}`, { anchor: 'bottom' }); // 获取绘制图层 const draw = map.getDrawLayer(); // 初始加入一个多边形 draw.set(vjmap.createPolygonGeoJson(map.toLngLat(bounds.randomPoints(3, 3)))); } // 创建地图 let map = await newMap(env.serviceUrl, env.exampleMapId, vjmap.MapOpenWay.GeomRender, true) setInterval(async () => { map.logInfo("销毁地图") map.destory(); // 销毁地图 curMapIdIndex = (curMapIdIndex + 1) % mapids.length; // 再创建一个新的map对象 map = await newMap(env.serviceUrl, env.exampleMapId, vjmap.MapOpenWay.GeomRender, true) }, 10000) ``` ## 切换图形(地图对象改变, 关联不同div元素) 这个方法是清空之前地图对象关联的div,再重新创建一个新的div,与创建的新的`map`对象相关联 [切换图形(地图对象改变,不同div)](https://vjmap.com/demo/#/demo/map/service/14switchMapNew) ```js // 创建一个新的div对象 const createNewMapDivId = ()=> { // 先清空之前的 let parentDiv = document.getElementById("map"); parentDiv.innerHTML = ""; let newMapDiv = document.createElement("div"); newMapDiv.id = vjmap.RandomID(6); newMapDiv.style.position = 'absolute'; newMapDiv.style.width = "100%"; newMapDiv.style.height = "100%"; parentDiv.appendChild(newMapDiv); return newMapDiv.id; } const openNewMap = async (mapid, isVector) => { // 打开地图 let res = await svc.openMap({ mapid: mapid, mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开 style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式 }) if (res.error) { message.error(res.error) return; } // 获取地图的范围 let mapExtent = vjmap.GeoBounds.fromString(res.bounds); // 建立坐标系 let prj = new vjmap.GeoProjection(mapExtent); // 新建地图对象 let map = new vjmap.Map({ container: createNewMapDivId(), // 这里要创建一个新的div对象,与新的map对象相绑定 style: isVector ? svc.vectorStyle() : svc.rasterStyle(), // 栅格瓦片样式 center: prj.toLngLat(mapExtent.center()), // 中心点 zoom: 2, renderWorldCopies: false }); // 地图关联服务对象和坐标系 map.attach(svc, prj); // 使地图全部可见 map.fitMapBounds(); } let curMapId; const switchMap = async mapid => { if (curMapId == mapid) return; // 如果一样,不用管了 await openNewMap(mapid); curMapId = mapid; } await switchMap("sys_zp"); ``` ::: tip 提示 地图销毁前记得调用 `map.destory()` [销毁地图对象](https://vjmap.com/demo/#/demo/map/map/closeMap) ::: --- # 地图数据源 ## 概述 --- 数据源定义地图上能够被应用的数据,每个数据源都必须有唯一的 ID 标识,不能重复。 每个数据源都必须要有指定一个数据源类型,数据源类型范围如下: ``` ["vector", "raster", "geojson", "image", "video"]; ``` ### vector 类型说明 --- vector 类型表示矢量数据类型,数据提供的形式为矢量瓦片服务的形式,数据返回格式为 protubuf 格式。 详细说明如下: ```js map.addSource("Source1", { /*Source1代表数据源ID,是唯一的,不能重复*/ type: "vector" /*数据源类型*/, tiles: ["http://yourwebsite/{z}/{x}/{y}.mvt"] /*数据源请求地址URL,可以是多个*/, }); ``` ## raster 类型说明 --- raster 类型表示栅格类型,数据提供的形式为瓦片服务的形式,数据返回栅格图片。 详细说明如下: ```js map.addSource("Source1", { /*Source1代表数据源ID,是唯一的,不能重复*/ type: "raster" /*数据源类型*/, tileSize: 256 /*返回栅格图片尺寸,如果不添加该参数,默认为512*/, tiles: ["http://yourwebsite/{z}/{x}/{y}.png"] /*数据源请求地址URL,可以是多个*/, }); ``` ## geojson 类型说明 --- geojson 类型表示 geojson 类型,数据提供的形式为 geojson 结构的数据,数据一次性加载。 详细说明如下: 示例一: ```js map.addSource("jsonSource", { /*jsonSource代表数据源ID,是唯一的,不能重复*/ "type": "geojson", /*数据源类型*/ "data": "./data.json" /*data参数为geojson格式的数据,支持两种结构:1、url形式,返回的结果为geojson格式;2、具体的geojson格式的数据*/ }); ``` 示例二: ```js var jsonData = {"type": "FeatureCollection", "features": [{ "type": "Feature", "geometry": { "type": "Point", "coordinates": [116.46, 39.92] }, "properties": { "title": "大学" } }] }; map.addSource("jsonSource", { "type": "geojson", "data": jsonData }); ``` geojson 数据源数据内容更新,调用 setData 方法: ```js map.addSource("jsonSource", { "type": "geojson", "data": {"type": "FeatureCollection","features": []} /*空数据*/ }); // 某些业务场景先定义geojson数据源,数据内容设为空,添加具体的图层,当业务数据组织好后,通过调用setData方法更新数据 map.getSource('jsonSource').setData(json) /*参数json可以是url或具体的geojson数据*/ ``` ## image 类型说明 --- image 类型表示图像类型,图像格式可以为 gif、png、jpg、jpeg 形式。 详细说明如下: ```js map.addSource("imageSource", { /*imageSource代表数据源ID,是唯一的,不能重复*/ "type": "image", /*数据源类型*/ "url": "./images/flash.gif", /*图像url地址*/ "coordinates": [[106.45, 29.93],[106.47, 29.93],[106.47, 29.91],[106.45, 29.91]] /*图像在地图上的坐标点边界,边界值为顺时针方式,依次为:左上、右上、右下、左下。*/ }); ``` ## video 类型说明 --- video 类型表示视频类型,视频格式可以为 mp4 等形式。 详细说明如下: ```js map.addSource("videoSource", { /*videoSource代表数据源ID,是唯一的,不能重复*/ "type": "video", /*数据源类型*/ "urls": ["http://yourwebsite/assets/v.mp4"], /*视频请求地址URL,可以是多个*/ "coordinates": [[106.45, 29.93],[106.47, 29.93],[106.47, 29.91],[106.45, 29.91]] /*图像在地图上的坐标点边界,边界值为顺时针方式,依次为:左上、右上、右下、左下。*/ }); ``` --- # 地图图层 ## 概述 --- 图层定义数据源中的数据内容在地图上的展现方式,每个图层都必须有唯一的 ID 标识,不能重复。 每个图层都必须要有指定一个数据源类型,图层类型范围如下: ```js 背景类型:'background' 面几何对应类型:'fill'、'extrusion' 栅格图片对应类型:'raster' 线几何对应类型:'line' 点几何对应类型:'symbol'、'circle'、'heatmap' ``` ## 图层规格 --- 图层样式设置的结构为 JSON 结构,根级别结构如下: ```js { "id": "road-layer-1", "type": "line", "source": "RoadSource", "source-layer": "Road", "layout": {...}, "paint": {...}, "minzoom": 6, "maxzoom": 17.5, "filter": [...] } ``` 字段定义说明如下: **id**:图层 ID,必填项;说明:(值唯一,不能重复) **type**:图层渲染类型,必填项;(值域范围,参考本章「概述」) **source**:所使用的数据源 ID,说明:(当图层类型不为 background 时,该值为必填项) **source-layer**:所使用的 vector 数据源中的图层标识,说明:(当数据源类型为 vector 时,该值为必填项;其它数据源类型,去除该参数) **layout**:布局属性 **paint**:绘制属性 **minzoom**:图层可展示的最小缩放等级值,选填项;说明:(值为小数或整数,值域范围\[0,24\],不设置该参数则地图允许的最小缩放等级内都可显示) **maxzoom**:图层可展示的最大缩放等级值,选填项;说明:(值为小数或整数,值域范围\[0,24\],不设置该参数则地图允许的最大缩放等级内都可显示) **filter**:所使用数据源的 features 数据的过滤条件,选填项;说明:(当数据源 features 数据信息和 filter 条件匹配时图层才显示) ### filter 规格 --- fliter 表示从所有图层过滤出特定特征的图层,有以下几种过滤形式: **1、存在过滤:** 表达式形式:\[way, key\] ```js 存在过滤主要有“has”、“!has”两种形式 ["has", "count"],表示过滤出存在属性"count"的所有的feature数据 ["!has","count"],表示过滤出不存在属性"count"的所有的feature数据 ``` **2、比较过滤:** 表达式形式:\[way, key, value\] ```js 比较过滤有等于“==”、大于“>”、小于“<”、不等于“!=”、大于等于“>=”、小于等于“<=”几种形式 ["==", "count", "1000"],表示过滤出属性"count"值为1000的feature数据, // 注意此时数据源name存储的值为"1000"而不是1000 过滤时数据类型是严格匹配的 ``` **3、成员过滤:** 计算形式:\[way, key, v0,v1,…,vn\] ```js 成员过滤主要有"in"、"!in"两种形式 ["in", "name", "point", "fill", "line"], // 表示过滤出属性"name"值为"point", "fill", "line"其中任一一个的feature数据 ``` **4、匹配过滤:** 选择标签值与输入值匹配的输出,如果找不到匹配项,则选择回退值。输入可以是任何表达式(例如["get", "building_type"])。每个标签label必须是: 单个文字值;要么 文字值数组,其值必须是所有字符串或所有数字(例如[100, 101]或["c", "b"])。如果数组中的任何值匹配,则输入匹配,类似于不推荐使用的"in"运算符。 每个标签必须唯一。如果输入类型与标签类型不匹配,则结果将是后备值。 ```js "circle-color": [ 'match', ['get', 'name'], '点1', 'red', '点2', 'yellow', ['点3','点4'], 'blue', '#fff' ], //获取feature's properties 中的name值,根据name值匹配对应的颜色,默认是(没有匹配到)#fff , ['点3','点4'] 表示是label是可以是数组类型, ``` **5、组合过滤:** 计算形式:\[way, f0,f1,…,fn\] ```js 组合过滤主要有等于"all"、"any"、"none"三种形式 "all"表示满足所有过滤条件的数据,"any"表示满足任一一个过滤条件的数据,"none"表示过滤出不满足所有过滤条件的数据 ["all", ["<=", "count", 34], ["like", "code", "101"]] // 表示过滤出属性"count"满足大于等于34,属性"code"值包含101的feature数据 ``` 更多的表达式语法请查看文档 [样式表达式](/guide/expression.html) ### 图层样式操作 --- 图层样式操作常用方法,说明如下: **获取图层布局的一个具体的样式值:** ```js 参数说明:图层id、图层布局参数名称,example: map.getLayoutProperty('my-layer-id', 'visibility') ``` **设置图层布局的一个具体的样式值:** ```js 参数说明:图层id、图层布局参数名称、布局参数值,example: map.setLayoutProperty('my-layer-id', 'visibility', 'none') ``` **获取图层绘制的一个具体的样式值:** ```js 参数说明:图层id、图层绘制参数名称,example: map.getPaintProperty('my-layer-id', 'fill-color') ``` **设置图层绘制的一个具体的样式值:** ```js 参数说明:图层id、图层绘制参数名称、绘制参数值,example: map.setPaintProperty('my-layer-id', 'fill-color', '#faafee') ``` **设置图层的缩放区间:** ```js 参数说明:图层id、最小缩放级别、最大缩放级别,example: map.setLayerZoomRange('my-layer-id', 6, 12) ``` **获取图层的过滤器:** ```js 参数说明:图层id,example: map.getFilter('my-layer-id') ``` **设置图层的过滤器:** ```js 参数说明:图层id、过滤器内容,如果过滤器内容为null或是undefined,将清除图层上所有已有的过滤器,example: map.setFilter('my-layer-id', ['==', 'name', 'myname']) ``` ## 增加图层的三种写法 ::: tip 提示 以下三种写法效果一样,只是封装程度不同 ::: **以增加线图层为例** 线数据示例如下 ```js var geoLines = { "type": "LineString", "properties":{ "prop0": "value0" }, "coordinates": [ [ 10, 30 ], [ 50, 30 ], [ 110, 40 ] ] }; ``` ### 写法一 ::: tip 提示 属性用基于`kebab-case`写法 ::: ```js map.addSource("lines", { type: "geojson", data: geoLines }); map.addLayer({ id: 'route', source: 'lines', type: 'line', "layout": { "line-cap": "square", //butt 尖头,round 圆头,square平头 "line-join": "round" //bevel平拐,round 圆拐,miter棱拐 }, "paint": { "line-width": 20, //宽度 "line-color": "#FF0000", //颜色 "line-opacity": 0.8 //透明度 }}) ``` ### 写法二 ::: tip 提示 属性用驼峰式大小写,不用区分`layout`还是`paint`属性,更详细的写法参考[数据源和图层](/guide/sourcelayer.html) ::: ```js map.addGeoJSONSource("lines", geoLines); map.addLineLayer("route", "lines", { lineCap: "square", //butt 尖头,round 圆头,square平头 lineJoin: "round", //bevel平拐,round 圆拐,miter棱拐 lineWidth: 20, //宽度 lineColor: "#FF0000", //颜色 lineOpacity: 0.8 //透明度 }) ``` ### 写法三 ::: tip 提示 更进一步简化,封装省了传递sourceID和layerID ::: ```js let polylines = new vjmap.Polyline({ data: geoLines, lineCap: "square", //butt 尖头,round 圆头,square平头 lineJoin: "round", //bevel平拐,round 圆拐,miter棱拐 lineWidth: 20, //宽度 lineColor: "#FF0000", //颜色 lineOpacity: 0.8 //透明度 }); polylines.addTo(map); ``` --- # 图层样式 ## Background #### 参数 ##### layout参数 |名称|类型|说明| |:---|:---|:---| |visibility|String字符串| visible, 是否可见 visible / none| ##### paint参数 |名称|类型|说明| |:---|:---|:---| |background-color|String字符串|颜色举例:`#ff0`,`#ffff00`,`rgb(255, 255, 0)`,`rgba(255, 255, 0, 1)`,`hsl(100, 50%, 50%)`,`hsla(100, 50%, 50%, 1)`,`yellow`| |background-opacity|Number数字型|0~1.0, 透明度,越低越透明| |`background-pattern`|String字符串|picture_name, 区的拉伸图片类型,一定要与对应的样式库的图片名字一一对应| ## symbol **Symbol样式由两部分组成,分别是`图标`与`文字`,这两者是各自独立互不影响的** > 核心思想是,通过`geojson图层`来实现特定的样式配置,然后再通过geojson图层`动态添加数据`的方式实现动态添加图片注记。 ``` javascript map.on('click', function(mapMouseEvent) { //鼠标单击点坐标 var latlon = mapMouseEvent.lngLat; //添加一个新的标注(矢量要素) var symbol = { //一定要是最标准的geojson格式 "type": "Feature", "geometry": { "type": "Point", "coordinates": latlon.toArray() }, "properties": { "name": "新增点", } }; GeoPoints.features.push(symbol); map.getSource('symbolLayer').setData(GeoPoints);//名字symbolLayer与上面的source一致 }); ``` ``` javascript //添加数据源,这里是geojson map.addSource('symbolLayer', { "type": "geojson", "data": GeoPoints }); // 增加图层 map.addLayer({ "id": "zondy", "type": "symbol", "source": "symbolLayer", //必须和上面的symbolLayer一致 "filter": ["==", "name", "东正门", "南副门", "北副门"], //关键点:name对应geojson中的属性字段 "layout": { "symbol-placement": "point", //"point"表示基于单个点的注记, "line"表示平铺在线上的注记,"line-center"表示平铺在线的中心点的注记 "symbol-spacing": 200, //单位像素,便是每个注记的占用空间,只有symbol-placement=line才生效 "symbol-avoid-edges": false, //避免在边界情况的,注记碰撞压盖,一般不设置 "icon-allow-overlap": false, //true表示在图层有压盖关系时,仅仅是当前图标保持可见 "icon-ignore-placement": false, //如果设置成true,即使其他要素有碰撞的情况下,依然可以显示 "icon-optional": true, // true表示和其他注记冲突的时候,如果文字没被压盖,显示文字,隐藏图标 "icon-rotation-alignment": "viewport", //map表示随地图旋转角度旋转,viewport表示随当前视角/数据形态(线的方向)一致 "icon-size": 1.4, //表示图片的缩放比例 "icon-text-fit": "none", //"none", "width", "height", "both"让图片适应文字的宽高 "icon-text-fit-padding": [0, 0, 0, 0], // 在适应文字大小的基础上添加对应的内部padding距离 "icon-image": "college-15", //与line-pattern,fill-pattern一样是对应样式库中的图片的名字 "icon-rotate": 0, //图片旋转角度 "icon-padding": 2, //图片正常的内部padding间距 "icon-keep-upright": false, //true表示在地图旋转方向变化是保持图片的上下翻转,就是防止拼接的时候上下颠倒 "icon-offset": [0, 0], //[x,y]图片的偏移,图标与其锚点的偏移距离。正值表示向右和向下,而负值表示向左和向上。每个分量乘以 的值icon-size以获得最终的偏移量(以像素为单位)。与icon-rotate偏移结合时,就像旋转方向向上一样。 "icon-anchor": "center", //"center", "left", "right", "top", "bottom", "top-left", "top-right", "bottom-left", "bottom-right"图片的对齐方向 "icon-pitch-alignment": "auto", //"map", "viewport"对齐方向,这个用于道路的注记,表示是延道路方向显示还是生硬的添加注记 "text-pitch-alignment": "auto", //"map", "viewport"对齐方向,这个用于道路的注记,表示是延道路方向显示还是生硬的添加注记 "text-rotation-alignment": "auto", //map表示随地图旋转角度旋转,viewport表示随当前视角/数据形态(线的方向)一致 "text-field": "{name}", //固定语法,请不要忘记{}号 "text-font": ["Open Sans Regular", "Arial Unicode MS Regular"], "text-size": 20, //字体大小,默认16 "text-max-width": 10, //字体最大宽度,默认10 ems "text-line-height": 1.2, //字体高度,默认 1.2 ems "text-letter-spacing": 0, "text-justify": "center", //文字对齐方式,"left", "center", "right" "text-anchor": "center", //文字对齐方向,"center", "left", "right", "top", "bottom", "top-left", "top-right", "bottom-left", "bottom-right" "text-max-angle": 45, //针对拐角处,相邻字符的最大角度差 "text-rotate": 0, //文字旋转方向,单位degrees "text-padding": 0, //文字的padding内间距,单位像素 "text-keep-upright": false, //true表示在地图旋转方向变化是保持文字的上下翻转,就是防止文字拼接的时候上下颠倒 "text-transform": "none", //"none", "uppercase", "lowercase",显示文字的时候是全大写、全小写,保持不变 "text-offset": [4.5,0], ////[x,y]文字的偏移 "text-allow-overlap": false, // true表示和其他注记有压盖关系时,当前文字保持可见 "text-ignore-placement": false, //如果设置成true,即使其他要素有碰撞的情况下,依然可以显示 "text-optional": true, // true表示和其他注记冲突的时候,如果图标没被压盖,显示图标,隐藏文字 "visibility": "visible", //visible/none }, "paint": { "icon-opacity": 1.0, //图标透明度 "icon-color": "#202", //图标颜色 "icon-halo-color": "#888", //图标光晕颜色 "icon-halo-width": 2, //图标光晕宽度 "icon-halo-blur": 1, //图标光晕模糊度 "icon-translate": [0, 0], //表示显示位置基于原始位置上,再按照屏幕坐标进行偏移 "text-opacity": 1.0, // 文字透明度 "text-color": "#fff", //文字颜色 "text-halo-color": "#000", //文字光晕颜色 "text-halo-width": 2, //文字光晕宽度 "text-halo-blur": 1, //文字光晕模糊度 "text-translate": [0, 0] //表示显示位置基于原始位置上,再按照屏幕坐标进行偏移 } }); ``` 上面的代码核心部分是4个部分 > + **source** `数据源的名称`必须和之前的map.addSource('sourceName')一致,`特别重要` > + **filter** `过滤条件` 按照各自的业务实现对应的数据过滤,注意属性字段的一一匹配 > + **layout** `视图` 一般是控制是否可见、注记的避让压盖关系等,`类型type=symbol时`需要特别重视 > + **paint** `画笔样式` *这才是真正决定颜色、宽度、透明度等样式的地方*,不同的类型对应的属性也各自不同,需要注意这点 --- #### 参数 ##### layout参数 |名称|类型|说明| |:---|:---|:---| |||**Symbol参数**| |`symbol-placement`|String字符串| "point"表示基于单个点的注记, "line"表示平铺在线上的注记| |symbol-spacing|Number数字型| 200, 单位像素,便是每个注记的占用空间,只有symbol-placement=line才生效| |symbol-avoid-edges|Bool布尔型| false, 避免在边界情况的,注记碰撞压盖,一般不设置| |||**Icon参数**| |icon-allow-overlap|Bool布尔型| false, true表示在图层有压盖关系时,仅仅是当前图标保持可见| |icon-ignore-placement|Bool布尔型| false, 如果设置成true,即使其他要素有碰撞的情况下,依然可以显示| |icon-optional|Bool布尔型| true, true表示和其他注记冲突的时候,如果文字没被压盖,显示文字,隐藏图标| |`icon-rotation-alignment`|String字符串| "viewport", map表示随地图旋转角度旋转,viewport表示随当前视角/数据形态(线的方向)一致| |`icon-size`|Number数字型|1.4, 表示图片的缩放比例| |icon-text-fit|String字符串| "none", "none", "width", "height", "both"让图片适应文字的宽高| |icon-text-fit-padding|Number数组型| [0, 0, 0, 0], 在适应文字大小的基础上添加对应的内部padding距离| |`icon-image`|String字符串| "college-15", 与line-pattern,fill-pattern一样是对应样式库中的图片的名字| |icon-rotate|Number数字型| 0, 图片旋转角度| |icon-padding|Number数字型| 2, 图片正常的内部padding间距| |icon-keep-upright|Bool布尔型| false, true表示在地图旋转方向变化是保持图片的上下翻转,就是防止拼接的时候上下颠倒| |`icon-offset` |Number数组型|[0, 0], [x,y]图片的偏移| |icon-anchor|String字符串| "center", "center", "left", "right", "top", "bottom", "top-left", "top-right", "bottom-left", "bottom-right"图片的对齐方向| |icon-pitch-alignment|String字符串| "auto", "map", "viewport"对齐方向,这个用于道路的注记,表示是延道路方向显示还是生硬的添加注记| |||**Text参数**| |text-pitch-alignment|String字符串| "auto", "map", "viewport"对齐方向,这个用于道路的注记,表示是延道路方向显示还是生硬的添加注记| |text-rotation-alignment|String字符串| "auto", map表示随地图旋转角度旋转,viewport表示随当前视角/数据形态(线的方向)一致| |`text-field`|String字符串| "{name}",| |`text-font`|String字符串数组| ["Open Sans Regular", "Arial Unicode MS Regular"], |`text-size`|Number数字型| 20, 字体大小,默认16| |text-max-width|Number数字型| 10, 字体最大宽度,默认10 ems| |text-line-height|Number数字型| 1.2, 字体高度,默认 1.2 ems| |text-letter-spacing|Number数字型| 0,文字字母间距,基本没用| |text-justify|String字符串| "center", 文字对齐方式,"left", "center", "right"| |text-anchor|String字符串| "center", 文字对齐方向,"center", "left", "right", "top", "bottom", "top-left", "top-right", "bottom-left", "bottom-right"| |text-max-angle|Number数字型|45, 针对拐角处,相邻字符的最大角度差| |text-rotate|Number数字型| 0, 文字旋转方向,单位degrees| |text-padding|Number数字型| 0, 文字的padding内间距,单位像素| |text-keep-upright|Bool布尔型| false, true表示在地图旋转方向变化是保持文字的上下翻转,就是防止文字拼接的时候上下颠倒| |text-transform|String字符串| "none", "none", "uppercase", "lowercase",显示文字的时候是全大写、全小写,保持不变| |`text-offset`|Number数组型| [4.5,0], [x,y]文字的偏移| |text-allow-overlap|Bool布尔型|true表示和其他注记有压盖关系时,当前文字保持可见| |text-ignore-placement|Bool布尔型| `false,如果设置成true,即使其他要素有碰撞的情况下,依然可以显示`| |text-optional|Bool布尔型| true, true表示和其他注记冲突的时候,如果图标没被压盖,显示图标,隐藏文字| |visibility|String字符串| "visible", visible/none| ##### paint参数 |名称|类型|说明| |:---|:---|:---| |||**Icon参数**| |`icon-opacity`|Number数字型| 1.0,图标透明度| |`icon-color`|String字符串|图标颜色,颜色举例:`#ff0`,`#ffff00`,`rgb(255, 255, 0)`,`rgba(255, 255, 0, 1)`,`hsl(100, 50%, 50%)`,`hsla(100, 50%, 50%, 1)`,`yellow`| |`icon-halo-color`|String字符串|图标光晕颜色,颜色举例:`#ff0`,`#ffff00`,`rgb(255, 255, 0)`,`rgba(255, 255, 0, 1)`,`hsl(100, 50%, 50%)`,`hsla(100, 50%, 50%, 1)`,`yellow`| |`icon-halo-width`|Number数字型| 2, 图标光晕宽度| |`icon-halo-blur`|Number数字型| 1, 图标光晕模糊度| |icon-translate|Number数组型|[x,y]表示显示位置基于原始位置上,再按照屏幕坐标进行偏移,这个应该绝大部分都用不上| |||**Text参数**| |`text-opacity`|Number数字型| 1.0, 文字透明度| |`text-color`|String字符串|文字颜色,颜色举例:`#ff0`,`#ffff00`,`rgb(255, 255, 0)`,`rgba(255, 255, 0, 1)`,`hsl(100, 50%, 50%)`,`hsla(100, 50%, 50%, 1)`,`yellow`| |`text-halo-color`|String字符串|文字光晕颜色,颜色举例:`#ff0`,`#ffff00`,`rgb(255, 255, 0)`,`rgba(255, 255, 0, 1)`,`hsl(100, 50%, 50%)`,`hsla(100, 50%, 50%, 1)`,`yellow`| |`text-halo-width`|Number数字型| 2,文字光晕宽度| |`text-halo-blur`|Number数字型| 1, 文字光晕模糊度,这里的数值要`小于`上面`text-halo-width`的宽度| |text-translate|Number数组型|[x,y]表示显示位置基于原始位置上,再按照屏幕坐标进行偏移,这个应该绝大部分都用不上| ## Line ``` json //添加数据源,这里是geojson map.addSource('lineLayer', { "type": "geojson", "data": geojsonData }); //针对数据源设置图层样式 map.addLayer({ "id": "roadid", //id不同重复,否则只绘制一次 "type": "line", "source": "lineLayer", //必须和上面的lineLayer一致 "filter": ["==", "name", "光谷大道"], //关键点:name对应geojson中的属性字段 "layout": { "line-cap": "square", //butt 尖头,round 圆头,square平头 "line-join": "miter", //bevel平拐,round 圆拐,miter棱拐 "line-miter-limit": 2, //棱拐的限制,一般用不上 "line-round-limit": 1.05, //圆拐的限制,一般用不上 "visibility": "visible", //是否可见 visible / none }, "paint": { "line-width": 10, //宽度 "line-color": "#9c27b0", //颜色 "line-opacity": 0.8, //透明度 `0 ~ 1.0` "line-gap-width": 0, //线的沟宽,如果有一条线会变成2条线,中间有条沟 "line-offset": 0, //尽量少用,如果这个值相对大的话在拐角处很容易变形变胖 "line-dasharray": [1,1],//实线、虚线的组合,可以表示铁路线等 "line-blur": 2, //模糊度,和宽度配合使用,当宽度20,模糊度10时,出现边线模糊的效果,该值要小于线宽度 "line-pattern": "picture_name", //线的拉伸图片类型,一定要与对应的样式库的图片名字一一对应 "line-translate": [0,0] //表示显示位置基于原始位置上,再按照屏幕坐标进行偏移,这个应该绝大部分都用不上 } }); ``` 上面的代码核心部分是4个部分 > + **source** `数据源的名称`必须和之前的map.addSource('sourceName')一致,`特别重要` > + **filter** `过滤条件` 按照各自的业务实现对应的数据过滤,注意属性字段的一一匹配 > + **layout** `视图` 一般是控制是否可见、注记的避让压盖关系等,`类型type=symbol时`需要特别重视 > + **paint** `画笔样式` *这才是真正决定颜色、宽度、透明度等样式的地方*,不同的类型对应的属性也各自不同,需要注意这点 --- #### 参数 ##### layout参数 |名称|类型|说明| |:---|:---|:---| |line-cap|String字符串|默认square, `butt` 尖头,`round` 圆头,`square`平头| |line-join|String字符串| 默认miter, `bevel`平拐,`round` 圆拐,`miter`棱拐| |line-miter-limit|Number数字型| 2, 棱拐的限制,一般用不上| |line-round-limit|Number数字型| 1.05, 圆拐的限制,一般用不上| |visibility|String字符串| visible, 是否可见 visible / none| ##### paint参数 |名称|类型|说明| |:---|:---|:---| |line-width|Number数字型| 10, 宽度| |line-color|String字符串| 颜色举例:`#ff0`,`#ffff00`,`rgb(255, 255, 0)`,`rgba(255, 255, 0, 1)`,`hsl(100, 50%, 50%)`,`hsla(100, 50%, 50%, 1)`,`yellow`| |line-opacity|Number数字型| 0.8, 透明度`0 ~ 1.0`| |line-gap-width|Number数字型| 0, 线的沟宽,如果有一条线会变成2条线,中间有条沟| |line-offset|Number数字型| 0,`尽量少用`,如果这个值相对大的话在拐角处很容易变形变胖| |line-dasharray|Number数组型| [1,1],实线、虚线的组合,可以表示铁路线等| |line-blur|Number数字型| 2, 模糊度,和宽度配合使用,当宽度20,模糊度10时,出现边线模糊的效果,该值要小于线宽度| |`line-pattern`|String字符串| picture_name, 线的拉伸图片类型,`一定`要与对应的`样式库Sprite`的图片名字`一一对应`| |line-translate|Number数组型| [0,0] `[x,y]`表示显示位置基于原始位置上,再按照屏幕坐标进行偏移,这个应该绝大部分都用不上| ## Circle ``` json //添加数据源,这里是geojson map.addSource('circleLayer', { "type": "geojson", "data": geojsonData }); //针对数据源设置图层样式 map.addLayer({ "id": "circle_cug", //id不同重复,否则只绘制一次 "type": "circle",// 固定语法 circle/line/fill/fill-extrusion/symbol/background "source": "circleLayer", //必须和上面的map.addSource('circleLayer')的circleLayer一致 "filter": ["==", "name", "中国地质大学(武汉)"], //过滤属性,关键点:name对应geojson中的属性字段 "layout": { "visibility": "visible",//visible/none 表示是否可见 }, "paint": { "circle-radius": 15, //半径 "circle-color": "#202", //颜色 "circle-opacity": 0.8, //透明度 "circle-stroke-width": 5, //轮廓线宽度 "circle-stroke-color": "#0000FF", //轮廓线颜色 "circle-stroke-opacity": 0.7, //轮廓线透明度 "circle-translate": [0,0] //表示显示位置基于原始位置上,再按照屏幕坐标进行偏移,这个应该绝大部分都用不上 } }); ``` 上面的代码核心部分是4个部分 > + **source** `数据源的名称`必须和之前的map.addSource('sourceName')一致,`特别重要` > + **filter** `过滤条件` 按照各自的业务实现对应的数据过滤,注意属性字段的一一匹配 > + **layout** `视图` 一般是控制是否可见、注记的避让压盖关系等,`类型type=symbol时`需要特别重视 > + **paint** `画笔样式` *这才是真正决定颜色、宽度、透明度等样式的地方*,不同的类型对应的属性也各自不同,需要注意这点 --- #### 参数 ##### layout参数 |名称|类型|说明| |:---|:---|:---| |visibility|String字符串| visible, 是否可见 visible / none| ##### paint参数 |名称|类型|说明| |:---|:---|:---| |circle-radius|Number数字型|15, 半径| |circle-color|String字符串|颜色举例:`#ff0`,`#ffff00`,`rgb(255, 255, 0)`,`rgba(255, 255, 0, 1)`,`hsl(100, 50%, 50%)`,`hsla(100, 50%, 50%, 1)`,`yellow`| |circle-opacity|Number数字型|0~1.0, 透明度,越低越透明| |circle-stroke-width|Number数字型|5, 轮廓线宽度| |circle-stroke-color|String字符串|: #0000FF, 轮廓线颜色,颜色举例:`#ff0`,`#ffff00`,`rgb(255, 255, 0)`,`rgba(255, 255, 0, 1)`,`hsl(100, 50%, 50%)`,`hsla(100, 50%, 50%, 1)`,`yellow`| |circle-stroke-opacity|Number数字型|0.7, 轮廓线透明度| |circle-translate|Number数组型|[0,0] 表示显示位置基于原始位置上,再按照屏幕坐标进行偏移,这个应该绝大部分都用不上| ## Fill ``` json //添加数据源,这里是geojson map.addSource('fillLayer', { "type": "geojson", "data": geojsonData }); //针对数据源设置图层样式 map.addLayer({ "id": "huiyishi", //id不同重复,否则只绘制一次 "type": "fill", "source": "fillLayer", //必须和上面的geojsonPolygon一致 "filter": ["==", "name", "会议室"], //关键点:name对应geojson中的属性字段 "layout": { "visibility": "visible",//visible/none 表示是否可见 }, "paint": { "fill-antialias": true, //抗锯齿,true表示针对边界缝隙进行填充 "fill-color": "#00695c", //颜色 "fill-opacity": 0.9, //透明度 `0 ~ 1.0` "fill-outline-color": "#FFFF00", //边线颜色,没错,确实没有边线宽度这个选项 "fill-pattern":"picture_name", //线的拉伸图片类型,一定要与对应的样式库的图片名字一一对应 "fill-translate": [0,0] //表示显示位置基于原始位置上,再按照屏幕坐标进行偏移,这个应该绝大部分都用不上 } }); map.addLayer({ "id": "bounding", "type": "line", //相信你的眼睛,这里的的确确是line,区可以复用线的样式 "source": "fillLayer", //必须和上面的geojsonPolygon一致 "filter": ["==", "name", "边界区"], //关键点:name对应geojson中的属性字段 "paint": { "line-width": 4, //宽度 "line-color": "#e51c23", //颜色 "line-opacity": 1.0, //透明度 "line-gap-width": 2, //线的沟宽,如果有一条线会变成2条线,中间有条沟 } }); ``` 上面的代码核心部分是4个部分 > + **source** `数据源的名称`必须和之前的map.addSource('sourceName')一致,`特别重要` > + **filter** `过滤条件` 按照各自的业务实现对应的数据过滤,注意属性字段的一一匹配 > + **layout** `视图` 一般是控制是否可见、注记的避让压盖关系等,`类型type=symbol时`需要特别重视 > + **paint** `画笔样式` *这才是真正决定颜色、宽度、透明度等样式的地方*,不同的类型对应的属性也各自不同,需要注意这点 --- #### 参数 ##### layout参数 |名称|类型|说明| |:---|:---|:---| |visibility|String字符串| visible, 是否可见 visible / none| ##### paint参数 |名称|类型|说明| |:---|:---|:---| |fill-antialias|Bool布尔型| true, 抗锯齿,true表示针对边界缝隙进行填充| |fill-color|String字符串|颜色举例:`#ff0`,`#ffff00`,`rgb(255, 255, 0)`,`rgba(255, 255, 0, 1)`,`hsl(100, 50%, 50%)`,`hsla(100, 50%, 50%, 1)`,`yellow`| |fill-opacity|Number数字型| 0.9, 透明度| |fill-outline-color|String字符串| #FFFF00, 边线颜色,没错,确实`没有边线宽度`这个选项| |`fill-pattern`|String字符串|picture_name, 区的拉伸图片类型,一定要与对应的样式库的图片名字一一对应| |fill-translate|Number数组型| [0,0] `[x,y]`表示显示位置基于原始位置上,再按照屏幕坐标进行偏移,这个应该绝大部分都用不上| ## fillextrusion ``` json //添加数据源,这里是geojson map.addSource('fillExtrusionLayer', { "type": "geojson", "data": geojsonData }); //针对数据源设置图层样式 map.addLayer({ "id": "main", //id不同重复,否则只绘制一次 "type": "fill-extrusion", "source": "fillExtrusionLayer", //必须和上面的fillExtrusionLayer一致 "filter": ["==", "name", "主楼"], //关键点:name对应geojson中的属性字段 "paint": { "fill-extrusion-height": ['get', 'height'], //固定语法,获取属性值height的数值 "fill-extrusion-base": 10,//基础高度,表示相对水平面的高度 "fill-extrusion-color": "#00695c", //颜色 "fill-extrusion-opacity": 0.9, //透明度 `0 ~ 1.0` "fill-extrusion-pattern":"si-main-3", //线的拉伸图片类型,一定要与对应的样式库的图片名字一一对应 "fill-extrusion-translate": [0,0] //表示显示位置基于原始位置上,再按照屏幕坐标进行偏移,这个应该绝大部分都用不上 } }); ``` 上面的代码核心部分是4个部分 > + **source** `数据源的名称`必须和之前的map.addSource('sourceName')一致,`特别重要` > + **filter** `过滤条件` 按照各自的业务实现对应的数据过滤,注意属性字段的一一匹配 > + **layout** `视图` 一般是控制是否可见、注记的避让压盖关系等,`类型type=symbol时`需要特别重视 > + **paint** `画笔样式` *这才是真正决定颜色、宽度、透明度等样式的地方*,不同的类型对应的属性也各自不同,需要注意这点 --- #### 参数 ##### layout参数 |名称|类型|说明| |:---|:---|:---| |visibility|String字符串| visible, 是否可见 visible / none| ##### paint参数 |名称|类型|说明| |:---|:---|:---| |`fill-extrusion-height`|Number数字型| `['get', 'height']`, 固定语法,获取属性值height的数值,或者是直接传递`固定数值`| |fill-extrusion-base|Number数字型| 10,基础高度,表示`相对水平面`的高度| |fill-extrusion-color|String字符串|#00695c颜色举例:`#ff0`,`#ffff00`,`rgb(255, 255, 0)`,`rgba(255, 255, 0, 1)`,`hsl(100, 50%, 50%)`,`hsla(100, 50%, 50%, 1)`,`yellow`| |fill-extrusion-opacity|Number数字型| 0.9, 透明度| |`fill-extrusion-pattern`|String字符串|`si-main-3`, 区表面的拉伸图片类型,一定要与对应的样式库的图片名字一一对应| |fill-extrusion-translate|Number数组型| [0,0] `[x,y]`表示显示位置基于原始位置上,再按照屏幕坐标进行偏移,这个应该绝大部分都用不上| --- # 数据源与图层 ### 主要特点: - 无需区分绘制、布局和其他属性。 - 所有属性都可以表示为camelCase 而不是kebab-case。 - 层操作可以作用于多个层(由数组、正则表达式或过滤器函数给出),而不仅仅是一层。 - 源类型、图层类型和属性名称合并到函数名称中:`addGeoJSON()`, `addCircleLayer()`, `setCircleRadius()`, `getTextFont()`... - 添加层和源是幂等的:调用`addLineLayer()`多次创建,然后更新层。 - 其他一些便利功能:`show()`, `hide()`, `onLoad()`, `setData()`,`fontsInUse()` - 更好的点击和悬停功能:`hoverPointer()`, `hoverFeatureState()`, `hoverPopup()`,`clickLayer()` - 某些功能表现更好:(`removeLayer()`如果图层不存在则不会出错),`removeSource()`(自动删除附加图层),`setFilter()`(一次在多个图层上工作),`setData()`如果没有提供 GeoJSON ,则清除数据。 ### 使用图层 `props`添加图层时传递的对象可以自由混合绘制、布局等属性。可以在camelCase 或kebab-case 中指定属性键: ```js map.addCircleLayer('trees-circle', 'trees', { circleColor: 'green', // paint property circleRadius: ['interpolate', ['zoom'], 12, 3, 15, 5], // paint property circleSortKey: ['get', 'tree-sort-key'], // layout property filter: ['!=', 'type', 'stump'], // other property }); ``` 几乎所有适用于现有层(例如`show()`)的方法都可以适用于多个层。有四种方法可以指定要修改的图层: - 字符串: `map.show('trees-label'); map.show('trees-circle');` - 字符串数组:`map.show(['trees-label', 'trees-circle'])`; - 正则表达式:`map.show(/^trees-/)`; - 接受一个层并返回真实值的函数: `map.show(layer => layer.source === 'trees');` ### 添加源 添加源的方法返回一个对象,可以链接该对象以允许向其中添加层: ```js map.addGeoJSONSource( 'properties' ) .addCircleLayer( 'properties-line' , { lineWidth : 3 }) .addSymbolLayer( 'properties-fill' , { fillColor : 'hsla(30,30%,60%,0.5)' }) ``` 添加和删除图层 ```js // 方便地添加线要素,混合绘制,布局等属性。 // 请注意,您可以对所有属性名称使用驼峰命名法。 map.addLineLayer( 'mylines' , 'mysource' , { lineWidth : 3 , lineCap : 'round' , minzoom : 11 }); // 还有 addFillLayer, addFillExtrusionLayer, addRasterLayer, addVideoLayer, addSymbolLayer, addHillshadeLayer, addHeatmapLayer map.addCircleLayer( 'mycircles' , 'mysource' , { circleStrokeColor : 'red' }); // 如果图层已经存在,调用 add*Layer 只会更新任何属性 map.addCircleLayer( 'mycircles' , 'mysource' , { circleStrokeColor : 'red' , circleRadius : 4 , filter: ['==', 'type', 'active'}); // 当然,如果需要,在另一个图层“之前”添加图层: map.addLineLayer( 'mylayer' , 'mysource' , { lineColor : 'red' }, 'toplayer' ); // 如果图层不存在,removeLayer() 不会抛出错误 map.removeLayer([ 'towns' , 'town-labels' ]); ``` ### 添加和删除源 ```js // 创建 GeoJSON 源的更简单方法: map.addGeoJSON( 'mysource' , geojson); // 或者使用初始空白数据创建一个 GeoJSON 源。如果您单独加载, 数据并且稍后将调用`setData()`这将非常方便。 map.addGeoJSON( 'mysource' ); // 创建矢量切片源的更简单方法: map.addVector( 'mysource' , 'xxx' ); map.addVector( 'mysource' , 'https://example.com/tiles/{z}/{x}/{y}.pbf' ); // 其他属性仍然有效 map.addVector( 'mysource' , 'https://example.com/tiles/{z}/{x}/{y}.pbf' , { maxzoom : 13 }); // 还有 addRaster(), addRasterDem(), addImage(), addVideo() // 调用任何 add* 函数只会更新源定义(如果源定义已经存在)。 // 使用这些源自动删除任何层。如果源不存在,则不是错误。 map.removeSource(['buildings', 'roads']); // 您也可以使用返回的对象方便地添加图层: map.addGeoJSON( 'buildings' , 'data/buildings.geojson' ) .addFillExtrusion( 'buildings-3d' , { fillExtrusionHeight : 100 , fillExtrusionColor : 'grey' }).addLineLayer( 'buildings-footprint' , { lineColor : ' lightblue ' }); // 替换现有图层上的源。(实际上删除并重新添加它。) map.setLayerSource( 'buildings' , 'newsource' ); map.setLayerSource([ 'buildings-3d' , 'buildings-outline]' , 'newsource' , 'newsourcelayer' ); // 要更改源图层,请传递第三个参数或 null 以清除它(如果从矢量切片切换到 geojson) map.setLayerSource( 'buildings' , 'mylocalbuildings' , null ); ``` ### 设置属性和更新数据 ```js // 每个属性都有一个 setXxx() 形式: map.setTextSize( 'mylayer' , 12 ); // 它们都同时作用于多个层: map.setLineWidth([ 'mylayer' , 'mylayer-highlight' ], 4 ); map.setLineOpacity( /^ border- / , 0 ); map.setFillColor( layer => layer.source === 'farms' , 'green' ); // 还有一个更熟悉的 setProperty() 形式。 map.setProperty( 'mylayer' , 'line-width' , 3 ); // 现有属性不会被触及 map.setProperty( 'mylayer' , { textSize : 12 , textColor : 'red' }); // 每个函数都有一个 `get...` 版本。 map.getFillColor( 'water' ) // 更新源数据的更简单方法: map.setData( 'mysource' , data); // 您可以省略 data 参数以清除 GeoJSON 源: map.setData( 'mysource' ); // 更容易记住打开和关闭图层的方法: map.show( 'mylayer' ); map.hide( 'mylayer' ); map.toggle([ 'mylayer' , 'myotherlayer' ], isVisible); // 为避免名称冲突,例如与 'raster' 的名称冲突,您可以使用更长的形式 // 以 ...Layer() 或 ...Source()结尾 map.addRasterSource( 'myrastersource' , { type : 'raster' , url : 'xxx' , tileSize : 256 }); map.addRasterLayer( 'myrasterlayer' , 'myrastersource' , { rasterSaturation : 0.5 }); ``` ### 悬停和点击 ```js // 悬停在此图层上时使用鼠标“手指”光标。 map.hoverPointer( 'mylayer' ); // 如果您传递多个图层,它会正确处理从一个图层移动到另一个图层 // 悬停在该图层上时使用鼠标“手指”光标。 map.hoverPointer([ 'regions-border' , 'regions-fill' ]); // 当鼠标移动到该层的特征上时,将“悬停”特征状态设置为真或假。 // 要求特性有一个 `id`。 map.hoverFeatureState( 'mylayer' ); // 想要将悬停功能状态应用于不同的源? // 例如,您将鼠标悬停在标签上,但想要突出显示周围的边界。 map.hoverFeatureState( 'town-labels' , 'boundaries' , 'town-boundaries' ); // 您还可以添加其他事件处理程序: map.hoverFeatureState( 'mylayer' , 'mysource' , 'mysourcelayer' , e => console.log(`Entered ${e.features[0].id}`), e => console.log(`Left ${e.oldFeatureid}`); // 当一个功能悬停或点击时显示一个弹出窗口。 // 第三个参数是一个选项对象,传递给 Popup 构造函数。 // 回调被调用为: (feature, popup) => htmlString map.hoverPopup( 'mylayer' , f => `

${f.properties.Name}

${f.properties.Description} ` , { anchor : 'left' }); map.clickPopup( 'mylayer' , f => `

${f.properties.Name}

${f.properties.Description} ` , { maxWidth : 500 }); // clickLayer() 类似于 .on('click)',但可以接受一个数组并向事件中添加一个 'features' 成员用于点击的内容。 map.clickLayer([ 'towns' , 'town-labels' ], e => panel.selectedId = e.features[ 0 ].id); // clickOneLayer 按顺序测试多个层,在第一个命中的 上触发回调。回调被传递 { 功能,功能,层,事件 }。 map.clickOneLayer([ 'town-labels' , 'state-boundaries' ], e => { if (e.layer === 'town-labels') { setView('town'); panel.selectedId = e.features[0].id; } else if (e.layer === 'state-boundaries') { setView('state'); panel.selectedId = e.features[0].id; } }); // 可选地传入一个额外的回调,该回调为错过所有图层的点击而触发: map.clickOneLayer(['town-labels', 'state-boundaries'], e => {...}, e => { console.log('Missed everything'); }); // 所有这些函数都返回一个“撤消”函数,删除添加的处理程序: const remove = map.hoverPopup( 'mylayer' , showPopupFunc); //... remove(); // 不再有悬停弹出 ``` ### 其他功能 ```js // 与 on('load') 类似,但在地图加载后的任何时候都会立即(并且可靠地)触发。 map.onLoad(回调); // 如果没有回调,则返回一个承诺: await map.onLoad(); // 获取图层定义。 const layer = map.getLayerStyle( 'mylayer' ); // 首先将所有其他属性重置为默认值。忽略非绘制、非布局属性。 map.setLayerStyle( 'mylayer' , { lineWidth : 3 }); // properties() 将对象转换为 `kebab-case`写法的图层对象 map.addLayer(map.properties({ id : 'mylayer' , source : 'mysource' , type : 'line' , lineWidth : 3 , lineCap : 'round' , minzoom : 11 , filter : [ '==' , 'status' , 'confirmed' ] })); // layerStyle() 很灵活,可以根据需要传递尽可能多的 id, source, 和 type map.layerStyle( 'mylayer' , 'mysource' , 'line' , { ... }) map.layerStyle( 'mylayer' , 'mysource' , { ... }) map.layerStyle( 'mylayer' , { ... }) map.layerStyle({ ... }) // 隐藏/显示/切换附加到此源地图的所有图层 map.hideSource('buildings'); map.showSource('buildings'); map.toggleSource('buildings', true); // 一次更新多个过滤器。 map.setFilter([ 'buildings-fill' , 'buildings-outline' , 'buildings-label' ], [...]); // 一步方便地将图像加载到地图中 map.loadImage( 'marker' , '/assets/marker-pin.png' ); map.loadImage( 'marker' , '/assets/marker-pin@2x.png' , { pixelRatio : 2 }).then( /* ... */) ; // 更新地图样式的根“transition”属性 map.setTransition({ delay : 1000 , delay : 0 }); // 使用 fontsUsed() 获取符号层中使用的字体列表。用于快速显示一些文本。 const fonts = map.fontsInUse(); map.addSymbolLayer( 'labels' , 'mysource' , { textFont : fonts[ 0 ], textField : '{label}' }); ``` ### 例子 ```js map.onload( () => { map.addGeoJSON('towns'); map.addCircleLayer('small-towns', 'towns', { circleColor: 'green', filter: ['==', 'size', 'small']}); map.addCircleLayer('large-towns', 'towns', { circleColor: 'red', filter: ['==', 'size', ['large']], circleStrokeWidth: ['case', ['to-boolean', ['feature-state', 'hover']], 5, 1] ); map.setCircleRadius(['small-towns', 'large-towns'], 12); map.hoverPointer(['small-towns', 'large-towns']); map.hoverFeatureState('large-towns'); // 更新数据 d3.json('http://example.com/towns.json', data => map.setData('towns', data)); }); ``` --- # 常见图层 ## 绘图图层 ```js let data = { "type": "FeatureCollection", "features": [ { "id": "1", "type": "Feature", "properties": { "name": "point1", "color": "#00ffff", "icon": "icon1" }, "geometry": { "coordinates": [ 6971.5378667085715, 14385.014943907285 ], "type": "Point" } }, { "id": "2", "type": "Feature", "properties": { "name": "polyline1", "color": "#00ffff" }, "geometry": { "coordinates": [ [ 3006.0952985307595, 15198.972934052355 ], [ 12606.64046359223, 9285.59366571666 ], [ 4745.324495099878, 3720.060236694666 ], [ 3006.0952985307595, 15198.972934052355 ] ], "type": "LineString" } }, { "id": "3", "type": "Feature", "properties": { "name": "polygon1", "color": "#ff00ff" }, "geometry": { "coordinates": [ [ [ 19841.833921319492, 13807.589576796912 ], [ 19841.833921319492, 4207.0444117340885 ], [ 26172.62819683108, 4207.0444117340885 ], [ 26172.62819683108, 13807.589576796912 ], [ 19841.833921319492, 13807.589576796912 ] ] ], "type": "Polygon" } } ] } map.getDrawLayer().set(map.toLngLat(data)); ```
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: "sys_hello", // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapLightStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    showError(res.error)
    return;
}
// 获取地图范围
let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapBounds);
//
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapBounds.center()), // 设置地图中心点
    zoom: 1, // 设置地图缩放级别
    pitch: 0,
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();
let data = {
    "type": "FeatureCollection",
    "features": [
        {
            "id": "1",
            "type": "Feature",
            "properties": {
                "name": "point1",
                "color": "#00ffff",
                "icon": "icon1"
            },
            "geometry": {
                "coordinates": [
                    6971.5378667085715,
                    14385.014943907285
                ],
                "type": "Point"
            }
        },
         {
            "id": "2",
            "type": "Feature",
            "properties": {
                "name": "polyline1",
                "color": "#00ffff"
            },
            "geometry": {
                "coordinates": [
                    [
                        3006.0952985307595,
                        15198.972934052355
                    ],
                    [
                        12606.64046359223,
                        9285.59366571666
                    ],
                    [
                        4745.324495099878,
                        3720.060236694666
                    ],
                    [
                        3006.0952985307595,
                        15198.972934052355
                    ]
                ],
                "type": "LineString"
            }
        },
        {
            "id": "3",
            "type": "Feature",
            "properties": {
                "name": "polygon1",
                "color": "#ff00ff"
            },
            "geometry": {
                "coordinates": [
                    [
                        [
                            19841.833921319492,
                            13807.589576796912
                        ],
                        [
                            19841.833921319492,
                            4207.0444117340885
                        ],
                        [
                            26172.62819683108,
                            4207.0444117340885
                        ],
                        [
                            26172.62819683108,
                            13807.589576796912
                        ],
                        [
                            19841.833921319492,
                            13807.589576796912
                        ]
                    ]
                ],
                "type": "Polygon"
            }
        }
    ]
}
map.getDrawLayer().set(map.toLngLat(data));


## 热力图层 ```js let heatmapLayer = new vjmap.Heatmap({ data: geoDatas, // 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 ] }); heatmapLayer.addTo(map); ```
// --热力图层--
// 地图服务对象
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: 'sys_zp', // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    message.error(res.error)
}
// 获取地图范围
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapExtent);
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapExtent.center()), // 设置地图中心点
    zoom: 2, // 设置地图缩放级别
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
// 根据地图本身范围缩放地图至全图显示
map.fitMapBounds();
await map.onLoad();
const mapBounds = map.getGeoBounds(0.6);
let geoDatas = []
for(let i = 0; i < 1000; i++) {
    const pt = mapBounds.randomPoint();
    const data = {
        point:  map.toLngLat(pt),
        properties: {
           value: vjmap.randInt(0, 100)
        }
    }
    geoDatas.push(data)
}
let heatmapLayer = new vjmap.Heatmap({
    data: geoDatas,
    // 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
    ]
});
heatmapLayer.addTo(map);
// 点击查询热力图层
map.on('click', heatmapLayer.layerId, (e) => {
    map.logInfo(`影响此点热力值的要素有: ${e.features.length} 个`);
});


## 背景遮盖层 ```js const background = new vjmap.BackgroundLayer({ backgroundColor: "yellow", backgroundOpacity: 0.4 } ) background.addTo(map); ```
// 地图服务对象
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: 'sys_zp', // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    message.error(res.error)
}
// 获取地图范围
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapExtent);
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapExtent.center()), // 设置地图中心点
    zoom: 2, // 设置地图缩放级别
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
// 根据地图本身范围缩放地图至全图显示
map.fitMapBounds();
await map.onLoad();
const background = new vjmap.BackgroundLayer({
        backgroundColor: "yellow",
        backgroundOpacity: 0.4
    }
)
background.addTo(map);


## 自定义水印背景层 ```js // 注意图片大小必须为2的指数倍,如(2, 4, 8, ..., 512) await map.loadImageEx(`testwatermark`, `https://vjmap.com/static/assets/images/testwatermark.png`); let layers = map.getStyle().layers const background = new vjmap.BackgroundLayer({ backgroundPattern: "testwatermark", backgroundOpacity: 0.3 }) background.addTo(map); // 把这个图层放至所有图层的最下面 map.moveLayer(background.layerId, layers[0].id) ```
// 地图服务对象
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: 'sys_zp', // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    message.error(res.error)
}
// 获取地图范围
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapExtent);
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapExtent.center()), // 设置地图中心点
    zoom: 2, // 设置地图缩放级别
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
// 根据地图本身范围缩放地图至全图显示
map.fitMapBounds();
await map.onLoad();
// 注意图片大小必须为2的指数倍,如(2, 4, 8, ..., 512)
await map.loadImageEx(`testwatermark`, `https://vjmap.com/static/assets/images/testwatermark.png`);
let layers = map.getStyle().layers
const background = new vjmap.BackgroundLayer({
    backgroundPattern: "testwatermark",
    backgroundOpacity: 0.3
})
background.addTo(map);
// 把这个图层放至所有图层的最下面
map.moveLayer(background.layerId, layers[0].id)


## 栅格图像图层 ```js const mapBounds = map.getGeoBounds(0.001); const coords = map.toLngLat(mapBounds.toPointArray()); map.addImageSource("park", { url: "https://vjmap.com/static/assets/images/park.jpg", coordinates: coords }) map.addRasterLayer("parkLayer", "park", { rasterOpacity: 0.8, minzoom: 7, maxzoom: 20 }) ```
// --热力图层--
// 地图服务对象
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: 'sys_zp', // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    message.error(res.error)
}
// 获取地图范围
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapExtent);
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapExtent.center()), // 设置地图中心点
    zoom: 9, // 设置地图缩放级别
    pitch: 40, // 倾斜角度
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();
const mapBounds = map.getGeoBounds(0.001);
const coords = map.toLngLat(mapBounds.toPointArray());
map.addImageSource("park", {
    url: "https://vjmap.com/static/assets/images/park.jpg",
    coordinates: coords
})
map.addRasterLayer("parkLayer", "park", {
    rasterOpacity: 0.8,
    minzoom: 7,
    maxzoom: 20
})
// 增加四个覆盖物用来调节位置
const colors = ["#FF0000", "#00FF00", "#0000FF", "#FFFF00"];
const markers = []
for(let c = 0; c <  colors.length; c++) {
    const marker = new vjmap.Marker({
        color: colors[c],
        draggable: true
    });
    marker.setLngLat(coords[c]).addTo(map);
    marker.on('drag', e => {
        updateBounds();
    });
    markers.push(marker);
}
const updateBounds = () => {
    // 更新位置
    map.getSource("park").setCoordinates(markers.map(m => {
        let lngLat = m.getLngLat();
        return [lngLat.lng,lngLat.lat]
    }));
}


## 天空图层 ```js const sky = new vjmap.SkyLayer({ 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 ] } ) sky.addTo(map); ```
// --热力图层--
// 地图服务对象
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: 'sys_zp', // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    message.error(res.error)
}
// 获取地图范围
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapExtent);
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapExtent.center()), // 设置地图中心点
    zoom: 2, // 设置地图缩放级别,
    pitch: 80, // 倾斜角度
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
// 根据地图本身范围缩放地图至全图显示
map.fitMapBounds();
await map.onLoad();
const sky = new vjmap.SkyLayer({
        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
        ]
    }
)
sky.addTo(map);


## 唯杰地图三维图层 [在线示例查看](https://vjmap.com/map3d/demo/#/demo/map/entity/custom/05entitycustommap2d)
// --唯杰2D地图效果--唯杰2D地图为底图,vjmap3d做为一个3D图层叠加在上面
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: env.exampleMapId, // 地图ID
    // @ts-ignore
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapLightStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    showError(res.error)
    return;
}
// 获取地图范围
let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapBounds);
let rasterStyle = svc.rasterStyle()
// @ts-ignore
rasterStyle.layers[0].paint = {
    // 自定义地图样式,用深色来代替默认的样式
    "raster-inverse": 1, //  0不反色 1 反色
    "raster-monochrome": "#4586b6", // // 修改地图样式,用纯色代替
    "raster-saturation": 0, // 饱和度  取值 -1 到 1
    "raster-contrast": 0, // 对比度   取值 -1 到 1
}
//
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: rasterStyle, // 样式,这里是栅格样式
    center: prj.toLngLat(mapBounds.center()), // 设置地图中心点
    zoom: 1, // 设置地图缩放级别
    pitch: 45,
    antialias: true, // 反锯齿
    renderWorldCopies: false // 不显示多屏地图
});
//
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();
//
// 创建3d图层
let mapLayer = new vjmap3d.MapThreeLayer(map, {
    stat: {
        show: true,
        left: "0"
    }
});
map.addLayer(new vjmap.ThreeLayer({ context: mapLayer }))
let app = mapLayer.app;
// 创建一个四棱锥
const createConeMesh = (co /*[number, number, number] | THREE.Vector3*/, opts /*{
    size?: number;
    height?: number;
    color?: THREE.ColorRepresentation;
    rotation?: boolean;
    rotationAxis?: THREE.Vector3;
    rotationSpeed?: number;
    jumpHeightScale?: number;
    jumpDuration?: number;
 }*/, app) => {
    opts ??= {};
    let size = opts.size || 0.5;
    let height = opts.height || size * 4; //棱锥高度
    // 圆锥体几何体API(ConeGeometry)圆周方向四等分实现四棱锥效果
    let geometry = new THREE.ConeGeometry(size, height, 4);
    // 可以根据需要旋转到特定角度
    // geometry.rotateX(Math.PI);
    app ??= vjmap3d.Store.app;
    if (app.isMapMode) {
        geometry.rotateX(-Math.PI / 2);
        geometry.translate(0, 0, height / 2);
    } else {
        geometry.rotateX(Math.PI);
        geometry.translate(0, height / 2, 0);
    }
    // MeshBasicMaterial MeshLambertMaterial
    let material = new THREE.MeshLambertMaterial({
        color: opts.color || 0xffcc00,
    });
    let mesh = new THREE.Mesh(geometry, material);
    // // 棱锥上在叠加一个棱锥
    let mesh2 = mesh.clone();
    if (app.isMapMode) {
        //2维地图模式
        mesh2.scale.z = 0.5;
        mesh2.position.z = height * (1 + mesh2.scale.z);
    } else {
        //3维模式
        mesh2.scale.y = 0.5;
        mesh2.position.y = height * (1 + mesh2.scale.y);
    }
    mesh2.rotateX(Math.PI);
    mesh.add(mesh2);
    let entity =  vjmap3d.Entity.fromObject3d(mesh);
    let pt = vjmap3d.toVector3(co);
    entity.position.copy(pt);
    entity.addTo(app);
    // 旋转
    let mod = entity.addModule(vjmap3d.BehaviorModule);
    let rotateId = "";
    const startRotation = () => {
        // 开始旋转
        stopRotation();
        rotateId = mod.addBehavior(vjmap3d.RotationBehavior.fromJSON({
            axis: opts.rotationAxis ?? (app.isMapMode ? new THREE.Vector3(0, 0, 1) : new THREE.Vector3(0, 1, 0)),
            speed: opts.rotationSpeed ?? 2,
            resetWhenFinalize: true // 结束时复位成原来角度
        }))
    }
    const stopRotation = () => {
        // 停止旋转
        if (rotateId) {
            mod.removeBehaviorById(rotateId);
            rotateId = ""
        }
    }
    if (opts.rotation !== false) {
        startRotation()
    }
    // 跳动
    opts.jumpHeightScale ??= 0.6;
    let jumpHeight = height * opts.jumpHeightScale;
    let anim;
    const startJump = () => {
        if (anim) {
            stopJump()
        }
        // 开始跳动
        anim = vjmap3d.animation({
            //from:  mesh.position,
            //to: new THREE.Vector3(mesh.position.x, mesh.position.y + dumpHeight, mesh.position.z),
            duration: opts.jumpDuration ?? 1000,
            onProgress(target, key, start, end, alpha, reversed) {
                if (app.isMapMode) {
                    mesh.position.z = pt.z + alpha * jumpHeight
                } else {
                    mesh.position.y = pt.y + alpha * jumpHeight
                }
            },
            yoyoForever: true
        })
    }
    const stopJump = () => {
        // 停止跳动
        if (!anim) return;
        app.tweenManager.stopById(anim.id)
        anim = null;
        mesh.position.copy(pt);
    }
    if (jumpHeight != 0) {
        startJump()
    }
    return {
        entity,
        startRotation,
        stopRotation,
        startJump,
        stopJump
    };
}
// 创建立体光墙
const createWall = (pts /*[number, number][]*/, opts /*{
    height?: number;
    flyline?: boolean;
    texture1?: string;
    repeatX?: number;
    repeatY?: number;
    color1?: THREE.ColorRepresentation;
    offsetX?: number;
    offsetY?: number;
    texture2?: string;
    color2?: THREE.ColorRepresentation;
    opacity?: number
 }*/, app /*vjmap3d.App*/) => {
    opts ??= {};
    let model = new THREE.Group(); //声明一个组对象,用来添加加载成功的三维场景
    let c = [...pts];
    let geometry = new THREE.BufferGeometry(); //声明一个空几何体对象
    let posArr = [];
    let uvrr = [];
    let h = opts.height || 1; // 围墙拉伸高度
    app ??= vjmap3d.Store.app;
    for (let i = 0; i < c.length - 1; i++) {
        if (app.isMapMode) {
            // 二维地图模式
            posArr.push(c[i][0], c[i][1], 0, c[i + 1][0], c[i + 1][1], 0, c[i + 1][0], c[i + 1][1], h);
            posArr.push(c[i][0], c[i][1], 0, c[i + 1][0], c[i + 1][1], h, c[i][0], c[i][1], h);
        } else {
            posArr.push(c[i][0], 0, c[i][1], c[i + 1][0], 0, c[i + 1][1], c[i + 1][0], h, c[i + 1][1]);
            posArr.push(c[i][0], 0, c[i][1], c[i + 1][0], h, c[i + 1][1], c[i][0], h, c[i][1]);
        }
        // 所有点展开  x方向从零到1   所有点生成的矩形铺满一张纹理贴图
        uvrr.push(i / c.length, 0, i / c.length + 2 / c.length, 0, i / c.length + 2 / c.length, 1);
        uvrr.push(i / c.length, 0, i / c.length + 2 / c.length, 1, i / c.length, 1);
    }
    // 设置几何体attributes属性的位置position属性
    geometry.attributes.position = new THREE.BufferAttribute(new Float32Array(posArr), 3);
    // 设置几何体attributes属性的位置uv属性
    geometry.attributes.uv = new THREE.BufferAttribute(new Float32Array(uvrr), 2);
    geometry.computeVertexNormals();
    if (opts.flyline !== false) {
        let texture = vjmap3d.ResManager.loadTexture(opts.texture1 ?? env.assetsPath + "textures/wall1.png");
        // 设置阵列模式为 RepeatWrapping
        texture.wrapS = THREE.RepeatWrapping
        texture.wrapT = THREE.RepeatWrapping
        texture.repeat.x = opts.repeatX || 3;// x方向阵列
        texture.repeat.y = opts.repeatY || 3;// y方向阵列
        let material = new vjmap3d.FlowMaterial({
            color: new THREE.Color(opts.color1 || 0xffff00),
            uvOffset: new THREE.Vector2(opts.offsetX || -1, opts.offsetY || 0),
            uvScale: new THREE.Vector2(texture.repeat.x, texture.repeat.y), // repeat
            map: texture,
            side: THREE.DoubleSide, //两面可见
            transparent: true, //需要开启透明度计算,否则着色器透明度设置无效
        }, {time: app.commonUniforms.time});
        let mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
        model.add(mesh);
    }
    let texture2 =  vjmap3d.ResManager.loadTexture(opts.texture2 ??  env.assetsPath + "textures/wall2.png");
    let material2 = new THREE.MeshLambertMaterial({
        color: opts.color2 || 0x00ffff,
        map: texture2,
        side: THREE.DoubleSide, //两面可见
        transparent: true, //需要开启透明度计算,否则着色器透明度设置无效
        opacity: opts.opacity || 0.5,//整体改变透明度
        depthTest: false,
    });
    let mesh2 = new THREE.Mesh(geometry, material2); //网格模型对象Mesh
    model.add(mesh2);
    let entity =  vjmap3d.Entity.fromObject3d(model);
    entity.addTo(app);
    return entity
}
// 创建波动光墙
const createWaveWall = (pt /*[number, number, number] | THREE.Vector3*/, opts /* {
    size?: number;
    height?: number;
 }*/, app /*vjmap3d.App*/) => {
    opts ??= {};
    app ??= vjmap3d.Store.app;
    let model = new THREE.Group(); //声明一个组对象
    // 矩形平面网格模型设置背景透明的png贴图
    let len = opts.size ?? 10;
    let height = opts.height ?? len / 10;
    const geometry = new THREE.CylinderGeometry(len, len, height, 32, 1, true);
    geometry.translate(0, height / 2, 0);
    if (app.isMapMode) {
        // 二维地图模式
        geometry.rotateX(Math.PI / 2.0);
    }
    let texture = vjmap3d.ResManager.loadTexture(opts.texture ?? env.assetsPath + "textures/wavewall.png")
    let material = new THREE.MeshLambertMaterial({
        color: opts.color || 0x00ffff,
        map: texture,
        side: THREE.DoubleSide, //两面可见
        transparent: true, //需要开启透明度计算,否则着色器透明度设置无效
        opacity: opts.opacity || 0.5,//整体改变透明度
        depthTest: false,
    });
    let mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
    model.add(mesh); //网格模型添加到model中
    model.position.copy(vjmap3d.toVector3(pt));
    let entity =  vjmap3d.Entity.fromObject3d(model);
    entity.addTo(app);
    entity.addAnimationAction({
        duration: 2000,
        repeatForever: true
    }, ({ alpha}) => {
        model.scale.set(alpha, alpha, alpha)
    })
    return entity
}
// 获取地图范围
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
// 查询要绘制的光墙线坐标
let query = await svc.conditionQueryFeature({
    condition: `objectid='9BE'`, // 只需要写sql语句where后面的条件内容,字段内容请参考文档"服务端条件查询和表达式查询"
    // 查询所有文字(包括单行文本,多行文本、块注记文字,属性文字) 具体类型数字参考文档"服务端条件查询和表达式查询-支持的cad实体类型"
    // condition: `name='12' or name='13' or name='26' or name='27'`,
    fields: ""
})
let linePoints = [];
if (query.error) {
    showError(query.error)
    return
} else {
    if (query.recordCount > 0) {
        let points = query.result[0].points; // 得到点坐标序列
        linePoints = points.split(";").map(p => vjmap.GeoPoint.fromString(p));
        // 闭合
        linePoints.push(linePoints[0]);
        linePoints = linePoints.map(p => vjmap3d.toWorld(p).toArray())
        createWall(linePoints, {
            height: vjmap3d.toDist(mapExtent.width() / 20)
        }, app)
    }
}
let c = mapExtent.center();
linePoints[0] = vjmap3d.toWorld(c).toArray();
for(let i = 0; i < linePoints.length; i++) {
    if (i != 0) {
        // 增加从中点至坐标点的飞线
        vjmap3d.createFlyline({
            source: [linePoints[0][0], linePoints[0][1], 0],
            target: [linePoints[i][0], linePoints[i][1], 0],
            count: 1000,
            range: 200,
            height: vjmap3d.toDist(mapExtent.width() / 5),
            color: vjmap.randomColor(),
            color2: vjmap.randomColor(),
            size: 5000
        }, app);
    }
    let pt = linePoints[i];
    pt.z = 1000;
    // 创建一个四棱锥
    createConeMesh(pt, {
        size: 10240,
        color: vjmap.randomColor()
    }, app);
}
// 创建一个波动光圈
createWaveWall(linePoints[0], {
    color: "#ffff00",
    size: vjmap3d.toDist(mapExtent.width() / 10),
    height: vjmap3d.toDist(mapExtent.width() / 20),
}, app);
// 增加烟花背景
for(let i = 0; i < 20; i++) {
    let pt = vjmap3d.toWorld(mapExtent.randomPoint().toArray());
    vjmap3d.createFlyline({
        source: [pt.x, pt.y, 0],
        target: [pt.x, pt.y, vjmap3d.toDist(mapExtent.width() / 10)],
        count: 1000,
        range: 500,
        height: 0,
        color: vjmap.randomColor(),
        color2: vjmap.randomColor(),
        speed: 1,
        size: 3,
        opacity: 1.0,
    }, app);
}


## 视频图层 ```js map.addSource("butterfly_src", { type: 'video', urls: ["https://vjmap.com/static/assets/video/butterfly.mp4"], coordinates: map.toLngLat(mapBounds.toPointArray()) }); map.addLayer({ 'id': 'video_layer', 'type': 'raster', 'source': 'butterfly_src' }); let video = map.getSource('butterfly_src'); // 播放 // video.play(); // 暂停 // video.pause(); ```
// --热力图层--
// 地图服务对象
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: 'sys_zp', // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    message.error(res.error)
}
// 获取地图范围
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapExtent);
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapExtent.center()), // 设置地图中心点
    zoom: 4, // 设置地图缩放级别
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
// 根据地图本身范围缩放地图至全图显示
map.fitMapBounds();
await map.onLoad();
const mapBounds = map.getGeoBounds(0.1);
map.addSource("butterfly_src", {
    type: 'video',
    urls: ["https://vjmap.com/static/assets/video/butterfly.mp4"],
    coordinates: map.toLngLat(mapBounds.toPointArray())
});
map.addLayer({
    'id': 'video_layer',
    'type': 'raster',
    'source': 'butterfly_src'
});
let video = map.getSource('butterfly_src');
// 播放
// video.play();
// 暂停
// video.pause();


--- # 图层操作 ## 增加图层 先增加数据源,再根据数据源增加相应的图层。如增加`wms`栅格图层 ```js map.addSource('wms-test-source', { 'type': 'raster', 'tiles': [ wmsurl ], 'tileSize': 256 }); map.addLayer({ 'id': 'wms-test-layer', 'type': 'raster', 'source': 'wms-test-source', 'paint': { "raster-opacity": 1 } } ); ``` [在线示例天地图与CAD图叠加](https://vjmap.com/demo/#/demo/map/web/04webWms) ```js // 创建为"ids"的geojson数据源 map.addGeoJSONSource('ids'); // 创建关联"ids"的图层id为 'ids-circle'的圆图层 map.addCircleLayer('ids-circle', 'ids', { // 根据hover不同的状态设置不同的颜色值 circleColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', '#FFA0FD'], // 根据hover不同的状态设置不同的宽度值 circleStrokeWidth: ['case', ['to-boolean', ['feature-state', 'hover']], 2, 1], }); // 设置图层的圆的半径 map.setCircleRadius(['ids-circle'], 12); // 设置鼠标位于此图层上时,鼠标改变为 'pointer‘ 形状 map.hoverPointer(['ids-circle']); // 设置可以改变此图层的 hover state,用于根据hover不同的状态设置不同的值 map.hoverFeatureState('ids-circle'); // 随机生成一些geojson的点 let data = mapBounds.randomGeoJsonPointCollection(1000); // 设置数据源数据 map.setData('ids', map.toLngLat(data)) ```
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: "sys_hello", // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapLightStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    showError(res.error)
    return;
}
// 获取地图范围
let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapBounds);
//
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapBounds.center()), // 设置地图中心点
    zoom: 1, // 设置地图缩放级别
    pitch: 0,
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();
// 创建为"ids"的geojson数据源
map.addGeoJSONSource('ids');
// 创建关联"ids"的图层id为 'ids-circle'的圆图层
map.addCircleLayer('ids-circle', 'ids', {
    // 根据hover不同的状态设置不同的颜色值
    circleColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', '#FFA0FD'],
    // 根据hover不同的状态设置不同的宽度值
    circleStrokeWidth: ['case', ['to-boolean', ['feature-state', 'hover']], 2, 1],
});
// 设置图层的圆的半径
map.setCircleRadius(['ids-circle'], 12);
// 设置鼠标位于此图层上时,鼠标改变为 'pointer‘ 形状
map.hoverPointer(['ids-circle']);
// 设置可以改变此图层的 hover state,用于根据hover不同的状态设置不同的值
map.hoverFeatureState('ids-circle');
// 随机生成一些geojson的点
let data = mapBounds.randomGeoJsonPointCollection(1000);
// 设置数据源数据
map.setData('ids', map.toLngLat(data))
setInterval(() => {
    data = mapBounds.randomGeoJsonPointCollection(Math.floor(Math.random() * 1000));
    // 模拟更新数据
    map.setData('ids', map.toLngLat(data))
}, 5000)


其他增加图层方法 ```ts /** 添加一个图层,给定 id、来源、类型和属性。 * @param id * @param source * @param type * @param props * @param before */ addLayerEx( id: string, source: string, type: string, props: {}, before: string | null | undefined ): SourceBoundUtils; /** 在某个图层之前添加一个图层,给定 id、来源、类型和属性。 * @param id * @param source * @param type * @param props * @param before */ addLayerBefore( id: string, source: SourceOrData, type: string, props: {}, before?: string ): SourceBoundUtils | null | undefined; /** 添加一个类型为“line”的图层。 * @param id * @param props * @param before * @param source */ addLineLayer(id: string, source: string, props: LineLayerStyleProp, before?: string): void; /** 添加一个类型为“fill”的图层。 * @param id * @param props * @param before * @param source */ addFillLayer(id: string, source: string, props: FillLayerStyleProp, before?: string): void; /** 添加一个类型为“circle”的图层。 * @param id * @param props * @param before * @param source */ addCircleLayer( id: string, source: string, props: CircleLayerStyleProp, before?: string ): void; /** 添加一个类型为“符号”的图层。 * @param id * @param props * @param before * @param source */ addSymbolLayer( id: string, source: string, props: SymbolLayerStyleProp, before?: string ): void; /** 添加一个类型为`video`的层。 * @param id * @param props * @param before * @param source */ addVideoLayer(id: string, source: string, props: object, before?: string): void; /**添加一个类型为 `raster` 的图层。 * @param id * @param props * @param before * @param source */ addRasterLayer( id: string, source: string, props: RasterLayerStyleProp, before?: string ): void; /** 添加一个类型为“FillExtrusion”的层。 * @param id * @param props * @param before * @param source */ addFillExtrusionLayer( id: string, source: string, props: FillExtrusionLayerStyleProp, before?: string ): void; /** 添加一个类型为“Heatmap”的图层。 * @param id * @param props * @param before * @param source */ addHeatmapLayer( id: string, source: string, props: HeatmapLayerStyleProp, before?: string ): void; /** 添加一个类型为“hillshade”的图层。 * @param id * @param props * @param before * @param source */ addHillshadeLayer( id: string, source: string, props: HillshadeLayerStyleProp, before?: string ): void; /** 添加一个 `raster` 源 @param sourceId ID of the new source. @param {object} props Properties defining the source, per the style spec. */ addRasterSource(sourceId: string, props: RasterSourceSpecification): SourceBoundUtils; /** 添加一个 `raster-dem` 源 @param sourceId ID of the new source. @param {object} props Properties defining the source, per the style spec. */ addRasterDemSource(sourceId: string, props: RasterDEMSourceSpecification): SourceBoundUtils; /** 添加一个 `image` 源 @param sourceId ID of the new source. @param {object} props Properties defining the source, per the style spec. */ addImageSource(sourceId: string, props: ImageSourceSpecification): SourceBoundUtils; /** 添加“Video”源 @param sourceId ID of the new source. @param {object} props Properties defining the source, per the style spec. */ addVideoSource(sourceId: string, props: VideoSourceSpecification): SourceBoundUtils; ``` ## 删除图层 `removeLayer` 从地图的样式中删除具有给定 ID 的图层。如果不存在这样的层,error则会触发一个事件。 ```js if (map.getLayer('state-data')) map.removeLayer('state-data'); ``` `removeLayerEx` 移除一个或多个图层,如果不存在,不会报错 ```js map.removeLayerEx('state-data'); map.removeLayerEx(['state-data', "layer2"]); ``` `removeSourceEx` 移除一个或多个源,首先移除所有依赖于它们的层。如果源不存在,则不会提示错误 ```js map.removeSourceEx('ids'); map.removeLayerEx(['source1', "source2"]); ``` ## 显示图层 通过设置布局`visibility`属性`显示图层` ```js map.setLayoutProperty(layerid, "visibility", "visible"); ``` 或通过图层id显示 ```js map.show(layerid); ``` 或 ```js map.toggle(layerid, true) ``` 或使数据源相关的图层都可见 ```js map.showSource(sourceid); ``` 或 ```js map.toggleSource(sourceid, true) ``` ## 隐藏图层 通过设置布局`visibility`属性`隐藏图层` ```js map.setLayoutProperty(layerid, "visibility", "none"); ``` 或通过图层id隐藏 ```js map.hide(layerid); ``` 或 ```js map.toggle(layerid, false) ``` 或使数据源相关的图层都隐藏 ```js map.hideSource(sourceid); ``` 或 ```js map.toggleSource(sourceid, false) ``` ## 设置图层属性 ```js // 它们都同时作用于多个层: map.setLineWidth([ 'mylayer' , 'mylayer-highlight' ], 4 ); map.setLineOpacity( /^ border- / , 0 ); map.setFillColor( layer => layer.source === 'farms' , 'green' ); // 还有一个更熟悉的 setProperty() 形式。 map.setProperty( 'mylayer' , 'line-width' , 3 ); // 更新源数据的更简单方法: map.setData( 'mysource' , data); // 您可以省略 data 参数以清除 GeoJSON 源: map.setData( 'mysource' ); // 一次更新多个过滤器。 map.setFilter([ 'buildings-fill' , 'buildings-outline' , 'buildings-label' ], [...]); ``` ## 图层事件 通过`map`事件监听,如 ```js map.on("click", layerid, e => { console.log(e) }) ``` 或 ```js // clickLayer() 类似于 .on('click)',但可以接受一个数组并向事件中添加一个 'features' 成员用于点击的内容。 map.clickLayer([ 'towns' , 'town-labels' ], e => panel.selectedId = e.features[ 0 ].id); ``` ```js // 悬停在此图层上时使用鼠标“手指”光标。 map.hoverPointer( 'mylayer' ); // 如果您传递多个图层,它会正确处理从一个图层移动到另一个图层 // 悬停在该图层上时使用鼠标“手指”光标。 map.hoverPointer([ 'regions-border' , 'regions-fill' ]); // 当鼠标移动到该层的特征上时,将“悬停”特征状态设置为真或假。 // 要求特性有一个 `id`。 map.hoverFeatureState( 'mylayer' ); // 想要将悬停功能状态应用于不同的源? // 例如,您将鼠标悬停在标签上,但想要突出显示周围的边界。 map.hoverFeatureState( 'town-labels' , 'boundaries' , 'town-boundaries' ); // 您还可以添加其他事件处理程序: map.hoverFeatureState( 'mylayer' , 'mysource' , 'mysourcelayer' , e => console.log(`Entered ${e.features[0].id}`), e => console.log(`Left ${e.oldFeatureid}`); // 当一个功能悬停或点击时显示一个弹出窗口。 // 第三个参数是一个选项对象,传递给 Popup 构造函数。 // 回调被调用为: (feature, popup) => htmlString map.hoverPopup( 'mylayer' , f => `

${f.properties.Name}

${f.properties.Description} ` , { anchor : 'left' }); map.clickPopup( 'mylayer' , f => `

${f.properties.Name}

${f.properties.Description} ` , { maxWidth : 500 }); // clickOneLayer 按顺序测试多个层,在第一个命中的 上触发回调。回调被传递 { 功能,功能,层,事件 }。 map.clickOneLayer([ 'town-labels' , 'state-boundaries' ], e => { if (e.layer === 'town-labels') { setView('town'); panel.selectedId = e.features[0].id; } else if (e.layer === 'state-boundaries') { setView('state'); panel.selectedId = e.features[0].id; } }); // 可选地传入一个额外的回调,该回调为错过所有图层的点击而触发: map.clickOneLayer(['town-labels', 'state-boundaries'], e => {...}, e => { console.log('Missed everything'); }); // 所有这些函数都返回一个“撤消”函数,删除添加的处理程序: const remove = map.hoverPopup( 'mylayer' , showPopupFunc); //... remove(); // 不再有悬停弹出 ``` --- # 地图控件 ### 目前支持的控件 目前支持的控件有: - 导航条控件 `NavigationControl` - 比例尺控件 `ScaleControl` - 全屏控件 `FullscreenControl` - 鼠标位置控件 `MousePositionControl` - 小地图控件(鹰眼控件) `MiniMapControl` - 按钮组控件 `ButtonGroupControl` ### 示例
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: env.exampleMapId, // 地图ID
    // @ts-ignore
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapLightStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    showError(res.error)
    return;
}
// 获取地图范围
let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapBounds);
//
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapBounds.center()), // 设置地图中心点
    zoom: 2, // 设置地图缩放级别
    pitch: 45,
    antialias: true, // 反锯齿
    renderWorldCopies: false // 不显示多屏地图
});
//
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();
// 比例尺控件
let scaleControl =  new vjmap.ScaleControl();
map.addControl(scaleControl, "bottom-left");
// 导航条控件
let navigationControl = new vjmap.NavigationControl();
map.addControl(navigationControl, "top-right");
// 全屏控件
let fullScreenControl = new vjmap.FullscreenControl();
map.addControl(fullScreenControl, "bottom-right");
// 鼠标位置控件
let mousePositionControl =  new vjmap.MousePositionControl();
map.addControl(mousePositionControl, "top-left");
map.addControl(new vjmap.MiniMapControl({
    containerStyle: {
    background: "#31346a85",
    transform: "scale(0.6)",
    transformOrigin: "0% 100%"
    // 放置位置不同,transformOrigin也应相应的修改
    //'top-right'
    //transformOrigin: "100% 0%"
    //'top-left'
    //transformOrigin: "0% 0%"
    //'bottom-right'
    //transformOrigin: "100% 100%"
    //'bottom-left'
    //transformOrigin: "0% 100%"
    }
}), 'bottom-left');


#### ```js // 比例尺控件 let scaleControl = new vjmap.ScaleControl(); map.addControl(scaleControl, "bottom-left"); // 导航条控件 let navigationControl = new vjmap.NavigationControl(); map.addControl(navigationControl, "top-right"); // 全屏控件 let fullScreenControl = new vjmap.FullscreenControl(); map.addControl(fullScreenControl, "bottom-right"); // 鼠标位置控件 let mousePositionControl = new vjmap.MousePositionControl(); map.addControl(mousePositionControl, "top-left"); ``` ### 小地图控件(鹰眼控件) ```js map.addControl(new vjmap.MiniMapControl({ containerStyle: { background: "#31346a85", transform: "scale(0.6)", transformOrigin: "100% 0%" // 放置位置不同,transformOrigin也应相应的修改 //'top-right' //transformOrigin: "100% 0%" //'top-left' //transformOrigin: "0% 0%" //'bottom-right' //transformOrigin: "100% 100%" //'bottom-left' //transformOrigin: "0% 100%" } }), 'top-right'); ``` ### 按钮组控件
// --按钮组控件--
// 地图服务对象
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: env.exampleMapId, // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    message.error(res.error)
}
// 获取地图范围
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapExtent);
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapExtent.center()), // 设置地图中心点
    zoom: 2, // 设置地图缩放级别
    pitch: 0, // 倾斜角度
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
// 根据地图本身范围缩放地图至全图显示
//map.fitMapBounds();
await map.onLoad();
const mapBounds = map.getGeoBounds(0.6);
let options = {};
options.buttons = [
    {
        id: "zoomFit",
        title: "全图显示",
        style: {
            "background-image": `url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAALNJREFUWEftltENgCAMRGEwJtBRdAZ1Bh1FJ2AwTIwkBqw56EcxKd+lHK9Ne9YIHyv8vlEBGQE/HBNVFrd1C1Kykhy5gHEPpIC1h0rmC3LQAoKdUyEsAjZc+VzyCVJAGoig/4qJVH4g4G5CFDdKJjZmmhdqKvSRmjgVoASUgBJQAu0RoJZGzZx/3oGXEbW32QJul/QDPxD9nLQle0OO2jSeKZW25dxmK73f3hwo/QE3XpzACTqIZiEG/iYrAAAAAElFTkSuQmCC')`,
            "background-size": "80% 80%"
        },
        onActivate: (ctx) => {
            map.fitMapBounds();
        }
    },
    {
        id: "zoomFit",
        title: "允许地图右键旋转",
        style: {
            "background-image": `url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAllJREFUWEfFl1FOwzAMhu1wAcR4ZwwOwk4CuwWqkMYkVLgF4ySUe8Ao7xRxgDVB7prKCW6Sdkj0sW3qL/bv3ynCP1/4z/FhNMDx+c0Fh99ut+V3+VAO3dAggMlZdgUGlgAwFQMZUxjEQmv9lAqTBBANLG97XWu9ioEEAQ6n19MDpd6F768NQIkIH/TMGDhBYy4AkTLDs1OiUovP17uirzQOAAW0xELwEhBW1Vu+DtVZzBbCom9dB0CiMnW9rI1ZUABv5+tqkzf3U6/JLHsEgCv7fq31qVQOJwOTWUbpdgRmAG6/NvkqNTB/72iWLRHgtr1X1lrPfQgfwKEGY4rq/X4+JrhdwyGkzbgAuzYjCH4lqTmoC5ZZvxQOQKMDrZ99AFTqKaTkWIZCWfjVhq0OCkB4iSk+Fpg/7/TllTXJiKRA1KZKqUsEmKYYDu8KXoZRAJJHxAznTwH8Hm/cMNKurUE1Akel5lZTozLg9betUNCs+Jq9AaQZwT8qaWYQALVjrO1aiCUNpZTxy8tWbfIu804JWkryb1K26N1DWs9rQ2Mtudrkp/aZBLDz7sAEGwrBBeiL9dc4PlCKnJAGkjg8hgan9yezzO4eePp3+/Su2PAYCsBrHx1G9uN8LMf6OwTkj2Nee1ED9mY7lMg0mrMBQaQo3ROdM9r72rTXiHhHWPXSkayudRE6aEpHspBHBJ3Qz0S3w/b47RxKd9nqjmAWOjYjkqy4FRL9iMj/A7IQks6RSQD0fTZ+G6PqFR/CIlYmvjYZwA/If83G/paJPjC0z/d9f3QG9g1s1/8ATi1yMPW6z1YAAAAASUVORK5CYII=')`,
            "background-size": "80% 80%"
        },
        isCheckbox: true,
        checked: true,
        onActivate: (ctx, e) => {
            e.target.checked ? map.dragRotate.enable() : map.dragRotate.disable();
        }
    },
    {
        id: "pos",
        title: "定位到一个位置",
        text: "定位",
        onActivate: (ctx, e) => {
            map.flyTo({
                center: [10, 20],
                pitch: 30,
                zoom: 6,
                speed: 0.2,
                easing(t) {
                    return t;
                }
            });;
        }
    }
];
let btnGroupCtrl = new vjmap.ButtonGroupControl(options);
map.addControl(btnGroupCtrl, 'top-right');


### 自定义控件 #### 示例 ```js // 自定义显示缩放级的控件 class CustomShowZoomControl { constructor(options = {}) { this.style = options.style || {};// 样式 } _insertControl() { this.container = document.createElement("div"); this.container.classList.add("vjmap-ctrl"); this.container.style.border = this.style.border || "1px"; this.container.style.backgroundColor = this.style.backgroundColor || "#A0CFFF"; this.panel = document.createElement("div"); this._zoomend(); this.container.appendChild(this.panel); } _zoomend() { let strZoom = `当前级别: ${this.map.getZoom().toFixed(2)}` this.panel.innerHTML = strZoom; } onAdd(map) { this.map = map; this._insertControl(); this.map.on("zoomend", this._zoomend.bind(this)); return this.container; } onRemove() { this.map.off("zoomend", this._zoomend.bind(this)); this.container.parentNode.removeChild(this.container); } getDefaultPosition() { return "bottom-left"; } } let control = new CustomShowZoomControl({style: {border : "1px"}}) map.addControl(control); ``` --- # 地图事件 本章介绍 `唯杰地图` `vjmap` 事件类型的使用。 ## 事件绑定和取消 事件的绑定通过 map 实例的 on()方法实现,事件解绑移除通过 map 实例的 off()方法实现。 事件定义示例: ```js function customClick(e){ // 实现自定义单击事件 }) ``` 绑定自定义事件示例: ```js map.on("click", customClick); ``` 自定义事件解除绑定示例: ```js map.off("click", customClick); ``` 只监听一次特定事件。事件响应后将自动取消绑定 ```js map.once('click', (e) => { console.log('仅一次点击事件:', e.lngLat); }); ``` ## 地图常见事件 ### 地图加载事件 --- **load 事件**:地图对象创建后,开始加载地图资源,地图资源加载完成后触发 load 事件。 ```js map.on("load", function() { // 地图资源加载完成后触发 // 增加自定义数据源、自定义图层 }); ``` ### 地图鼠标事件 **mouseout 事件**:当鼠标移出地图时触发; ```js map.on("mouseout", function() { console.log("鼠标从地图上移出了"); }); ``` **2、mousedown 事件**:当鼠标按下时触发; ```js map.on("mousedown", function() { console.log("鼠标按键按下了"); }); ``` **3、mouseup 事件**:当鼠标按键释放时触发; ```js map.on("mouseup", function() { console.log("鼠标按键释放了"); }); ``` **4、mousemove 事件**:当鼠标在地图内移动时触发; ```js map.on("mousemove", function() { console.log("鼠标在地图内移动中"); }); ``` **5、click 事件**:鼠标在地图上任一位置单击时触发; ```js map.on("click", function() { console.log("鼠标单击地图了"); }); ``` **6、dblclick 事件**:鼠标在地图上任一位置双击时触发; ```js map.on("dblclick", function() { console.log("鼠标双击地图了"); }); ``` **7、contextmenu 事件**:鼠标在地图上任一位置右键点击时触发; ```js map.on("contextmenu", function() { console.log("鼠标右键点击地图了"); }); ``` ### 地图移动事件 --- **1、movestart 事件**:地图要开始移动,但还未移动时触发的事件,地图移动过程中不触发; ```js map.on("movestart", function() { console.log("地图要开始移动了"); }); ``` **2、move 事件**:地图移动过程中触发; ```js map.on("move", function() { console.log("地图在移动中"); }); ``` **moveend 事件**:地图移动一次结束时触发的事件,地图移动过程中不触发; ```js map.on("moveend", function() { console.log("地图移动结束了"); }); ``` ### 地图缩放事件 --- **zoomstart 事件**:当地图缩放开始时触发; ```js map.on("zoomstart", function() { console.log("地图要开始zoom缩放了"); }); ``` **zoom 事件**:地图缩放进行时触发; ```js map.on("zoom", function() { console.log("地图在zoom缩放中"); }); ``` **zoomend 事件**:地图缩放结束时触发; ```js map.on("zoomend", function() { console.log("地图zoom缩放结束了"); }); ``` ### 地图旋转事件 --- **rotatestart 事件**:当地图旋转开始时触发; ```js map.on("rotatestart", function() { console.log("地图要开始旋转了"); }); ``` **rotate 事件**:地图旋转进行时触发; ```js map.on("rotate", function() { console.log("地图在旋转进行中"); }); ``` **rotateend 事件**:地图旋转结束时触发; ```js map.on("rotateend", function() { console.log("地图旋转结束了"); }); ``` ### 地图俯仰事件 --- **pitchstart 事件**:当地图俯仰开始时触发; ```js map.on("pitchstart", function() { console.log("地图要开始俯仰了"); }); ``` **pitch 事件**:地图俯仰进行时触发; ```js map.on("pitch", function() { console.log("地图在俯仰进行中"); }); ``` **pitchend 事件**:地图俯仰结束时触发; ```js map.on("pitchend", function() { console.log("地图俯仰结束了"); }); ``` ### 地图触摸事件 --- **touchstart 事件**:手指放在地图上时触发; ```js map.on("touchstart", function() { console.log("手指开始触摸地图了"); }); ``` **touchend 事件**:手指从地图上移开时触发; ```js map.on("touchend", function() { console.log("手指触摸地图结束了"); }); ``` **touchcancel 事件**:手指从地图上移动中断时触发; ```js map.on("touchcancel", function() { console.log("手指触摸地图中断了"); }); ``` --- # 地图投影 地图投影根据地图底图不同,分为`几何投影` `GeoProjection` 和`经纬度投影` `LnglatProjection` 投影主要用于计算`图形几何坐标`转渲染用的`经纬度坐标` ## 以CAD为底图的几何投影 ```js // 得到图的元数据 let metadata = await svc.metadata(mapid, "v1"); // 根据图的范围字符串生成一个范围对象 let mapBounds = vjmap.GeoBounds.fromString(metadata.bounds); // 根据地图范围建立几何投影坐标系 let prj = new vjmap.GeoProjection(mapBounds); // 获取CAD图范围的中心点几何坐标 let center = mapBounds.center(); // 利用投影对象把CAD几何坐标转渲染用的经纬度坐标 let lngLat = prj.toLngLat(center); // 根据这个经纬度坐标创建一个marker加进地图中 new vjmap.Marker().setLngLat(lngLat).addTo(map); ``` ## 以互联网地图为底图的经纬度投影 对于以互联网地图为底图的经纬度投影,因为互联网地图本身就是经纬度坐标,所以`图形中的经纬度坐标`转渲染用的`经纬度坐标`是一致的。 ```js let prj = new vjmap.LnglatProjection(); let mapPoint = [106.31, 26.87]; let lngLat = prj.toLngLat(lngLat); // lngLat输出的值也是 [106.31, 26.87] ``` --- # 测量工具 CAD如今在各个领域均得到了普遍的应用并大大提高了工程技术人员的工作效率。在桌面端,AutoCAD测量工具已经非常强大;然后在Web端,如何准确、快速的对CAD图在Web进行测量呢? # 功能 - 能Web在线打开AutoCAD图形 - 测量距离 - 测量面积 - 测量角度 - 坐标标注 - 测量时能捕捉Web端CAD图形上面的坐标,提高准确度 - 测量时能对捕捉进行开关启用 - 测量时能启用正交模式 - 测量时能自定义右键菜单功能 - 能进行连续测量 - 测量结束后,能删除已测量的结果 # 效果 ## 测量距离 ![image-20220623192548087](https://vjmap.com/blogimages/image-20220623192548087.png) ## 测量面积 ![image-20220623192725011](https://vjmap.com/blogimages/image-20220623192725011.png) ## 测量角度 ![image-20220623193100555](https://vjmap.com/blogimages/image-20220623193100555.png) ## 坐标标注 ![image-20220623193145638](https://vjmap.com/blogimages/image-20220623193145638.png) ## 其他功能 在测量过程中,按`Alt`键可开启关闭捕捉;按`Ctrl`键可启用正交模式;按`退格键`可删除上一个点;按`ESC`键取消测量;按`Enter`键结束测量; 按`右键`弹出上下文菜单 ![image-20220623194559185](https://vjmap.com/blogimages/image-20220623194559185.png)
// --测距--
// 地图服务对象
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: env.exampleMapId, // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    message.error(res.error)
}
// 获取地图范围
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapExtent);
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapExtent.center()), // 设置地图中心点
    zoom: 2, // 设置地图缩放级别
    renderWorldCopies: false, // 不显示多屏地图
    doubleClickZoom: false // 禁用鼠标双击放大
});
// 关联服务对象和投影对象
map.attach(svc, prj);
// 根据地图本身范围缩放地图至全图显示
map.fitMapBounds();
const addStyle = cssStyle => {
    let style = `<style>${cssStyle }</style>`;
    let ele = document.createElement('div');
    ele.innerHTML = style;
    document.getElementsByTagName('head')[0].appendChild(ele.firstElementChild)
}
// 增加一个popup自定义的样式
addStyle(`
                .custom-popup .vjmapgis-popup-content {
                    background-color: #0EFFC2;
                    opacity: 0.7;
                }
                .custom-popup .vjmapgis-popup-tip {
                    border-top-color: #0EFFC2;
                    opacity: 0.7;
                }
            `)
// 跟随鼠标动的弹框
let popup = null;
const setPopupText = text => {
    if (text) {
        if (!popup) {
            popup = new vjmap.Popup({
                className: "custom-popup",
                closeOnClick: false,
                closeButton: false
            })
                .setHTML(text)
                .setMaxWidth("500px")
                .trackPointer()
                .addTo(map)
        }
        else {
            popup.setHTML(text);
        }
    } else {
        // 如果为空时,则删除popup
        if (popup) {
            popup.setLngLat([0,0]); // 取消trackPointer
            popup.remove();
            popup = null;
        }
    }
}
let snapObj = {}; // 设置的捕捉的实体
// 测量距离
const measureDist = async ()=> {
    let isDrawing = false;
    let line = await vjmap.Draw.actionDrawLineSting(map, {
        api: {
            getSnapFeatures: snapObj //要捕捉的数据项在后面,通过属性features赋值
        },
        updatecoordinate: (e) => {
            if (!e.lnglat) return;
            /*
            // 如果需要指定事先指定起点,可用如下代码
            if (e.state.line.coordinates.length == 1) {
                // 第一个点时自动加最开始的点做为起点
                e.state.line.coordinates.unshift(map.toLngLat([
                    587662045.9814225, // cad坐标x
                    3103973655.658176 // cad坐标y
                ]))
                e.state.currentVertexPosition = 1;
            }*/
            isDrawing = true;
            const co = map.fromLngLat(e.feature.coordinates[e.feature.coordinates.length - 1]);
            let html = `当前坐标:<span style="color: #ff0000"> ${co.x.toFixed(2)}, ${co.y.toFixed(2)}</span>`;
            if (e.feature.coordinates.length == 1) {
                html += "<r/>请指定要测量的第一点的坐标位置"
            } else {
                let len = e.feature.coordinates.length;
                html += `<br/>按Alt键取捕捉; Ctrl键启用正交; 退格键删除上一个点`
                html += `<br/>距上一点距离: <span style="color: #ff0000">${getDist([e.feature.coordinates[len - 2], e.feature.coordinates[len -1]])}</span>`
                html += `;  当前总的距离: <span style="color: #ff0000">${getDist(e.feature.coordinates)}</span>`
            }
            setPopupText(html)
        },
        contextMenu: (e) => {
            new vjmap.ContextMenu({
                event: e.event.originalEvent,
                theme: "dark", //light
                width: "250px",
                items: [
                    {
                        label: '确认',
                        onClick: () => {
                            // 给地图发送Enter键消息即可取消,模拟按Enter键
                            map.fire("keyup", {keyCode:13})
                            setPopupText("");
                        }
                    },
                    {
                        label: '取消',
                        onClick: () => {
                            // 给地图发送ESC键消息即可取消,模拟按ESC键
                            map.fire("keyup", {keyCode:27})
                            setPopupText("");
                        }
                    },{
                        label: '删除上一个点',
                        onClick: () => {
                            // 给地图发送退格键Backspace消息即可删除上一个点,模拟按Backspace键
                            map.fire("keyup", {keyCode:8})
                        }
                    },{
                        label: '结束测距',
                        onClick: () => {
                            // 给地图发送ESC键消息即可取消,模拟按ESC键
                            map.fire("keyup", {keyCode:27})
                            isDrawing = false;
                            setPopupText("");
                        }
                    }
                ]
            });
        }
    });
    if (line.cancel) {
        setPopupText("");
        return {
            cancel: true,
            exit: isDrawing === false // 如果还没有绘制,就取消的话,就结束测距
        };// 取消操作
    }
    let color = vjmap.randomColor();
    let polyline = new vjmap.Polyline({
        data: line.features[0].geometry.coordinates,
        lineColor: color,
        lineWidth: 2
    });
    polyline.addTo(map);
    addMarkersToLine(line.features[0].geometry.coordinates, color, polyline.sourceId);
    return {
        polyline
    };
}
// 测量距离循环,直至按ESC键取消,否则测量完一条后,继续测量下一条
const measureDistLoop = async ()=> {
    while(true) {
        let res = await measureDist();
        if (res.exit === true) break;
    }
}
measureDistLoop();
const clearAll  = (map)=> {
    // 清空绘制的图层,如果怕误删除,每次绘制完成,把相关的 sourceId和marker记录下来。然后依次删除
    // @ts-ignore
    Object.keys(map.getStyle().sources).filter(e=>e.indexOf("custom_") == 0).forEach(e=> map.removeSourceEx(e));
    // 清除所有marker
    map.removeMarkers();
}
// 给线的加个点加个测量的结果值
const addMarkersToLine = (coordinates, color, sourceId) => {
    let markerTexts = [];
    for(let i = 1; i < coordinates.length; i++) {
        let text = new vjmap.Text({
            text: getDist(coordinates.slice(0, i + 1)),
            anchor: "left",
            offset: [3, 0], // x,y 方向像素偏移量
            style:{     // 自定义样式
                'cursor': 'pointer',
                'opacity': 0.8,
                'padding': '6px',
                'border-radius': '12px',
                'background-color': color,
                'border-width': 0,
                'box-shadow': '0px 2px 6px 0px rgba(97,113,166,0.2)',
                'text-align': 'center',
                'font-size': '14px',
                'color': `#${color.substr(1).split("").map(c => (15 - parseInt(c,16)).toString(16)).join("")}`,
            }
        });
        text.setLngLat(coordinates[i]).addTo(map);
        markerTexts.push(text);
    }
    // 给第一个点加一个marker用来删除
    const deletePng = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAbNJREFUWEftlrFOAkEQhv9Z4XgJOCrlTgp5ABOhMBoTCws7EzUaeAx5A0sDiYmFxpLGaAkU2KqFArHi4CU4cMccAQN6HHcIwZi7cnd25tt/5maHsOCPFhwfPoAnBarhQFIIccjAkV3qCMhKKct6q1tym1pPAHVVOWMg6+ScgKuYYR7PBaCmKmw5XgLiy4b5NhzkXVVWP4BXa00zTNcXc21oOR4AjAswaX9M2n4u1yLBIoiSbmV0Zcdc0pqd1HdbWwUWDuDqRjMy8lQDM4o54sYRoJcKq6qHclePKnuScQ0pd/RWt2ztVyOhLUFckIwTvWneDiLYnXdVA18O+r/dcNXXIsozCGsMzuhGJ98DUINpAuUAqmhGe33k/JjiG9g4K2ADUFdDOQan7QAIlI8Z7YwP4CvgK/C/FahFlQcwtm0bEeNFa5qJuSpgdT2AzkFyU290H3udMBzYgBD3gnAQa5iFuQJ4eZB6A8ovW/ETgARLmfIyaPZVSZIQRTBdas326Thwx7fAzRA6SREB2l0x2ndTAfTnwH0CrGk4PinY6D5VQHyhNcwbp3N/eyDxduPprH0FPgHEyH4wGIHUYwAAAABJRU5ErkJggg==";
    let el = document.createElement('div');
    el.className = 'marker';
    el.style.backgroundImage =
        `url(${deletePng})`;
    el.style.width = '20px';
    el.style.height = '20px';
    el.style.backgroundSize = '100%';
    el.style.cursor = "pointer";
    el.addEventListener('click', function (e) {
        map.removeSourceEx(sourceId); // 删除绘制的线
        markerTexts.forEach(m => m.remove());
        markerTexts = [];
        // 多点了下,给地图发送退格键Backspace消息即可删除上一个点,模拟按Backspace键
        map.fire("keyup", {keyCode:8})
    });
    // Add markers to the map.
    let deleteMarker = new vjmap.Marker({
        element: el,
        anchor: 'right'
    });
    deleteMarker.setLngLat(coordinates[0])
        .setOffset([-5, 0])
        .addTo(map);
    markerTexts.push(deleteMarker)
    // 把坐标加进捕捉数组中。
    addSnapCoordinates(coordinates);
}
// 得到距离值
const getDist = (coordinates) => {
    let result = vjmap.Math2D.lineDist(map.fromLngLat(coordinates));
    let unit = "m";
    if (result >= 1000) {
        result /= 1000;
        unit = "km";
    } else if (result < 0.01) {
        result *= 100;
        unit = "cm";
    }
    return result.toFixed(2) + " " + unit;
}
// 增加捕捉点
const addSnapCoordinates = (coordinates) => {
    snapObj.features.push({
        type: "Feature",
        geometry: {
            type: "LineString",
            coordinates: [...coordinates]
        }
    })
}
// 获取捕捉点
if (map.hasVectorLayer()) {
    // 矢量样式
    // 矢量样式获取捕捉点
    snapObj.features = map.queryRenderedFeatures({layers: ['vector-layer-lines']})
} else {
    //栅格样式去服务器获取坐标点数据
    // 栅格样式获取捕捉点
    // 查询所有坐标数据,字段含义可参考https://vjmap.com/guide/svrStyleVar.html
    let res = await svc.conditionQueryFeature({ fields:"s3", condition:"s3 != ''", limit: 100000})
    res = res.result.map(e => e.s3.split(";"))
    snapObj.features = []
    for(let item of res) {
        let coordinates = []
        for(let pt of item) {
            const p = pt.split(",")
            if (p.length >= 2) {
                coordinates.push(map.toLngLat([+p[0], +p[1]]))
            }
        }
        if (coordinates.length == 1) {
            snapObj.features.push({
                type: "Feature",
                geometry: {
                    type: "Point",
                    coordinates: coordinates[0]
                }
            })
        }
        else if (coordinates.length > 1) {
            snapObj.features.push({
                type: "Feature",
                geometry: {
                    type: "LineString",
                    coordinates: coordinates
                }
            })
        }
    }
}


# 代码实现 上面的案例代码已开源。访问 ([唯杰地图云端图纸管理平台](https://vjmap.com/app/cloud) https://vjmap.com/app/cloud) ,点击`下载此案例源码`即可。 ```typescript import vjmap, { Map } from 'vjmap' import { sleep } from '~/utils/ui'; import { getMapSnapPoints } from './snap'; let snapObj: any; // 设置的捕捉的实体 let curMeasureCmd: string; // 当前测量命令 export async function runMeasureCmd(map: Map, cmd: string) { curMeasureCmd = cmd; if (cmd != "measureCancel") { // 先结束当前测量 await measureCancel(map); if (!snapObj) { // 获取地图上的捕捉点 snapObj = {}; getMapSnapPoints(map, snapObj); } } switch (cmd) { case "measureDist": measureDistLoop(map, snapObj); break; case "measureArea": measureAreaLoop(map, snapObj); break; case "measureAngle": measureAngleLoop(map, snapObj); break; case "measureCoordinate": measureCoordinateLoop(map, snapObj); break; case "measureCancel": await measureCancel(map); break; } } // 结束绘制 const measureCancel = async (map: Map)=> { // 连续发送取消键,第一次取消当前绘制,第二次退出测量 map.fire("keyup", {keyCode:27}); await sleep(100); map.fire("keyup", {keyCode:27}); await sleep(100); map.setIsInteracting(false); // 没有进行交互操作了 } let popup: vjmap.Popup | null; const setPopupText = (text: string, map: Map) => { if (text) { if (!popup) { popup = new vjmap.Popup({ className: "my-custom-popup", closeOnClick: false, closeButton: false }) .setHTML(text) .setMaxWidth("500px") .trackPointer() .addTo(map) } else { popup.setHTML(text); } } else { // 如果为空时,则删除popup if (popup) { popup.setLngLat([0,0]); // 取消trackPointer popup.remove(); popup = null; } } } // 测量距离循环,直至按ESC键取消,否则测量完一条后,继续测量下一条 const measureDistLoop = async (map: Map, snapObj: any)=> { while(true) { let res = await measureDist(map, snapObj); if (res.exit === true) break; if (curMeasureCmd != "measureDist") break; } } // 测量距离 const measureDist = async (map: Map, snapObj: any)=> { let isDrawing = false; let line = await vjmap.Draw.actionDrawLineSting(map, { api: { getSnapFeatures: snapObj //要捕捉的数据项在后面,通过属性features赋值 }, updatecoordinate: (e: any) => { if (!e.lnglat) return; isDrawing = true; const co = map.fromLngLat(e.feature.coordinates[e.feature.coordinates.length - 1]); let html = `【测量距离】当前坐标: ${co.x.toFixed(2)}, ${co.y.toFixed(2)}`; if (e.feature.coordinates.length == 1) { html += "
请指定要测量的第一点的坐标位置" } else { let len = e.feature.coordinates.length; html += `
按Alt键取捕捉; Ctrl键启用正交; 退格键删除上一个点` html += `
距上一点距离: ${getDist(map, [e.feature.coordinates[len - 2], e.feature.coordinates[len -1]])}` html += `; 当前总的距离: ${getDist(map, e.feature.coordinates)}` } setPopupText(html, map) }, contextMenu: (e: any) => { new vjmap.ContextMenu({ event: e.event.originalEvent, theme: "dark", //light width: "250px", items: [ { label: '确认', onClick: () => { // 给地图发送Enter键消息即可取消,模拟按Enter键 map.fire("keyup", {keyCode:13}) setPopupText("", map); } }, { label: '取消', onClick: () => { // 给地图发送ESC键消息即可取消,模拟按ESC键 map.fire("keyup", {keyCode:27}) setPopupText("", map); } },{ label: '删除上一个点', onClick: () => { // 给地图发送退格键Backspace消息即可删除上一个点,模拟按Backspace键 map.fire("keyup", {keyCode:8}) } },{ label: '结束测距', onClick: () => { // 给地图发送ESC键消息即可取消,模拟按ESC键 map.fire("keyup", {keyCode:27}) isDrawing = false; setPopupText("", map); } } ] }); } }); if (line.cancel) { setPopupText("", map); return { cancel: true, exit: isDrawing === false // 如果还没有绘制,就取消的话,就结束测距 };// 取消操作 } let color = vjmap.randomColor(); let polyline = new vjmap.Polyline({ data: line.features[0].geometry.coordinates, lineColor: color, lineWidth: 2 }); polyline.addTo(map); addMarkersToLine(map, line.features[0].geometry.coordinates, color, polyline.sourceId || "", snapObj); return { polyline }; } // 给线的加个点加个测量的结果值 const addMarkersToLine = (map: Map, coordinates: Array<[number, number]>, color: string, sourceId: string, snapObj: any) => { let markerTexts: any = []; for(let i = 1; i < coordinates.length; i++) { let text = new vjmap.Text({ text: getDist(map, coordinates.slice(0, i + 1)), anchor: "left", offset: [3, 0], // x,y 方向像素偏移量 style:{ // 自定义样式 'cursor': 'pointer', 'opacity': 0.8, 'padding': '6px', 'border-radius': '12px', 'background-color': color, 'border-width': 0, 'box-shadow': '0px 2px 6px 0px rgba(97,113,166,0.2)', 'text-align': 'center', 'font-size': '14px', 'color': `#${color.substring(1).split("").map(c => (15 - parseInt(c,16)).toString(16)).join("")}`, } }); text.setLngLat(coordinates[i]).addTo(map); markerTexts.push(text); } // 给第一个点加一个marker用来删除 const deletePng = "delete.png"; let el = document.createElement('div'); el.className = 'marker'; el.style.backgroundImage = `url(${deletePng})`; el.style.width = '20px'; el.style.height = '20px'; el.style.backgroundSize = '100%'; el.style.cursor = "pointer"; el.addEventListener('click', function (e) { map.removeSourceEx(sourceId); // 删除绘制的线 markerTexts.forEach((m: any) => m.remove()); markerTexts = []; // 多点了下,给地图发送退格键Backspace消息即可删除上一个点,模拟按Backspace键 map.fire("keyup", {keyCode:8}) }); // Add markers to the map. let deleteMarker = new vjmap.Marker({ element: el, anchor: 'right' }); deleteMarker.setLngLat(coordinates[0]) .setOffset([-5, 0]) .addTo(map); markerTexts.push(deleteMarker) // 把坐标加进捕捉数组中。 addSnapCoordinates(snapObj, coordinates); } // 得到距离值 const getDist = (map: Map, coordinates: Array<[number, number]>) => { let result = vjmap.Math2D.lineDist(map.fromLngLat(coordinates)); let unit = "m"; if (result >= 1000) { result /= 1000; unit = "km"; } else if (result < 0.01) { result *= 100; unit = "cm"; } return result.toFixed(2) + " " + unit; } // 增加捕捉点 const addSnapCoordinates = (snapObj: any, coordinates: Array<[number, number]>) => { snapObj.features.push({ type: "Feature", geometry: { type: "LineString", coordinates: [...coordinates] } }) } // 测量面积 const measureArea = async (map: Map, snapObj: any)=> { let isDrawing = false; let poly = await vjmap.Draw.actionDrawPolygon(map, { api: { getSnapFeatures: snapObj //要捕捉的数据项在后面,通过属性features赋值 }, updatecoordinate: (e: any) => { if (!e.lnglat) return; isDrawing = true; const co = map.fromLngLat(e.feature.coordinates[0][e.feature.coordinates.length - 1]); let html = `【测量面积】当前坐标: ${co.x.toFixed(2)}, ${co.y.toFixed(2)}`; if (e.feature.coordinates[0].length == 1) { html += "
请指定要测量的第一点的坐标位置" } else { html += `
按Alt键取捕捉; Ctrl键启用正交; 退格键删除上一个点` html += `
当前面积: ${getArea(map, e.feature.coordinates[0])}` } setPopupText(html, map) }, contextMenu: (e: any) => { new vjmap.ContextMenu({ event: e.event.originalEvent, theme: "dark", //light width: "250px", items: [ { label: '确认', onClick: () => { // 给地图发送Enter键消息即可取消,模拟按Enter键 map.fire("keyup", {keyCode:13}) setPopupText("", map); } }, { label: '取消', onClick: () => { // 给地图发送ESC键消息即可取消,模拟按ESC键 map.fire("keyup", {keyCode:27}) setPopupText("", map); } },{ label: '删除上一个点', onClick: () => { // 给地图发送退格键Backspace消息即可删除上一个点,模拟按Backspace键 map.fire("keyup", {keyCode:8}) } },{ label: '结束测面积', onClick: () => { // 给地图发送ESC键消息即可取消,模拟按ESC键 map.fire("keyup", {keyCode:27}) isDrawing = false; setPopupText("", map); } } ] }); } }); if (poly.cancel) { debugger setPopupText("", map); return { cancel: true, exit: isDrawing === false // 如果还没有绘制,就取消的话,就结束测距 };// 取消操作 } let color = vjmap.randomColor(); let polygon = new vjmap.Polygon({ data: poly.features[0].geometry.coordinates[0], fillColor: color, fillOpacity: 0.4, fillOutlineColor: color, }); polygon.addTo(map); addMarkersToPolygon(map, poly.features[0].geometry.coordinates[0], color, polygon.sourceId || "", snapObj); return { polygon }; } // 测量面积循环,直至按ESC键取消,否则测量完一条后,继续测量下一条 const measureAreaLoop = async (map: Map, snapObj: any)=> { while(true) { let res = await measureArea(map, snapObj); if (res.exit === true) break; if (curMeasureCmd != "measureArea") break; } } // 给加个测量的结果值 const addMarkersToPolygon = (map: Map, coordinates: Array<[number, number]>, color: string, sourceId: string, snapObj: any) => { let markerTexts: any = []; const center = vjmap.polygonCentroid(map.fromLngLat(coordinates)); let text = new vjmap.Text({ text: getArea(map, coordinates), anchor: "center", offset: [0, 0], // x,y 方向像素偏移量 style:{ // 自定义样式 'cursor': 'pointer', 'opacity': 0.8, 'padding': '6px', 'border-radius': '12px', 'background-color': `#${color.substring(1).split("").map(c => (15 - parseInt(c,16)).toString(16)).join("")}`, 'border-width': 0, 'box-shadow': '0px 2px 6px 0px rgba(97,113,166,0.2)', 'text-align': 'center', 'font-size': '14px', 'color': color, } }); text.setLngLat(map.toLngLat(center)).addTo(map); markerTexts.push(text); // 给第一个点加一个marker用来删除 const deletePng = = "delete.png"; let el = document.createElement('div'); el.className = 'marker'; el.style.backgroundImage = `url(${deletePng})`; el.style.width = '20px'; el.style.height = '20px'; el.style.backgroundSize = '100%'; el.style.cursor = "pointer"; el.addEventListener('click', function (e) { map.removeSourceEx(sourceId); // 删除绘制的线 markerTexts.forEach((m: any) => m.remove()); markerTexts = []; }); // Add markers to the map. let deleteMarker = new vjmap.Marker({ element: el, anchor: 'right' }); deleteMarker.setLngLat(coordinates[0]) .setOffset([-5, 0]) .addTo(map); markerTexts.push(deleteMarker) // 把坐标加进捕捉数组中。 addSnapCoordinates(snapObj, coordinates); } // 得到面积值 const getArea = (map: Map, coordinates: Array<[number, number]>) => { let result = vjmap.calcPolygonArea(map.fromLngLat(coordinates)); let unit = "m²"; if (result >= 1e6) { result /= 1e6; unit = "km²"; } else if (result < 1.0/1e4) { result *= 1e4; unit = "cm²"; } return result.toFixed(2) + " " + unit; } // 测量角度 const measureAngle = async (map: Map, snapObj: any)=> { let isDrawing = false; let line = await vjmap.Draw.actionDrawLineSting(map, { pointCount: 3,// 只需三个点,绘制完三个点后,自动结束 api: { getSnapFeatures: snapObj //要捕捉的数据项在后面,通过属性features赋值 }, updatecoordinate: (e: any) => { if (!e.lnglat) return; isDrawing = true; const co = map.fromLngLat(e.feature.coordinates[e.feature.coordinates.length - 1]); let html = `【测量角度】当前坐标: ${co.x.toFixed(2)}, ${co.y.toFixed(2)}`; if (e.feature.coordinates.length == 1) { html += "
请指定要测量的第一点的坐标位置" } else { let len = e.feature.coordinates.length; html += `
按Alt键取捕捉; Ctrl键启用正交; 退格键删除上一个点` html += `
当前角度: ${getAngle(map, e.feature.coordinates).angle}` } setPopupText(html, map) }, contextMenu: (e: any) => { new vjmap.ContextMenu({ event: e.event.originalEvent, theme: "dark", //light width: "250px", items: [ { label: '确认', onClick: () => { // 给地图发送Enter键消息即可取消,模拟按Enter键 map.fire("keyup", {keyCode:13}) setPopupText("", map); } }, { label: '取消', onClick: () => { // 给地图发送ESC键消息即可取消,模拟按ESC键 map.fire("keyup", {keyCode:27}) setPopupText("", map); } },{ label: '删除上一个点', onClick: () => { // 给地图发送退格键Backspace消息即可删除上一个点,模拟按Backspace键 map.fire("keyup", {keyCode:8}) } },{ label: '结束测角度', onClick: () => { // 给地图发送ESC键消息即可取消,模拟按ESC键 map.fire("keyup", {keyCode:27}) isDrawing = false; setPopupText("", map); } } ] }); } }); if (line.cancel) { setPopupText("", map); return { cancel: true, exit: isDrawing === false // 如果还没有绘制,就取消的话,就结束测距 };// 取消操作 } let color = vjmap.randomColor(); let polyline = new vjmap.Polyline({ data: line.features[0].geometry.coordinates, lineColor: color, lineWidth: 2 }); polyline.addTo(map); addMarkersToAngle(map, line.features[0].geometry.coordinates, color, polyline.sourceId || "", snapObj); return { polyline }; } // 测量角度循环,直至按ESC键取消,否则测量完一条后,继续测量下一条 const measureAngleLoop = async (map: Map, snapObj: any)=> { while(true) { let res = await measureAngle(map, snapObj); if (res.exit === true) break; if (curMeasureCmd != "measureAngle") break; } } // 给加个测量的结果值 const addMarkersToAngle = (map: Map, coordinates: Array<[number, number]>, color: string, sourceId: string, snapObj: any) => { if (coordinates.length < 3) return; let markerTexts: any = []; let points = map.fromLngLat(coordinates); let textPoint = coordinates[1]; let ang = getAngle(map, coordinates); // 绘制注记圆弧 const cirleArcPath = vjmap.getCirclePolygonCoordinates( points[1], points[1].distanceTo(points[0]) / 4.0, 36, ang.startAngle, ang.endAngle, false); let path = new vjmap.Polyline({ data: map.toLngLat(cirleArcPath), lineColor: color, lineWidth: 2 }); path.addTo(map); markerTexts.push(path) // @ts-ignore let arcPoints = path.getData().features[0].geometry.coordinates; let arcMid = arcPoints[Math.ceil(arcPoints.length / 2)];// 取中点 let textAngle = vjmap.radiansToDegrees(-map.fromLngLat(arcMid).angleTo(points[1])) + 90; if (textAngle > 90) textAngle += 180; else if (textAngle > 270) textAngle -= 180; let text = new vjmap.Text({ text: ang.angle as string, anchor: "center", rotation: textAngle, offset: [0, 0], // x,y 方向像素偏移量 style:{ // 自定义样式 'cursor': 'pointer', 'opacity': 0.8, 'padding': '6px', 'border-radius': '12px', 'background-color': color, 'border-width': 0, 'box-shadow': '0px 2px 6px 0px rgba(97,113,166,0.2)', 'text-align': 'center', 'font-size': '14px', 'color': `#${color.substring(1).split("").map(c => (15 - parseInt(c,16)).toString(16)).join("")}`, } }); text.setLngLat(arcMid).addTo(map); markerTexts.push(text); // 给第一个点加一个marker用来删除 const deletePng = = "delete.png"; let el = document.createElement('div'); el.className = 'marker'; el.style.backgroundImage = `url(${deletePng})`; el.style.width = '20px'; el.style.height = '20px'; el.style.backgroundSize = '100%'; el.style.cursor = "pointer"; el.addEventListener('click', function (e) { map.removeSourceEx(sourceId); // 删除绘制的线 markerTexts.forEach((m: any) => m.remove()); markerTexts = []; }); // Add markers to the map. let deleteMarker = new vjmap.Marker({ element: el, anchor: 'right' }); deleteMarker.setLngLat(coordinates[1]) .setOffset([-5, 0]) .addTo(map); markerTexts.push(deleteMarker) // 把坐标加进捕捉数组中。 addSnapCoordinates(snapObj, coordinates); } // 得到角度值 const getAngle = (map: Map, coordinates: Array<[number, number]>) => { let points = map.fromLngLat(coordinates); if (points.length < 3) return { angle: 0.0 } let angle1 = points[0].angleTo(points[1]); let angle2 = points[2].angleTo(points[1]); let angle = angle1 - angle2; let deg = vjmap.radiansToDegrees(angle);//弧度转角度 let dir = true; if (deg < 0) { deg = -deg; dir = !dir; } if (deg > 180) { deg = 360 - deg; dir = !dir; } let startAngle = !dir ? vjmap.radiansToDegrees(angle1) : vjmap.radiansToDegrees(angle2); let endAngle = dir ? vjmap.radiansToDegrees(angle1) : vjmap.radiansToDegrees(angle2); startAngle = startAngle < 0 ? 360 + startAngle : startAngle; endAngle = endAngle < 0 ? 360 + endAngle : endAngle; if (endAngle < startAngle) { endAngle += 360; } return { angle: deg.toFixed(2) + "°", dir, startAngle, endAngle } } // 测量坐标 const measureCoordinate = async (map: Map, snapObj: any)=> { let isDrawing = false; let point = await vjmap.Draw.actionDrawPoint(map, { api: { getSnapFeatures: snapObj //要捕捉的数据项在后面,通过属性features赋值 }, updatecoordinate: (e: any) => { if (!e.lnglat) return; isDrawing = true; const co = map.fromLngLat(e.lnglat); let html = `【测量坐标】当前坐标: ${co.x.toFixed(2)}, ${co.y.toFixed(2)}`; setPopupText(html, map) }, contextMenu: (e: any) => { new vjmap.ContextMenu({ event: e.event.originalEvent, theme: "dark", //light width: "250px", items: [ { label: '确认', onClick: () => { // 给地图发送Enter键消息即可取消,模拟按Enter键 map.fire("keyup", {keyCode:13}) setPopupText("", map); } }, { label: '取消', onClick: () => { // 给地图发送ESC键消息即可取消,模拟按ESC键 map.fire("keyup", {keyCode:27}) setPopupText("", map); } }, { label: '结束测坐标', onClick: () => { // 给地图发送ESC键消息即可取消,模拟按ESC键 map.fire("keyup", {keyCode:27}) isDrawing = false; setPopupText("", map); } } ] }); } }); if (point.cancel) { setPopupText("", map); return { cancel: true, exit: isDrawing === false };// 取消操作 } addMarkersToCoord(map, point.features[0].geometry.coordinates); return { point }; } // 测量坐标循环,直至按ESC键取消 const measureCoordinateLoop = async (map: Map, snapObj: any)=> { while(true) { let res = await measureCoordinate(map, snapObj); if (res.exit === true) break; if (curMeasureCmd != "measureCoordinate") break; } } // 给加个点加个测量的结果值 const addMarkersToCoord = (map: Map, coordinates: [number, number]) => { let markerTexts: any = []; let co = map.fromLngLat(coordinates); let content = `X: ${co.x.toFixed(2)}, Y: ${co.y.toFixed(2)}` let marker = createLeaderMarker(map, coordinates, content); markerTexts.push(marker); // 给第一个点加一个marker用来删除 const deletePng = "delete.png"; let el = document.createElement('div'); el.className = 'marker'; el.style.backgroundImage = `url(${deletePng})`; el.style.width = '20px'; el.style.height = '20px'; el.style.backgroundSize = '100%'; el.style.cursor = "pointer"; el.addEventListener('click', function (e) { markerTexts.forEach((m: any) => m.remove()); markerTexts = []; }); // Add markers to the map. let deleteMarker = new vjmap.Marker({ element: el, anchor: 'right' }); deleteMarker.setLngLat(coordinates) .setOffset([-5, 0]) .addTo(map); markerTexts.push(deleteMarker) } // 引线标记 const createLeaderMarker = (map: Map, lnglat: [number, number], content: string) => { let el = document.createElement('div'); el.className = 'marker'; el.style.position = 'absolute' let img = document.createElement("div"); img.style.backgroundImage = 'bk.png'; img.style.backgroundRepeat = "no-repeat" img.style.height = '37px'; img.style.width = '100px'; img.style.position = 'absolute'; img.style.left = '-3px'; img.style.bottom = '-3px'; img.style.right = "0px" el.appendChild(img); let panel = document.createElement("div"); panel.style.height = '50px'; panel.style.width = '350px'; panel.style.position = 'absolute'; panel.style.left = '97px'; panel.style.top = '-60px'; panel.style.border = "solid 1px #8E0EFF"; panel.style.background = 'linear-gradient(#00ffff, #00ffff) left top, linear-gradient(#00ffff, #00ffff) left top, linear-gradient(#00ffff, #00ffff) right bottom, linear-gradient(#00ffff, #00ffff) right bottom'; panel.style.backgroundRepeat = 'no-repeat'; panel.style.backgroundColor ='rgba(87,255,255, 0.3)' panel.style.backgroundSize = '1px 6px, 6px 1px'; panel.style.fontSize = '18px'; panel.style.color = '#ffffff'; panel.innerHTML = `
${content}
`; el.appendChild(panel); // Add markers to the map. let marker = new vjmap.Marker({ element: el, anchor: "bottom-left" }) marker.setLngLat(lnglat) .addTo(map); return marker } ``` --- # 样式规范 样式规范是定义地图视觉外观的文档:绘制哪些数据,绘制顺序以及绘制数据时如何设置数据样式。 一个样式有以下根属性: - 描述信息:`version`, `metadata` - 地图状态:`center`, `bearing`, `pitch` - 地图要素:`sources`, `layers` - 其他:`sprite`, `glyphs`, `light`, `transition` ``` js { 'version': number, // 版本号 'name': 'string', // 样式名称 'sprite': 'string', // 雪碧图 'glyphs': '***/{fontstack}/{range}.pbf', // 字体 'sources': {}, // 数据源 'layers': [] // 图层 } ``` ## 数据源 > `sources` > 数据源类型由 `type` 指定,并且必须是 `vector`, `raster`, `raster-dem`, `geojson`, `image`, `video` 之一。 ::: tip 提示 另外,还有一个 `canvas` 类型,是 `image` 的扩展:[CanvasSource](/doc/#canvassource) ::: ### vector 使用 `vector` 数据源的图层必须指定 `source-layer` 。 ``` js 'sources': { '{source-id}': { 'type': 'vector', 'tiles': [ 'http://a.example.com/tiles/{z}/{x}/{y}.pbf' ], 'bounds': [-180, -85.051129, 180, 85.051129] }, '{source-id}': { 'type': 'vector', 'url': 'http://api.example.com/tilejson.json' }, '{source-id}', { 'type': 'vector', 'url': 'xxx' }, } ``` ### raster ``` js 'sources': { '{source-id}': { 'type': 'raster', 'tiles': [ 'http://xxx&x={x}&y={y}&l={z}&tk={key}', 'http://xxx&x={x}&y={y}&l={z}&tk={key}' ], 'tileSize': 256 }, } ``` ### raster-dem 栅格 DEM 数据源,可使用 [rio-rgbify](https://github.com/mapbox/rio-rgbify) 生成。 ``` sh # fill no data gdalwarp -t_srs EPSG:3857 -dstnodata None -co TILED=YES -co COMPRESS=DEFLATE -co BIGTIFF=IF_NEEDED E:\xxx.tif E:\xxxx.tif # conver to rgb rio rgbify -b -10000 -i 0.1 E:\xxxx.tif E:\test\xxx.tif ``` ### geojson GeoJSON 数据源,必须提供 `data` 属性。可设置 `cluster`, `filter` 等。 ``` js 'sources': { // inline GeoJSON 'geojson-marker': { 'type': 'geojson', 'data': { 'type': 'Feature', 'geometry': { 'type': 'Point', 'coordinates': [-77.0323, 38.9131] }, 'properties': { 'title': 'xxx', 'marker-symbol': 'monument' } } }, // url 'geojson-lines': { 'type': 'geojson', 'data': './lines.geojson' } } ``` ### image & video 见[数据/栅格](/guide/data.html#raster)。 ### GeoServer #### TMS > 标准:[Tile Map Service](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification) Tile Map Service(瓦片地图服务),简称 `TMS`。TMS 的切片坐标系统和组织方式可参考下图:
在加载 TMS 服务时,需要设置 `scheme` 属性为 `tms`。`scheme` 属性影响瓦片坐标 `y` 轴的方向。`scheme` 有两个值可选:`xyz`(默认)和 `tms`。其中 `xyz` 代表常用的切片规则,即 `OGC` 标准,切片源点在左上角。`tms` 是 `OSGeo` 标准,切片源点是左下角。 加载示例: ``` js // 矢量 'source-id': { 'type': 'vector', 'tiles': [ 'https://xxx@pbf/{z}/{x}/{y}.pbf' ], 'scheme': 'tms' } // 栅格 'source-id': { 'type': 'raster', 'tiles': [ 'https://xxx@png/{z}/{x}/{y}.png' ], 'tileSize': 256, 'scheme': 'tms' } ``` #### WMS > 标准:[Web Map Service](http://www.opengeospatial.org/standards/wms) Web Map Service(Web 地图服务),简称 `WMS`。WMS 标准定义了允许用户通过 HTTP 对空间数据进行渲染出图等操作。WMS 服务中包括以下3个操作: 1. GetCapabilities(获取服务中的要素类及支持的操作) 2. GetMap(获取地图) 3. GetFeatureInfo(根据地图上的像素点获取更详细的要素信息,类似Identify功能) 利用 `GetMap` 操作和指定地图边框范围(`bbox`)和输出格式(`format`)即可使用。 加载示例(image/png 格式): ``` js 'source-id': { 'type': 'raster', 'tiles': [ 'https://xxx?service=WMS&version=1.3.0&request=GetMap&layers=china:bj&bbox={bbox-epsg-3857}&width=512&height=512&srs=EPSG:3857&format=image/png&TRANSPARENT=TRUE' ] } ``` 注意url中的 `bbox` 参数值 `"{bbox-epsg-3857}"`。地图会自动计算地图范围替换该值,获取地图。这也是与下面WMTS同样用栅格格式,WMS不会模糊的原因。 #### WMTS > 标准:[Web Map Tile Service](http://www.opengeospatial.org/standards/wmts) Web Map Tile Service(Web地图瓦片服务),简称 `WMTS`。WMTS 标准定义了一些操作允许用户访问瓦片地图。WMTS 服务中包括以下 3 个操作: 1. GetCapabilities(获取服务的元信息) 2. GetTile(获取切片) 3. GetFeatureInfo(可选,获取点选的要素信息) WMTS的切片坐标系统和组织方式可参考下图:
利用 `GetTile` 操作和指定输出格式即可在中使用。 加载示例(image/png格式): ``` js 'source-id': { 'type': 'raster', 'tiles': [ 'https://xxx/service/wmts?REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&LAYER=buildings:Chongqing&STYLE=&TILEMATRIX=EPSG:900913:{z}&TILEMATRIXSET=EPSG:900913&FORMAT=image/png&TILECOL={x}&TILEROW={y}' // 或 geoserver rest 服务 // 'https:/xxx/wmts/rest/buildings:Chongqing/EPSG:900913/EPSG:900913:{z}/{y}/{x}?format=image/png' ] } ``` ::: warning 注意 这种方式请求栅格格式(如 `image/png`)数据会有些模糊,设置 `'tileSize': 256` 会好很多。但是实际 WMTS 请求返回的图片格式是清晰的。模糊的原因是地图的缩放是无极缩放,也就是 `zoom` 可以是小数,而 WMTS 的 `z` 值是整数,在渲染过程中有缩放,所以模糊了。 前面提到加载 wms 也不会模糊,但是 wms 实际上是根据请求范围实时渲染,加载速度不如瓦片服务。 ::: 加载示例 ``` js 'source-id': { 'type': 'vector', 'tiles': [ // geoserver rest 服务 // 'https://xxx/wmts/rest/buildings:Chongqing/EPSG:900913/EPSG:900913:{z}/{y}/{x} ] } ``` #### WFS > 标准:[Web Feature Service](http://www.opengeospatial.org/standards/wfs) Web Feature Service(Web 要素服务),简称 WFS。WFS 标准定义了一些操作允许用户通过 HTTP 对空间数据进行查询、编辑等操作。WFS 服务中常用的操作包括: 1. GetCapabilities(获取服务中的要素类及支持的操作) 2. DescribeFeatureType(描述要素类的信息) 3. GetFeature(获取要素) 4. Transaction(创建、更新、删除数据的事务操作) 利用 `GetFeature` 操作和指定输出格式即可在使用。 加载示例(application/json 格式): ``` js 'source-id': { 'type': 'geojson', 'data': `https://xxx?service=WFS&version=2.0.0&request=GetFeature&typeName=buildings:cq_point&outputFormat=application/json` } ``` ::: tip 提示 - wfs会请求全部数据,不适用于大量数据。但是支持空间查询和属性查询,也会经常用到。 - 文档:[wfs/wms filter](https://docs.geoserver.org/latest/en/user/filter/index.html#filtering) ::: ## 图层 > `layers` 图层属性配置如下: | 属性 | 类型 | 必须 | 描述 | | :-- | :--: | :--: | :-- | | **`id`** | string | 是 | 图层唯一标识 | | **`type`** | string | 是 | 图层类型 | | **`source`** | string | 否 | 数据源名称,图层类型除 `background` 外必须 | | **`source-layer`** | string | 否 | 图层名,矢量瓦片源(vector)必须,其他类型不可用 | | **`minzoom`** | number | 否 | 图层最小缩放级别 | | **`maxzoom`** | number | 否 | 图层最大缩放级别 | | **`filter`** | expression | 否 | 过滤器 | | **`layout`** | object | 否 | 图层布局属性 | | **`paint`** | object | 否 | 图层绘制属性 | | **`metadata`** | any | 否 | 任意属性,对跟踪图层很有用,不影响渲染 | 图层示例: ``` js 'layers': [ { 'id': 'road', 'type': 'line', 'source': 'your source', 'source-layer': 'road', 'minzoom': 7, 'maxzoom': 18, 'layout': { 'visibility': 'visible' }, 'paint': { 'line-color': 'hsl(55, 1%, 20%)' }, 'filter': ['==', '$type', 'LineString'] 'metadata': { 'type': 'base' } } ] ``` ::: tip 提示 - **`"type"`** 属性必须是 `background`, `fill`, `line`, `symbol`, `raster`, `circle`, `fill-extrusion`, `heatmap`, `hillshade` 之一。 - 除 `background` 外的图层都需要引用一个源(source),图层从源获取数据、过滤要素和渲染。 - **`layout`** 属性都有 **`visibility`** 属性,可设置 `"visible"`(默认) 和 `"none"` 。 - 颜色支持 `十六进制`, `RGB`, `RGBA`, `HSL`, `HSLA`, `HTML 预定义颜色名称` 格式。 ::: ## 表达式 > 更详细的介绍请参考:[expressions](/guide/expression.html) 任何 `layout` 属性、`paint` 属性或 `filter` 的值都可以是表达式。示例: ``` js { "circle-radius": [ "interpolate", ["linear"], ["zoom"], // when zoom is 0, set each feature's circle radius to the value of its "rating" property 0, ["get", "rating"], // when zoom is 10, set each feature's circle radius to four times the value of its "rating" property 10, ["*", 4, ["get", "rating"]] ] } ``` ::: warning 注意 在表达式中, `layout` 属性和 `paint` 属性有一个很重要的区别。`paint` 属性,只要缩放级别发生变化,都会重新计算 `paint` 属性,即使是小数变化,比如在 4.1 - 4.6 之间。而 `layout` 属性只在整数缩放级别时计算,在 4.1 - 4.6 之间不会发生变化,只有在高于 5 或小于 4 时计算。 ::: ### Types 表达式拥有强大的类型系统,并支持转换和检测。比如,表达式的输入和输出数据类型需和样式规范中的类型一致,在计算表达式时会自动检测,如果检测到错误,会在控制台输出错误提示。 ### Feature data - feature-state:要素状态,要素状态不是数据的某一属性,需要对通过要素 `id` 设置,如给要素设置 `hover` 状态:`map.setFeatureState({source: 'states', id: hoveredStateId}, {hover: false})`,然后在表达式中即可使用:`['feature-state', 'hover']` ### Lookup - get:获取要素属性,如获取 `name` 属性:`['get', 'name']` ### Decision 在表达式中,可使用常用的条件逻辑,比如:`"if/then/else"` 的逻辑可使用 `'case'` 表达式,如需将输入的特定值映射不同的输出,可使用 `'match'` 表达式。 ### Ramps, scales, curves - interpolate:插值,支持 `linear`、`exponential` 和 `cubic-bezier` - step:分段 ### Variable binding - let 绑定变量 - var 引用 `let` 绑定的变量 ### String - downcase - upcase ### Color - rgb - rgba ### Math 支持基本的数学运算,如 ``` js ['/', ['get', 'gdp'], 1000] ``` ### Zoom 获取当前缩放级别,仅用于 `step` 或 `interpolate` 表达式的输入。 ``` js ['zoom']: number ``` ``` { "id": "road", "type": "line", "source": "a", "paint": { "line-width": 10, "line-pattern": [ "step", ["zoom"], "generic_icon", /*小于2级时用这个线型符号*/ 2, "generic_metro" /*大小或等于2级时用这个线型符号*/ ] } } ``` ### Heatmap 获取热力图图层中像素的核密度估计,只能用于 `heatmap-color` 属性。 ``` js ['heatmap-density']: number ``` > 更详细的介绍请样式表达式请参考:[expressions](/guide/expression.html) --- # 地图表达式 ## 目录 ## 表达式语法和概念 可以将任何layout属性,paint属性或filter的值指定为表达式。 ```js [表达式运算符, 参数1, 参数2, ...] ``` 参数的值类型:字符串,数字,布尔值,或者另一个表达式数组,null。 也就是说表达式可以嵌套。也就是组合表达式: 例如: ```js //geojson 的 features 参数 "features": [ { "type": "Feature", "properties": { "id": "1", "name":"点1", "desc":"这是点1", "radius": 10 }, "geometry": { "type": "Point", "coordinates": [ 108.9902114868164, 34.24813554589752 ] } } ] // 根据geojson中 radius 的值来确定圆的半径 "circle-radius": [ "interpolate", ["linear"], ["zoom"], //zoom为 0,半径为radius的值 0, ['get','radius'], //zoom为 10,半径为radius的值,zoom为0-10是按照linear 线性增加。 10, ['*', ['get','radius'], 10] ] ``` ## 表达式类型系统 ### `概述` 表达式输入参数和输出结果的类型:boolean,color,string,number,array。来源于 style 规范中的 Types, 表达式的每次使用都具有已知的结果类型和必需的参数类型,并且SDK会验证表达式的结果类型是否适合使用该表达式的上下文,例如,filter属性中表达式的结果类型必须为boolean,+运算符的参数必须为number。 使用要素数据时,SDK通常不会提前知道要素属性值的类型。为了保持类型安全,在评估数据表达式时,SDK将检查属性值是否适合上下文。例如,如果您["get", "feature-color"]对circle-color属性使用表达式,则SDK将验证feature-color每个功能的值是否是标识有效color的字符串。如果此检查失败,将以特定于SDK的方式指示错误(通常是一条日志消息),并且将使用该属性的默认值。 在大多数情况下,此验证将在需要的任何地方自动进行。但是,在某些情况下,SDK可能无法根据周围环境自动确定数据表达式的预期结果类型。例如,不清楚表达式["<", ["get", "a"], ["get", "b"]]是要比较字符串还是数字。在这种情况下,可以使用类型断言表达式运算符之一来指示数据表达式的预期类型:["<", ["number", ["get", "a"]], ["number", ["get", "b"]]]。类型声明检查特征数据是否确实与数据表达式的预期类型匹配。如果此检查失败,则会产生错误,并使整个表达式恢复为所定义属性的默认值。断言运营商array,boolean,number,和string。 表达式仅执行一种隐式类型转换:在期望颜色的上下文中使用的数据表达式会将颜色的字符串表示形式转换为颜色值。在其他情况下,如果你想类型之间的转换,你必须使用一个类型转换表达式运算符: ```js to-boolean: 返回 boolean to-number: 返回 number to-string: 返回 string to-color: 返回 color ``` 例如,如果您具有一个功能属性,该属性以字符串格式存储数字值,并且您希望将这些值用作数字而不是字符串,则可以使用诸如的表达式["to-number", ["get", "property-name"]]。 例如对数据中的字符串类型,转换成数字类型: ```js //circle-radius: 的值期望是 number,所以字符串类型会报错 "circle-radius": [ "interpolate",["linear"],["zoom"], 0, ['get','radius'], 10, '*', ["to-number", ["get", "id"]], 10] ] ``` ### `断言` 判断输入值是不是属于某种类型 ```js //判断输入值value是一个数组 ["array", value]: 返回array //判断输入值value是一个布尔值 ["boolean", value]: 返回boolean //判断输入值value是一个数字 ["number", value]: number //判断输入值value是一个对象 ["object", value]: object //判断输入值value是一个字符串 ["string", value]: string //以上输入多个value,会按顺序比较,知道返回第一个满足的value,如果都不满足,返回false或者报错 // ``` 1. collator 返回一个collator,用于与语言环境相关的比较操作。在case-sensitive和diacritic-sensitive选项默认为false。该locale参数指定要使用的语言环境的IETF语言标记。如果未提供,则使用默认语言环境。如果请求的语言环境不可用,collator则将使用系统定义的后备语言环境。使用resolved-locale测试的现场备用行为的结果。 ```js ["collator", { "case-sensitive": boolean, "diacritic-sensitive": boolean, "locale": string } ]: collator ``` 2. literal 提供文字数组或对象值。 ```js ["literal", [...] (JSON array literal)]: array ["literal", {...} (JSON object literal)]: Object ``` 3. format 返回formatted包含用于混合格式text-field条目的注释的文本。如果设置,则text-font参数将覆盖由根布局属性指定的字体。如果设置,则font-scale参数指定相对于text-size根布局属性中指定的比例因子。 ```js ["format", input_1: string, options_1: { "font-scale": number, "text-font": array }, ..., input_n: string, options_n: { "font-scale": number, "text-font": array } ]: formatted ``` ### `表达式类型转换` 1. 转布尔值boolean 将输入值转换为布尔值。当输入是空字符串,0 ,false,null 或 NaN,结果是false. 否则是true。 ```js ["to-boolean", value]: boolean ``` 如果提供了多个值,则按顺序计算每个值,直到得到一个布尔值为止。如果所有输入都不是布尔值,则表达式是错误的。 ```js ['boolean', ['feature-state', 'hover'], false] ``` 首先看`['feature-state', 'hover']`表达式的值是否为bool值,如果是,则返回对应的bool值;如果否,则按顺序依次往后看第二个值是否为bool值。 2. 转为颜色color 将输入值转换为颜色。如果提供了多个值,则会依次评估每个值,直到获得第一个成功的转换为止。如果没有任何输入可以转换,则该表达式为错误。 ```js ["to-color", value, fallback: value, fallback: value, ...]: color ``` 3. 转数字number 将输入值转换为数字。 * 如果输入为null或false,结果为0。 * 如果输入为true,结果为1。 * 如果输入为字符串,则将按照[ECMAScript中“字符串转Number规则”](https://tc39.es/ecma262/#sec-tonumber-applied-to-the-string-type)中指定的语言规范执行。 * 如果提供了多个值,则会依次评估每个值,直到获得第一个成功的转换为止。 * 如果没有任何输入可以转换,则该表达式为错误。 ```js ["to-number", value, fallback: value, fallback: value, ...]: number ``` 4. 转字符串string 将输入值转换为字符串。 * 如果输入为null,则结果为""。 * 如果输入为布尔值,则结果为"true"或"false"。 * 如果输入是数字,则将其转换为[ECMAScript语言规范的“ NumberToString”](https://tc39.es/ecma262/#sec-tostring-applied-to-the-number-type)算法指定的字符串。 * 如果输入是彩色,它被转换为以下形式的串"rgba(r,g,b,a)",其中r,g和b是数字范围从0到255,和a范围为0〜1。 * 其他,按照[JSON.stringifyECMAScript语言规范](https://tc39.es/ecma262/#sec-json.stringify)的功能执行。 ```js ["to-string", value]: string ``` 4. 类型判断typeof 返回描述给定值类型的字符串。 ```js ["typeof", value]: string ``` ### 特征数据 Feature data 1. accumulated 获取到目前为止累积的集群属性的值。只能在clusterProperties集群化GeoJSON源的选项中使用。 ```js ["accumulated"]: value ``` 2. feature-state 从当前要素的状态检索属性值。如果请求的属性不在功能状态上,则返回null。要素的状态不是GeoJSON或矢量切片数据的一部分,必须在每个要素上以编程方式设置。请注意,[“ feature-state”] 仅可与支持数据驱动样式的绘画属性一起使用。 ```js ["feature-state", string]: value ``` 从当前feature-state中检索属性值。如果所请求的属性在feature-state上不存在,则返回null。feature-state不是GeoJSON或矢量瓦片数据的一部分,必须在每个feature上以编程的方式设置。feature由它们的id属性标识,该属性必须是整数或可以转换为整数的字符串。 数据格式示例: ```js { "type":"FeatureCollection", "features": [ { "type":"Feature", "geometry":{ "type":"Polygon", "coordinates":[[[10,20],[10,30],[20,30],[20,10],[10,20]]] }, "properties":{ "STATE_ID":"54", "STATE_NAME":"name" }, "id":54 }, ] } ``` 注意`id`的位置不在properties的属性中 ```js 'paint': { 'fill-color': '#627BC1', 'fill-opacity': [ 'case', ['boolean', ['feature-state', 'hover'], false], 1, 0.5 ] } ``` ```js hoveredStateId = e.features[0].id; map.setFeatureState( { source: 'states', id: hoveredStateId }, { hover: false } ); ``` 3. geometry-type 获取要素的几何类型:(点,多点,线串,多线串,多边形,多多边形)。 Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon. ```js ["geometry-type"]: string ``` 例如: ```js layout:{ "text-field": ["geometry-type"] } //获取的值是 Point ``` 4. id 获取功能的ID(如果有的话)。 ```js ["id"]: value ``` 5. line-progress 沿渐变线获取进度。只能在line-gradient属性中使用。 ```js ["line-progress"]: number ``` 6. properties 获取要素属性对象。请注意,在某些情况下,直接使用[“ get”,“ property_name”] 可能更有效。 例如获取的是properties的整个对象 ```js ["properties"]: object ``` ### Lookup 1. at 从数组中检索一个项目。 number,在不在数组中 ```js ["at", number, array]: ItemType ``` 2. get 从当前要素的feature's properties 中 或从 另一个对象(如果提供了第二个自变量)中检索属性值。如果缺少所请求的属性,则返回null。 ```js ["get", string]: value ["get", string, object]: value ``` 3. has 测试当前要素feature's properties中是否存在某个属性值;如果提供了第二个参数,则从另一个对象中进行测试。 ```js ["has", string]: boolean ["has", string, object]: boolean ``` 例如: ```js // 判断properties对象中是否有 id 属性,度过有返回true,否则返回false filter:["has", 'id'] ``` 4. length 获取数组或字符串的长度。 ```js ["length", string | array | value]: number ``` ### 判断 本节中的表达式可用于将条件逻辑添加到样式中。例如,'case'表达式提供基本的“ if / then / else”逻辑,还有'match'允许您将输入表达式的特定值映射到不同的输出表达式。 1. `! 逻辑非` 逻辑否定。true如果输入为false,false则返回true。 ```js ["!", boolean]: boolean ``` 2. `!= 不等于` true如果输入值不相等,false则返回,否则返回。比较是严格类型化的:不同运行时类型的值始终被认为是不相等的。在解析时已知类型不同的情况将被视为无效,并会产生解析错误。接受一个可选collator参数来控制与语言环境有关的字符串比较。 ```js ["!=", value, value]: boolean ["!=", value, value, collator]: boolean ``` 3. `< 小于` 如果第一个输入严格小于第二个输入,则返回true,否则返回false。参数必须是字符串或数字。如果在求值过程中不满足,则表达式求值会产生错误。已知此约束在解析时不成立的情况被认为是有效的,并且会产生解析错误。接受一个可选collator参数来控制与语言环境有关的字符串比较。 ```js ["<", value, value]: boolean ["<", value, value, collator]: boolean ``` 4. `<= 小于等于(不大于)` 类似于上面的<逻辑,等于或者小于 ```js ["<=", value, value]: boolean ["<=", value, value, collator]: boolean ``` 5. `== 等于比较` 比较是严格类型化的,不同运行时类型的值始终被认为是不相等的。 ```js ["==", value, value]: boolean ["==", value, value, collator]: boolean ``` 6. `> 大于` 第一个参数是否大于第二个参数 ```js [">", value, value]: boolean [">", value, value, collator]: boolean ``` 7. `>= 大于等于(不小于)` ```js [">=", value, value]: boolean [">=", value, value, collator]: boolean ``` 8. `all` 所有满足 是有参数是否都为true,如果是返回true,如果有一个不是,则返回false ```js ["all", boolean, boolean]: boolean ["all", boolean, boolean, ...]: boolean ``` 9. `any` 任一满足 是有参数只要有一个为true,如果是返回true,如果全部为false,则返回false ```js ["any", boolean, boolean]: boolean ["any", boolean, boolean, ...]: boolean ``` 10. `case` 条件 结果为第一个满足条件的输出吗,如果都不满足则为fallback。 ```js ["case", 条件1: boolean, 输出: OutputType, 条件2: boolean, 输出: OutputType, ..., fallback: OutputType ]: OutputType ``` 例如: ```js "circle-color": [ "case", ['has','ids'], 'red', ['has','id'], 'yellow', 'blue' ], // 条件返回值需要是一个boolean类型,相当于 "circle-color": [ "case", true, 'red', false, 'yellow', 'blue' ], //以上语句的的意思是,满足为red,不满足为yellow,都不满足(默认)为blue ``` 11. `coalesce` 合并 依次评估每个表达式,直到获得第一个非空值,然后返回该值。 ["coalesce", OutputType, OutputType, ...]: OutputType 12. `match` 匹配 选择标签值与输入值匹配的输出,如果找不到匹配项,则选择回退值。输入可以是任何表达式(例如["get", "building_type"])。每个标签label必须是: * 单个文字值;要么 * 文字值数组,其值必须是所有字符串或所有数字(例如[100, 101]或["c", "b"])。如果数组中的任何值匹配,则输入匹配,类似于不推荐使用的"in"运算符。 每个标签必须唯一。如果输入类型与标签类型不匹配,则结果将是后备值。 ```js ["match", input: InputType (number or string), label: InputType | [InputType, InputType, ...], output: OutputType, label: InputType | [InputType, InputType, ...], output: OutputType, ..., fallback: OutputType ]: OutputType ``` 例如: ```js "circle-color": [ 'match', ['get', 'name'], '点1', 'red', '点2', 'yellow', ['点3','点4'], 'blue', '#fff' ], //获取feature's properties 中的name值,根据name值匹配对应的颜色,默认是(没有匹配到)#fff , ['点3','点4'] 表示是label是可以是数组类型, ``` ### Ramps, scales, curves 倾斜,比例,曲线 1. interpolate 插 通过在成对的输入和输出值(“stops”)之间进行插值, 产生连续,平滑的结果。input可以是任何number表达式(例如,["get", "population"])。stop_input必须是严格按升序排列的数字文字。输出类型必须是`number`,`array`,或`color`。 interpolation 类型: * ["linear"]:在两个止动点之间线性地插值,该插值刚好小于输入并且大于输入。 * ["exponential", base]:在停靠点之间小于输入并大于输入的位置按指数进行插值。base控制输出增加的速率:较高的值可使输出向范围的高端增加更多。值接近1时,输出线性增加。 * ["cubic-bezier", x1, y1, x2, y2]:使用给定控制点定义的三次贝塞尔曲线进行插值。 ```js ["interpolate", interpolation: ["linear"] | ["exponential", base] | ["cubic-bezier", x1, y1, x2, y2 ], input: number, stop_input_1: number, stop_output_1: OutputType, stop_input_n: number, stop_output_n: OutputType, ... ]: OutputType (number, array, or Color) ``` 例如: ```js "line-opacity": [ "interpolate", ["linear"], ["zoom"], 9, 0.8, 15, 0.5 ] //当zoom 为9的时候,透明度是0.8,当zoom为15的时候,透明度为0.5 ``` 2. interpolate-hcl 同上,区别是返回的必须是hcl color颜色, ```js ["interpolate-hcl", interpolation: ["linear"] | ["exponential", base] | ["cubic-bezier", x1, y1, x2, y2 ], input: number, stop_input_1: number, stop_output_1: Color, stop_input_n: number, stop_output_n: Color, ... ]: Color ``` 3. interpolate-lab 同上,区别是返回的必须是lab color颜色, ["interpolate-lab", interpolation: ["linear"] | ["exponential", base] | ["cubic-bezier", x1, y1, x2, y2 ], input: number, stop_input_1: number, stop_output_1: Color, stop_input_n: number, stop_output_n: Color, ... ]: Color 2. step 相当于,获取input的值的结果,若在某个范围内,就给一个结果。 通过评估由成对的输入和输出值(“停止”)定义的分段常数函数,可以产生离散的步进结果。 * input可以是任何number数值表达式(例如,["get", "radius"])。 * stop_input必须是严格按升序排列的number。stop_output为输出值; * 如果输入小于第一个止损,则返回第一个输入值stop_output_0。 ```js ["step", input: number, stop_output_0: OutputType, stop_input_1: number, stop_output_1: OutputType, stop_input_n: number, stop_output_n: OutputType, ... ]: OutputType ``` 例如 ```js "circle-color": [ //根据feature's properties中的radius值, 'step', ['get', 'radius'], //radius值小于3的显示#fff '#fff', //3-4 显示red 3, 'red', //5-6显示yellow 5, 'yellow', //7以上显示blue 7, 'blue', ], ``` ### 变量绑定 1. let 将表达式绑定到命名变量,然后可以使用[“ var”,“ variable_name”] 在结果表达式中进行引用。 ```js ["let", string (alphanumeric literal), any, string (alphanumeric literal), any, ..., OutputType ]: OutputType ``` 2. var 引用变量绑定使用“ let”。 ```js ["var", previously bound variable name]: the type of the bound expression ``` ### 字符串操作 1. concat 字符串连接 返回string,连接各个输入值。每个输入都将转换为字符串,就像通过to-string。 ```js ["concat", value, value, ...]: string ``` 例如: ```js "text-field": ['concat' ,'aaa', 'bbb'] 结果: aaabbb ``` 2. downcase 转小写 ```js ["downcase", string]: string ``` 3. upcase 转大写 返回转换为大写的输入字符串。遵循Unicode默认大小写转换算法和Unicode字符数据库中不区分语言环境的大小写映射。 ```js ["upcase", string]: string ``` 4. `is-supported-script 支持脚本` 返回true是否期望输入字符串清晰呈现。返回false如果输入字符串包含无法呈现而不会失去意义的部分。 ```js ["is-supported-script", string]: boolean ``` 5. `resolved-locale` 返回提供的所使用语言环境的IETF语言标记collator。这可用于确定默认系统区域设置,或确定是否成功加载了请求的区域设置。 ```js ["resolved-locale", collator]: string ``` ### 颜色表达式 1. rgb 从红色,绿色和蓝色分量创建颜色值,该值必须在0到255之间,并且alpha分量为1。如果任何分量超出范围,则该表达式为错误。 ```js ["rgb", number, number, number]: color ``` 2. rgba 从必须在0到255之间的红色,绿色,蓝色分量和必须在0到1之间的alpha分量创建颜色值。如果任何分量超出范围,则表达式为错误。 ```js ["rgba", number, number, number, number]: color ``` 3. to-rgba 返回一个四元素的数组,该数组包含输入颜色的红色,绿色,蓝色和Alpha分量的顺序。 ```js ["to-rgba", color]: array ``` ### Math 表达式 1. `- 减法` 对于两个输入,返回从第一个输入减去第二个输入的结果。对于单个输入,返回从0减去它的结果。 ```js ["-", number, number]: number ["-", number]: number ``` 2. `* 乘法` 返回输入的乘积。 ```js ["*", number, number, ...]: number ``` 3. `/ 除法` 返回第一个输入与第二个输入的浮点除法的结果。 ```js ["/", number, number]: number ``` 4. `% 取余` 返回第一个输入除以第二个输入的整数后的余数。 ```js ["%", number, number]: number ``` 5. `^ 幂` 返回将第一个输入提高到第二个指定的幂的结果。 ```js ["^", number, number]: number ``` 6. `+ 加法` 返回输入的总和。 ```js ["+", number, number, ...]: number ``` 7. `abs 绝对值` 返回输入的绝对值。 ```js ["abs", number]: number ``` 8. `三角函数` ```js // 1. 返回输入的反余弦值。 ["acos", number]: number // 2. 返回输入的反正弦值。 ["asin", number]: number // 3. 返回输入的反正切。 ["atan", number]: number // 4. 返回输入的余弦值。 ["cos", number]: number // 5. 返回输入的正弦值。 ["sin", number]: number // 6. 返回输入的切线。 ["tan", number]: number ``` 9. `四舍五入取整相关` ```js // 1. 向上取整,返回大于或等于输入的最小整数。 ["ceil", number]: number // 2. 返回小于或等于输入的最大整数。 ["floor", number]: number // 3. 四舍五入 将输入舍入到最接近的整数。中途值从零舍入。例如,["round", -1.5]计算结果为-2。 ["round", number]: number ``` 10. `常数类` ```js // 1. 返回数学常数e。 ["e"]: number // 2. 返回输入的自然对数。 ["ln", number]: number // 3. 返回数学常数ln(2)。 ["ln2"]: number // 4. 返回输入的以十为底的对数。 ["log10", number]: number // 5. 返回输入的以2为底的对数。 ["log2", number]: number // 6. 返回数学常数pi。 ["pi"]: number ``` 11. max 取最大值 返回输入的最大值。 ```js ["max", number, number, ...]: number ``` 12. min 取最小值 返回输入的最小值。 ```js ["min", number, number, ...]: number ``` 13. sqrt 返回输入的平方根。 ```js ["sqrt", number]: number ``` ### Zoom 缩放 获取当前的缩放级别。请注意,在样式布局和绘画属性中,[“ zoom”] 可能仅作为顶级“ step”或“ interpolate”表达式的输入出现。 ```js ["zoom"]: number ``` ### Heatmap 热力图 1. heatmap-density 获取热图图层中像素的内核密度估计,这是在特定像素周围拥挤多少数据点的相对度量。只能在heatmap-color属性中使用。 ```js ["heatmap-density"]: number ``` heatmap-weight:表示一个点对热力图权重的贡献,在贡献越大的地方热力图显示应该越明显 heatmap-intensity:热力图强度,有点类似于heatmap-weight属性,但是该属性是设置整体上热力图的强度 heatmap-color:热力图的颜色,设置在各个热力图的数值上是什么颜色 heatmap-radius:热力图的一个点计算权重的时候计算的点的半径,单位为像素,默认为30 heatmap-opacity:热力图的透明度 ```js "heatmap-weight": [ "interpolate", ["linear"], ["get", "mag"], 0, 0, 6, 1 ] ``` 获取配一个要素的mag属性的值来设置每一个点对热力图强度的贡献,事例表示当mag的数值在0~6之间的时候对热力图强度贡献为从0到1进行线性贡献,大于6表示贡献为1,小于0表示无贡献 ```js "heatmap-intensity": [ "interpolate", ["linear"], ["zoom"], 0, 1, 9, 3 ] ``` 根据地图的缩放级别类设置热力图的强度,当缩放级别在0~9之间进行线性变化的时候,热力图的强度从1~3进行线性变化,小于0是 强度为0,大于9时强度为3 ```js "heatmap-color": [ "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)" ] ``` 设置热力图的颜色,热力图是颜色在强度在给定的两个范围进颜色记性线性变化,heatmap-density表示热力图的密度, ```js "heatmap-radius": [ "interpolate", ["linear"], ["zoom"], 0, 2, 9, 20 ] ``` 设置在不同缩放级别的时候更改热力图计算的半径, ```js "heatmap-opacity": [ "interpolate", ["linear"], ["zoom"], 7, 1, 9, 0 ] ``` 设置热力图的透明度,是整体图形的透明度,在zoom处于7~9间将热力图逐渐的透明,在zoom大于9的时候完全透明 ### 其他方法 可以将任何layout或paint属性的值指定为一个函数。使用功能可以使地图要素的外观随当前缩放级别zoom和/或地图项的feature's properties 变化而变化。默认输入值是zoom 1. stops 必须是一个数组,根据输入和输出值定义功能。一组一个输入值和一个输出值称为stop”。停止输出值必须是文字值(即不是函数或表达式),并且适用于该属性。例如,fill-color属性中使用的stops函数输出值必须为colors。 例如: ```js "circle-radius": { "stops": [ // zoom 为1时,半径为2 [1, 2], //zoom为10时,半径为20 [10, 20] ] } // [10, 20] 就是一个stop, 10是输入值, 20是输出值,多个stop就是一个stops ``` stop的四种类型 (1) 指数类型,最终输出值由stops中的差值项进行区间范围内的指数级差值计算生成 ```js map.addLayer({ "paint": { "circle-color": { // 指数类型type可不写 "type": "exponential", "property": "val", "stops": [ // properties.val为0,圆的颜色为红色 [0, "red"], // properties.val为500,圆的颜色为绿色 [500, "green"], // properties.val为1000,圆的颜色为蓝色 [1000, "blue"] ], // 可选,默认值:1。插值运算的曲率指数基数,控制最终计算结果值的增长率,值越大,最终计算结果值越大,当值趋近于1时,函数采用线性计算方式。 "base": 0.9 } } }); ``` (2) 区间类型 最终输出值由stops中的差值项进行区间范围内的阶梯型差值计算生成,stops中的输入参数必须为数值类型。 ```js map.addLayer({ "paint": { "circle-opacity": { "type": "interval", "property": "val", // 不写,将会根据zoom设置 "stops": [ // properties.val大于等于0时,圆颜色为红色 [0, "red"], // properties.val大于等于500时,圆颜色为绿色 [500, "green"], // properties.val大于等于1000时,圆颜色为蓝色 [1000, "blue"], ] } } }); ``` (3) 类别类型, 最终输出值完全匹配stops中的输入值对应的输出值 ```js map.addLayer({ "paint": { "fill-color": { "type": "categorical", "property": "num", // 必写 "stops": [ // 仅当properties.num等于5时,面颜色为红色 [5, "red"], // 仅当properties.num等于10时,面颜色为绿色 [10, "green"], ] } } }); ``` (4) 恒等类型 最终输出值完全等于输入值 ```js map.addLayer({ "paint": { "line-color": { "type": "identity", // 线颜色直接取properties.color的值 "property": "color" } } }); ``` 2. property 是一个字符串,表示属性名。 如果指定,该函数将采用指定的要素属性作为输入。 ```js "circle-radius": { "property": "radius", "stops": [ // radius 为1时,半径为2 [1, 2], // radius 为5时,半径为 20 [5, 20], // radius 为10时,半径为5 [10, 5] ] } // 此时的 1 5 10 就是radius 的取值步长,在1-5的范围内,取值是2-20, ``` 3. base 类型是number,默认是1。 插值曲线的指数基础。它控制函数输出的增加速率。较高的值会使输出向该范围的高端增加更多。值接近1时,输出线性增加. 调节线性 ```js "circle-radius": { "base": 1.3, "stops": [ // radius 为1时,半径为2 [1, 2], // radius 为5时,半径为 20 [10, 20], ] } // base为1时,是正常显示,小于1,如0.8时候,差距会变小,大于1时,差距会拉大,大的更大,小的更小。 ``` 4. type 可选值:"identity", "exponential","interval", or "categorical" * identity:返回其输入作为输出的函数。 * exponential:通过在停靠点之间进行插值而生成输出的函数,该停靠点小于且大于函数输入。域(输入值)必须是数字,并且style属性必须支持插值。支持插值的样式属性用标记为“指数”符号,并且指数是这些属性的默认函数类型。 * interval: 返回停止点输出值刚好小于函数输入的函数。域(输入值)必须是数字。任何样式属性都可以使用间隔函数。对于标有,“间隔”符号的属性,这是默认功能类型。 *categorical: 一个函数,其返回的停止值等于函数输入的输出值。 5. default 如果没有其他可用值,则该值将用作备用功能。在以下情况下使用它: * 在分类函数中,当要素值与任何 stops值都不匹配时。 * 在特性和缩放特性函数中,当要素不包含指定特性的值时。 * 在标识函数中,当 feature值对于样式属性无效时(例如,如果函数用于 circle-color属性,但特征属性值不是字符串或无效颜色)。 * 在间隔或指数属性以及缩放和特性函数中,当特征值不是数字时。 如果未提供默认值,则在这些情况下使用style属性的默认值。 6. colorSpace 可选值:"rgb", "lab", "hcl"。 插值颜色的颜色空间。与RGB空间中插值的颜色相比,在LAB和HCL等感知颜色空间中插值的颜色趋向于生成看起来更一致的色带,并产生易于区分的颜色。 * "rgb": 使用RGB颜色空间内插颜色值 * "lab":使用LAB颜色空间对颜色值进行插值。 * "hcl": 使用HCL颜色空间内插颜色值,分别内插Hue,Chroma和Luminance通道。 7. Zoom-and-property functions 允许地图要素的外观随其属性和缩放而改变。每个止挡是一个包含两个元素的数组,第一个是具有属性输入值和缩放的对象,第二个是函数输出值。请注意,对属性功能的支持尚未完成。 ```js { "circle-radius": { "property": "rating", "stops": [ // zoom is 0 and "rating" is 0 -> circle radius will be 0px [{zoom: 0, value: 0}, 0], // zoom is 0 and "rating" is 5 -> circle radius will be 5px [{zoom: 0, value: 5}, 5], // zoom is 20 and "rating" is 0 -> circle radius will be 0px [{zoom: 20, value: 0}, 0], // zoom is 20 and "rating" is 5 -> circle radius will be 20px [{zoom: 20, value: 5}, 20] ] } } ``` 用法示例 ```js // match // match通常用于枚举型的字段渲染,如唯一值渲染。 'circle-color': [ 'match', ['get', 'type'], 1, '#FFD273', 2, '#E86D68', '#A880FF' ] // case // case通常用于分段数值型的字段渲染,值域是前关后开,如分级渲染。 'circle-color': [ 'case', ['<', ['get', 'speed'], 10.8], 'rgba(0,0,0,0)', //<10.8 ['<', ['get', 'speed'], 17.2], 'rgba(153, 255, 153, .9)', //>=10.8 & <17.2 ['<', ['get', 'speed'], 24.5], 'rgba(102, 204, 255, .9)', ['<', ['get', 'speed'], 32.7], 'rgba(255, 255, 102, .9)', ['<=', ['get', 'speed'], 41.5], 'rgba(253, 139, 0, .9)', ['<=', ['get', 'speed'], 50.1], 'rgba(255, 51, 0, .9)', //>=41.5 & <50.1 'rgba(255, 0, 255, .9)' // 默认值, >=50.1 ] // 注意: // 第一个的判断是<; // 中间的判断是>= 和 <; // 最后一个判断是>=; // step // step和上面的case很类似,只是值域是前开后关的。 // <=100, 100-500, >500 "circle-color": [ "step", ["get", "count"], "#51bbd6", 100, "#f1f075", 500, "#f28cb1" // other ] 'circle-color': [ 'step', ['to-number', ['get', 'CID']], '#0098A3', 10, '#00CA8D', 20, '#37C508', 30, '#98F300', 40, '#EFFF85' ] // interpolate // interpolate 我们可通过interpolate实现按照比例的插值渲染。 // <=8, 8-10, >10 "background-color": [ "interpolate", ["linear"], ["zoom"], 8, "rgba(0, 0, 255, 0.2)", 10, "rgba(255, 0, 0, 0.2)" ] // <=20, 20-60, 60-100, >100 'fill-extrusion-color': [ 'interpolate', ['linear'], ['get', 'height'], 20, 'rgba(255,255,191, 0.65)', 60, 'rgba(253,174,97, 0.65)', 100, "rgba(215,25,28, 0.65)" ] // exponential,指数 "fill-opacity": [ "interpolate", ["exponential", 1.5], ["zoom"], 2, 0.3, 7, 0 ] // 说明: // zoom是一个特殊字符,特制地图的缩放级别,同样的还有geometry-type,特指的是geom类型。 // 说明: // 对于非数值型的字段,我们可以用to-number对字段进行转换。 ``` ### 一些给filter用的表达式 ```js { paint:{ ... } filter: ['in','radius',5] } ``` 1. 是否存在属性 ```js ["has", key] feature[key] 存在 ["!has", key] feature[key] 不存在 ``` 2. 比较过滤器 ```js ["==", key, value] 相等: feature[key] = value ["!=", key, value] 不相等: feature[key] ≠ value [">", key, value] 大于: feature[key] > value [">=", key, value] 大于或等于: feature[key] ≥ value ["<", key, value] 小于: feature[key] < value ["<=", key, value] 小于或等于: feature[key] ≤ value ``` 3. in ```js ["in", key, v0, ..., vn] 集合包含:feature [key] ∈ { v0,...,vn } ["!in", key, v0, ..., vn] 设置排除:feature [key] ∉ { v0,...,vn } ``` 4. 组合过滤器 ["all", f0, ..., fn] 逻辑AND:F0 ∧...∧ FN ["any", f0, ..., fn] 逻辑OR:F0 ∨...∨ FN ["none", f0, ..., fn] 逻辑NOR:¬ F0 ∧...∧¬ FN 以上四类中的: key 必须是 feature property属性或者以下特殊值: * "$type":feature 类型,他可以和 "==","!=", "in", and "!in" 一起使用. 结果可能是"Point", "LineString", 或 "Polygon". * "$id":功能标识符。此键可以与"==","!=","has","!has","in",和"!in"一起使用。 ```js { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "id": "1", "name":"点1", "desc":"这是点1", "radius": 1 }, "id":54, //$id 就是这里 "geometry": { "type": "Point", //$type 就是他 "coordinates": [ 108.9902114868164, 34.24813554589752 ] } } ] } ``` value 值必须是必须是一个字符串string,数字number或布尔值boolean进行比较的属性值。 相关用法示例 ```js // ==和!=可实现根据某个字段图层的过滤展示。如:只在地图上展示昌平区或者在地图上展示除昌平外的所有区域。 // 只在地图上展示昌平区 var filter = ['==', 'name', '昌平区']; //地图上展示除昌平外的所有区域 var filter = ['!=', 'name', '昌平区']; // >、>=、<、<=是通过比较大小的方式,实现图层的过滤,所以此处需要的字段得是数字类型或者通过to-number将字段转换成数字类型。如:展示count>=10的区域。 var filter = ['>=', 'count', 10]; // in和match都可实现对图层根据某个字段进行多值过滤。如:在地图上展示昌平区和海淀区。 // in var filter = [ 'in', 'name', '昌平区', '海淀区' ]; // match var filter = [ "match", [ "get", "name" ], [ "昌平区", "海淀区", ], true, false ] // 接着上面的例子,如果我们要实现在地图中展示除海淀和昌平区外的其他区域,我们可以直接用!in和将match的条件互换的方式来实现,如下: // in var filter = [ '!in', 'name', '昌平区', '海淀区' ]; // match var filter = [ "match", [ "get", "name" ], [ "昌平区", "海淀区", ], false, true ] // 多条件 // 有时候,会存在多条件的过滤,例如:我们选择type==1并且count>10的区域,我们可以这么写: var filter = [ 'all', ['>=', 'count', 10], ['==', 'type', 1] ] // 有时我们会存在根据几何类型来进行过滤,此时,我们可用: var filter = [ "==", ["geometry-type"], "LineString" ]; ``` 表达式参考 ## 一、数学运算符 ## 二、逻辑运算符 ## 三、字符串运算符 ## 四、数据运算符 数据表达式是用来访问数据的,例如: ['get','name'],就是获取数据中的name值 ## 五、Camera operators 表达式 相机类表达式,可以根据地图 zoom 的值变化而变化。 例如: ```js { "circle-radius": [ "interpolate", ["linear"], ["zoom"], // zoom 是5或者更小 -> 圆半径是1px 5, 1, // zoom 是10或者更大 -> 圆半径是5px 10, 5 ] } ``` --- # 坐标转换 ## 坐标转换原因 CAD二维图形世界坐标系是`笛卡尔直角坐标系`,图形的范围是指绘制出的图形所占的范围。 如用户在坐标 [1000,1000]和[2000,2000]绘制了一条直线,那么图形的范围 [左上角x, 左下角y,右上角x,右下角y] 就是[1000,1000,2000,2000]。 但有时候用户制图时坐标用的是大地坐标,坐标数值会比较大,如在坐标[600000000,30000000],[620000000,50000000]绘制了一条直线,那图形的范围就是[600000000,30000000,700000000,70000000]。 同时在一些基于互联网地图开发时,经纬度数值都很小。 为了解决大坐标渲染的问题和统一互联网底图和CAD底图坐标的需要,我们渲染底层采用了经纬度进行渲染,绘制时需要把实际坐标转换成经纬度进行绘制。经纬度范围为 经度范围为(-180,180), 纬度范围为(-85,85) ## 坐标转换关系 |名称|说明| |:---|:---| |几何坐标|CAD图中实际的坐标| |经纬度坐标|经纬度坐标(在渲染时必须要转成经纬度坐标)| |墨卡托坐标|投影坐标||
### 如果底图为CAD图为坐标系时 转换关系如下图
通过调用 `toLngLat` 把CAD图转成渲染用的`经纬度坐标`, 如会把 [CAD范围左下角x1,左下角y1]转成 经纬度[-180,-85]。
所以**这时候所转成的经纬度只是底层渲染用的经纬度坐标数值,不是实际互联网地图中所用到的经纬度坐标,这是两个不同的概念**。 ### 如果底图是互联网地图如天地图时 转换关系如下图
这时候因为互联网地图的范围和渲染用到的经纬度范围完全一样, 所以**这时候所转成的经纬度就是实际互联网地图中所用到的经纬度坐标,这是同一个概念**。 ### 有地理坐标系的CAD图与互联网地图经纬度转换 如果CAD图有地理坐标系系,如是2000坐标系时,需要转换为互联网地图经纬度时。这时候需要用第三方坐标转换库如`proj4`进行转换。转换原理如下图:
### 在CAD中使用UCS坐标绘制的图坐标与WCS坐标转换 在云端管理平台打开图后,右侧属性栏中 `ucsorg` 的值不为零的话表示有ucs,或者用`map`的`hasUcs`方法来判断是否有ucs坐标。 在唯杰地图获取的是CAD的`WCS`坐标。 `WCS坐标`转`UCS坐标`用 `map.wcsToUcs` `UCS坐标`转`WCS坐标`用 `map.ucsToWcs` ## 总结 ::: tip 总结1 唯杰地图渲染用的经纬度坐标只有在底图为互联网底图(如天地图、高德地图)时,才是真正的经纬度坐标。
否则为CAD底图时,经纬度坐标值只是一个用于底层渲染的坐标值,没有实际的地理意义,只是为了解决大坐标渲染的问题和统一互联网底图和CAD底图坐标的需要。
如果CAD底图是有真正地理坐标系,如北京54坐标、西安80坐标、2000坐标系时,需要转换成wgs84经纬度时,可以用SDK提供的vjmap.transform.convert方法或者自己用第三方库如proj4进行转换,具体可以参考演示示例中的`互联网地图` 或者使用四参数对不同的坐标系进行坐标转换[如何把另外一个坐标系的数据叠加到当前cad图上](https://vjmap.com/guide/faq.html#%E5%A6%82%E4%BD%95%E6%8A%8A%E5%8F%A6%E5%A4%96%E4%B8%80%E4%B8%AA%E5%9D%90%E6%A0%87%E7%B3%BB%E7%9A%84%E6%95%B0%E6%8D%AE%E5%8F%A0%E5%8A%A0%E5%88%B0%E5%BD%93%E5%89%8Dcad%E5%9B%BE%E4%B8%8A) ::: ::: tip 总结2 如果map关联了[投影对象](/guide/mapProjection.html) `map.attach(svc, prj)` 则
map.toLngLat 等价于 prj.toLngLat
map.fromLngLat 等价于 prj.fromLngLat
CAD实际几何坐标 转 底层渲染的经纬度坐标 一律请调用 toLngLat (与底层绘制有关的,都得调用toLngLat转经纬度)
由底层渲染的经纬度坐标 转 CAD实际几何坐标 一律请调用 fromLngLat (得到CAD图上面的实际几何坐标,都得调用fromLngLat转CAD实际几何坐标)
::: ::: tip 总结3 因为CAD图坐标转渲染经纬度时,转换过程是非线性的,在进行`几何计算`如相交、插值等请使用 `CAD几何坐标`, `渲染时`再转成 `经纬度坐标` ::: ::: tip 总结4 因为CAD图坐标转渲染经纬度时,和CAD当前绘制范围有关。绘图范围变化所转换的渲染经纬度的值也会变化。所以如果要保存坐标至后台下次打开再加载显示的时候,一定要保存CAD图坐标, 不要保存经纬度。因为如果CAD图版本更新了,如果绘图范围有变化的话,加载经纬度坐标就会和原图对应不上了。而加载CAD图坐标能完全对应上。 例如 [自定义高亮区域](https://vjmap.com/demo/#/demo/map/service/29customzhoverregion) 这个示例 当点击保存数据时会获取绘图对象的渲染经纬度,通过调用 `map.fromLngLat` 转为CAD几何坐标再保存至后台 ```js // 保存数据至后台过程 // 获取获取绘图对象的渲染经纬度 let lngLatJson = draw.getAll(); // 渲染经纬度转成CAD几何坐标 let cadJson = map.fromLngLat(lngLatJson); ``` 而在加载后台数据显示至地图中,则需要获取 `map.toLngLat` 把CAD几何坐标转为绘图对象的渲染经纬度进行绘制。 ```js // 前端加载后台数据进行显示 let cadJson = {......}; // 后台获取至的CAD几何坐标 // CAD几何坐标转成渲染经纬度 let lngLatJson = map.toLngLat(cadJson); // 设置绘图对象坐标 draw.set(lngLatJson) ``` ::: ## 如何对CAD图进行坐标转换处理 1、获取要转换的四参数 在CAD图上拾取`三组及以上`的对应点坐标(公共点尽量分布在图的四周) ```js let pts = [ { cadX: 1, // CAD图上对应点一的坐标X cadY: 1, // CAD图上对应点一的坐标Y destX: 101, // 对应点一目标图上的坐标x destY: 101, // 对应点一目标图上的坐标y }, { cadX: 1, // CAD图上对应点二的坐标X cadY: 5, // CAD图上对应点二的坐标Y destX: 101, // 对应点二目标图上的坐标x destY: 105, // 对应点二目标图上的坐标y }, { cadX: 5, // CAD图上对应点三的坐标X cadY: 5, // CAD图上对应点二的坐标Y destX: 105, // 对应点三目标图上的坐标x destY: 105, // 对应点三目标图上的坐标y } ] let cadPoints = []; let destPoints = []; for(let n = 0; n < pts.length; n++) { cadPoints.push(vjmap.geoPoint([pts[n].cadX, pts[n].cadY])) destPoints.push(vjmap.geoPoint([pts[n].destX, pts[n].destY])) } // 通过坐标参数求出四参数 let fourparam = vjmap.coordTransfromGetFourParamter(cadPoints, destPoints, false); console.log([fourparam.dx, fourparam.dy, fourparam.scale, fourparam.rotate]) // 计算误差 for(let i = 0; i< cadPoints.length; i++) { const destPt = vjmap.coordTransfromByFourParamter(cadPoints[i], fourparam); console.log("原始点:" + cadPoints[i], "计算结果:" + destPt, "误差:" + destPoints[i].distanceTo(destPt)) } ``` 例如`把ucs的图转成wcs坐标的图` 用如下代码可自动获取原图wcs到ucs转换的四参数 ```js let cadPoints = []; let destPoints = [] // 获取地图范围的四个点算出对应的ucs坐标,做为公共点 let mapExtent = map.getMapExtent() let pts = mapExtent.toPointArray() for (let n = 0; n < pts.length; n++) {     let wcs = vjmap.geoPoint(pts[n]);     let ucs = map.wcsToUcs(wcs);     cadPoints.push(wcs)     destPoints.push(ucs); } // 通过坐标参数求出四参数 let fourparam = vjmap.coordTransfromGetFourParamter(cadPoints, destPoints, false); console.log([fourparam.dx, fourparam.dy, fourparam.scale, fourparam.rotate]) ``` 2、在云端管理平台里面打开此图,点击`更多功能`里面的`在线处理图形` 3、在`在线处理图形`中`直接输入四参数格式`输入上面获取到`fourparam.dx, fourparam.dy, fourparam.scale, fourparam.rotate`值 4、点击确定,选择新建图形。即可。 ## 坐标控件 CAD为底图坐标系
// --打开地图[栅格瓦片]--以栅格瓦片方式打开已上传的CAD的DWG格式的图
// js代码
// 新建地图服务对象,传入服务地址和token
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: env.exampleMapId, // 地图ID,(请确保此ID已存在,可上传新图形新建ID)
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    message.error(res.error)
}
// 获取地图的范围
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
// 建立坐标系
let prj = new vjmap.GeoProjection(mapExtent);
// 新建地图对象
let map = new vjmap.Map({
    container: 'map', // container ID
    style: svc.rasterStyle(), // 栅格瓦片样式
    center: prj.toLngLat(mapExtent.center()), // 中心点
    zoom: 2,
    renderWorldCopies: false
});
// 地图关联服务对象和坐标系
map.attach(svc, prj);
// 使地图全部可见
map.fitMapBounds();
await map.onLoad();
let mousePositionControl = new vjmap.MousePositionControl({
    showLatLng: true
});
map.addControl(mousePositionControl, "top-left");


互联网地图为底图坐标系
// --天地图地图--
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 根据地图范围建立经纬度投影坐标系
let prj = new vjmap.LnglatProjection();
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: {
        version: svc.styleVersion(),
        glyphs: svc.glyphsUrl(),
        sources: {
            tdt1: {
                type: 'raster',
                // 影像
                //tiles: ["https://t3.tianditu.gov.cn/DataServer?T=img_w&X={x}&Y={y}&L={z}&tk=7baeffb96bf61861b302d0f963cfda66"],
                // 道路
                tiles: ["https://t3.tianditu.gov.cn/DataServer?T=vec_w&X={x}&Y={y}&L={z}&tk=7baeffb96bf61861b302d0f963cfda66"],
            },
            tdt2: {
                type: 'raster',
                tiles: ["https://t3.tianditu.gov.cn/DataServer?T=cva_w&X={x}&Y={y}&L={z}&tk=7baeffb96bf61861b302d0f963cfda66"],
            }
        },
        layers: [{
            id: 'tdt1',
            type: 'raster',
            source: 'tdt1',
        },{
            id: 'tdt2',
            type: 'raster',
            source: 'tdt2',
        }]
    },
    center: prj.toLngLat([116.3912, 39.9073]),
    zoom: 10,
    pitch: 0,
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
// 根据地图本身范围缩放地图至全图显示
//map.fitMapBounds();
await map.onLoad();
let mousePositionControl = new vjmap.MousePositionControl({
    showLatLng: true
});
map.addControl(mousePositionControl, "top-left");


--- # 相关示例 ## 增加Marker至地图 在地图`sys_hello`中,把此地图中一个CAD坐标为[725.32,18969.19]的点加进地图中 ```js let marker = new vjmap.Marker(); let lngLat = map.toLngLat([725.32,18969.19]); marker.setLngLat(lngLat); marker.addToMap() ```
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: "sys_hello", // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapLightStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    showError(res.error)
    return;
}
// 获取地图范围
let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapBounds);
//
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapBounds.center()), // 设置地图中心点
    zoom: 0, // 设置地图缩放级别
    pitch: 0,
    renderWorldCopies: false // 不显示多屏地图
});
//
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();
let marker = new vjmap.Marker();
let lngLat = map.toLngLat([725.32,18969.19]);
marker.setLngLat(lngLat);
marker.addTo(map)
let mousePositionControl = new vjmap.MousePositionControl({
    showLatLng: true
});
map.addControl(mousePositionControl, "top-left");


## 获取Marker的CAD坐标 ```js // 获取Marker的渲染经纬度坐标 let lngLat = marker.getLngLat(); // 把渲染经纬度坐标坐标转成CAD几何坐标 let pt = map.fromLngLat(lngLat) ```
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: "sys_hello", // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapLightStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    showError(res.error)
    return;
}
// 获取地图范围
let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapBounds);
//
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapBounds.center()), // 设置地图中心点
    zoom: 1, // 设置地图缩放级别
    pitch: 0,
    renderWorldCopies: false // 不显示多屏地图
});
//
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();
let marker = new vjmap.Marker({
    draggable: true
});
marker.setLngLat([10, 10]);
marker.addTo(map)
marker.on("dragend", e => {
    // 获取Marker的渲染经纬度坐标
    let lngLat = marker.getLngLat();
    // 把渲染经纬度坐标坐标转成CAD几何坐标
    let pt = map.fromLngLat(lngLat)
    // 像素坐标
    let pixel = map.project(lngLat);
    map.logInfo(`当前Marker的CAD坐标为 X:${pt.x.toFixed(4)}, y:${pt.y.toFixed(4)}, 经纬度:${lngLat.lng.toFixed(4)},${lngLat.lat.toFixed(4)},像素坐标: ${pixel.x.toFixed(0)},${pixel.y.toFixed(0)}`, "success", 3000)
})
let mousePositionControl = new vjmap.MousePositionControl({
    showLatLng: true
});
map.addControl(mousePositionControl, "top-left");
map.logInfo("拖动Marker,显示当前的CAD坐标", 600000)


## 根据CAD坐标序列创建多边形 已知几组CAD的GeoJSON坐标序列和属性值,创建多边形增加至地图中。通过调用 `map.toLngLat`(data) 把CAD的GeoJSON几何坐标转渲染经纬度 ```js let data = { "type": "FeatureCollection", "features": [ { "id": "1", "type": "Feature", "properties": { "name": "polygon1", "color": "#00ffff" }, "geometry": { "coordinates": [ [ [ 3006.0952985307595, 15198.972934052355 ], [ 12606.64046359223, 9285.59366571666 ], [ 4745.324495099878, 3720.060236694666 ], [ 3006.0952985307595, 15198.972934052355 ] ] ], "type": "Polygon" } }, { "id": "2", "type": "Feature", "properties": { "name": "polygon2", "color": "#ff00ff" }, "geometry": { "coordinates": [ [ [ 19841.833921319492, 13807.589576796912 ], [ 19841.833921319492, 4207.0444117340885 ], [ 26172.62819683108, 4207.0444117340885 ], [ 26172.62819683108, 13807.589576796912 ], [ 19841.833921319492, 13807.589576796912 ] ] ], "type": "Polygon" } } ] } let polygon = new vjmap.Polygon({ data: map.toLngLat(data), // CAD几何坐标转渲染经纬度 fillColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', ['get', 'color']], fillOpacity: 0.8, fillOutlineColor: "#f00", isHoverPointer: true, isHoverFeatureState: true }); polygon.addTo(map); ```
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: "sys_hello", // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapLightStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    showError(res.error)
    return;
}
// 获取地图范围
let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapBounds);
//
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapBounds.center()), // 设置地图中心点
    zoom: 0, // 设置地图缩放级别
    pitch: 0,
    renderWorldCopies: false // 不显示多屏地图
});
//
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();
let data = {
    "type": "FeatureCollection",
    "features": [
        {
            "id": "1",
            "type": "Feature",
            "properties": {
                "name": "polygon1",
                "color": "#00ffff"
            },
            "geometry": {
                "coordinates": [
                    [
                        [
                            3006.0952985307595,
                            15198.972934052355
                        ],
                        [
                            12606.64046359223,
                            9285.59366571666
                        ],
                        [
                            4745.324495099878,
                            3720.060236694666
                        ],
                        [
                            3006.0952985307595,
                            15198.972934052355
                        ]
                    ]
                ],
                "type": "Polygon"
            }
        },
        {
            "id": "2",
            "type": "Feature",
            "properties": {
                "name": "polygon2",
                "color": "#ff00ff"
            },
            "geometry": {
                "coordinates": [
                    [
                        [
                            19841.833921319492,
                            13807.589576796912
                        ],
                        [
                            19841.833921319492,
                            4207.0444117340885
                        ],
                        [
                            26172.62819683108,
                            4207.0444117340885
                        ],
                        [
                            26172.62819683108,
                            13807.589576796912
                        ],
                        [
                            19841.833921319492,
                            13807.589576796912
                        ]
                    ]
                ],
                "type": "Polygon"
            }
        }
    ]
}
let polygon = new vjmap.Polygon({
    data: map.toLngLat(data), // CAD几何坐标转渲染经纬度
    fillColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', ['get', 'color']],
    fillOpacity: 0.8,
    fillOutlineColor: "#f00",
    isHoverPointer: true,
    isHoverFeatureState: true
});
polygon.addTo(map);
let mousePositionControl = new vjmap.MousePositionControl({
    showLatLng: true
});
map.addControl(mousePositionControl, "top-left");


## 获取绘制图层的CAD坐标 通过调用 `map.fromLngLat` 把绘制图层的GeoJson转为CAD几何坐标 ```js // 获取获取绘图对象的渲染经纬度 let lngLatJson = draw.getAll(); // 渲染经纬度转成CAD几何坐标 let cadJson = map.fromLngLat(lngLatJson); ```
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: "sys_hello", // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapLightStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    showError(res.error)
    return;
}
// 获取地图范围
let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapBounds);
//
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapBounds.center()), // 设置地图中心点
    zoom: 0, // 设置地图缩放级别
    pitch: 0,
    renderWorldCopies: false // 不显示多屏地图
});
//
// 关联服务对象和投影对象
map.attach(svc, prj);
let mousePositionControl = new vjmap.MousePositionControl({
    showLatLng: true
});
map.addControl(mousePositionControl, "bottom-left");
const draw = new vjmap.Draw.Tool();
map.addControl(draw, 'top-left');
// 增加一个按钮
let options = {};
options.buttons = [
    {
        id: "getdaa",
        title: "查看绘制的GeoJson数据(CAD坐标)",
        text: "查看",
        onActivate: (ctx, e) => {
            // 获取获取绘图对象的渲染经纬度
            let lngLatJson = draw.getAll();
            // 渲染经纬度转成CAD几何坐标
            let cadJson = map.fromLngLat(lngLatJson);
            map.logInfo(JSON.stringify(cadJson));
            console.log(cadJson)
        }
    }
];
let btnGroupCtrl = new vjmap.ButtonGroupControl(options);
map.addControl(btnGroupCtrl, 'top-right');


## 获取鼠标当前位置的坐标
// 地图服务对象
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: "sys_hello", // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    message.error(res.error)
}
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(res.bounds);
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), //svc.rasterBlankStyle(0, 24), // 样式,这里是栅格样式
    center: prj.toLngLat(prj.getMapExtent().center()), // 设置地图中心点
    zoom: 2, // 设置地图缩放级别
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
// 根据地图本身范围缩放地图至全图显示
map.fitMapBounds();
await map.onLoad();
const addStyle = cssStyle => {
    let style = `<style>${cssStyle }</style>`;
    let ele = document.createElement('div');
    ele.innerHTML = style;
    document.getElementsByTagName('head')[0].appendChild(ele.firstElementChild)
}
// 增加一个popup自定义的样式
addStyle(`
    .custom-popup .vjmapgis-popup-content {
        background-color: #A0FFA0;
    }
    .custom-popup .vjmapgis-popup-tip {
        border-top-color: #A0FFA0;
    }
`)
// 跟随鼠标动的弹框
const div = window.document.createElement('div');
div.innerHTML = '当前位置:';
const popup = new vjmap.Popup({
    className: "custom-popup",
    closeOnClick: false,
    closeButton: false
})
//.setHTML("Hello VJMap!")
.setDOMContent(div)
.trackPointer()
.addTo(map);
map.on("mousemove", e => {
    // 获取当前鼠标的渲染经纬度坐标
    let lngLat = e.lngLat;
    // 把渲染经纬度坐标坐标转成CAD几何坐标
    let pt = map.fromLngLat(lngLat)
    // 像素坐标
    let pixel = map.project(lngLat);
    div.innerHTML =`CAD坐标为 X:${pt.x.toFixed(4)}, Y:${pt.y.toFixed(4)}<br/> 经纬度:${lngLat.lng.toFixed(4)},${lngLat.lat.toFixed(4)}<br/>像素坐标: ${pixel.x.toFixed(0)},${pixel.y.toFixed(0)}`
})


--- # Marker > Marker的样式与常规的css样式一致 > ## 常用方法 |方法名|参数|返回值|说明| |:---|:---|:---|:---| |addTo(map)|map|Marker.this|将这个Marker标注添加到地图上| |remove()|无|Marker.this |将这个Marker标注添加到地图上| |getLngLat()|无|Lnglat|返回该标注的经纬度对象| |setLngLat(lnglat)|lnglat|Marker.this|设置该标注的经纬度信息| |getElement()|无|[HTML5 Element](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element)|返回该标注的html信息| |setPopup(popup)|Popup|Marker.this|针对该标注绑定一个Popup信息| |getPopup()|无|Popup|返回该标注的Popup对象| |togglePopup()|无|Marker.this|触发该标注的Popup对象| |getOffset()|无|Point|返回该标注的偏移值| |setOffset(offset)|offset|Offset|设置该标注的偏移值| **示例代码** ```js var marker = new vjmap.Marker(); marker.setLngLat(latLng).addTo(map); ``` ## 根据CAD坐标增加Marker至地图 在地图`sys_hello`中,把此地图中一个CAD坐标为[725.32,18969.19]的点加进地图中 ```js let marker = new vjmap.Marker(); let lngLat = map.toLngLat([725.32,18969.19]); marker.setLngLat(lngLat); marker.addToMap() ```
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: "sys_hello", // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapLightStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    showError(res.error)
    return;
}
// 获取地图范围
let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapBounds);
//
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapBounds.center()), // 设置地图中心点
    zoom: 0, // 设置地图缩放级别
    pitch: 0,
    renderWorldCopies: false // 不显示多屏地图
});
//
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();
let marker = new vjmap.Marker();
let lngLat = map.toLngLat([725.32,18969.19]);
marker.setLngLat(lngLat);
marker.addTo(map)
let mousePositionControl = new vjmap.MousePositionControl({
    showLatLng: true
});
map.addControl(mousePositionControl, "top-left");


## 自定义标记样式 > Marker的Picture样式主要还是通过HTML的方式来实现的, **核心代码** ``` javascript #marker { background-image: url('../../images/myImg/diqiu.png'); background-size: cover; width: 20px; height: 20px; border-radius: 50%; cursor: pointer; } // 创建一个DOM element var el = document.createElement('div'); el.id = 'marker';//将该div与marker的样式绑定 new vjmap.Marker(el);////绑定对应的HTML的元素 ``` > Marker的 Picture&Text 样式主要还是通过HTML的方式来实现的,不同之处在与是通过一个`父div`动态添加`子picture div`和`子text div`来实现 **核心代码** ``` javascript // 创建一个子picture DOM element var icon = document.createElement('div'); icon.id = 'marker-icon';//绑定图片样式 // 创建一个子text DOM element var text = document.createElement('div'); text.id = 'marker-text';//绑定文字样式 text.innerText = "测试注记";//添加文字样式 // 创建一个父 div DOM elemen var el = document.createElement('div'); el.id = 'marker'; //将该div与marker的样式绑定 el.appendChild(icon);//整个父标注添加图片内容 el.appendChild(text);//整个父标注添加文字内容 new vjmap.Marker(el) ``` ## 自定义Marker缩放时如何保持位置不动 marker是通过设置`一个点`位置进行显示,但是marker是一个div,范围是`一个矩形`,所以要设置marker的锚点和偏移量,也就是这个点在矩形的位置,缩放时这个位置就不会发生变化。 设置`anchor`属性:anchor属性定义了Marker相对于其位置的锚点。常用的锚点包括选项包括'center','top','bottom','left','right','top-left','top-right','bottom-left',和'bottom-right'。(可选,默认'center')。 使用`offset`属性:offset可以用来调整Marker的显示位置。通过指定偏移量,可以将Marker相对于其默认位置进行微调。以像素为单位的偏移量,作为要应用的点对象相对于元素的中心。负数表示向左和向上。 示例: 官网提供的`默认marker`div的高为`28`个像素, `默认marker`地图放大时,底部中间的位置是不会变化的。所以需要把锚点设置为`'bottom'` 或者 锚点还是缺省的`'center'`但是`offset`需要往下移动高度的一半的像素值,也就是`14`个像素。所以`offset`应为[0,14] ```js const marker = new vjmap.Marker({ offset: [0, 14], // 自定义的偏移量 anchor: 'center' // 锚点设置为中心 }) .setLngLat([lng, lat]) // 设置经纬度 .addTo(map); ``` ## marker和symbol的区别 > 图文注记实现方式有两种,一种是采取marker的方式,另一种是采用symbol样式。 marker是用`div`的形式进行开发,优点是实现比较灵活,如可用`vue`进行开发[查看示例](https://vjmap.com/demo/#/demo/map/marker/04markerMvvm)。缺点是如果绘制的太多,会影响性能。 [symbol](https://vjmap.com/demo/#/demo/map/overlay/symbol/symbol) 用基于`webgl`进行渲染,这种方式使用参数相对固定,扩展性差,但是显示效果速度极佳。 ## 自定义的Marker设置动画为什么不生效 自定义的Marker通过`marker.setAnimation("MAP_ANIMATION_BOUNCE");` 设置动画效果有时候不生效的原因是这个动画是针对默认marker的,通过访问`marker.getElement().children[0]` 来设置`css动画效果`的。所以如果自定义的Marker如果不是这个`DOM结构`,需要在`外层用div`对整个marker进行包一下。如 ```js  let elMarker = document.createElement('div');  let el = document.createElement('div');  el.className = 'marker';  el.style.backgroundImage =         'url("static/assets/images/nilt.png")'; el.style.width = '42px';  el.style.height = '68px';  el.style.backgroundSize = '100%';   el.addEventListener('click', function (e) {      message.info("you click a marker");   });   elMarker.appendChild(el)   // Add markers to the map.   let marker = new vjmap.Marker({         element: elMarker,         anchor: 'bottom',         draggable: true   })   marker.setAnimation("MAP_ANIMATION_BOUNCE");   marker.setLngLat(map.toLngLat(mapBounds.randomPoint()))         .addTo(map); ``` ## Maker相关示例 ![image-20240927172636689](https://vjmap.com/blogimages/202409271726827.png) - [创建点标记](https://vjmap.com/demo/#/demo/map/marker/01marker) - [多个点标记](https://vjmap.com/demo/#/demo/map/marker/02markerSeveral) - [点标记事件](https://vjmap.com/demo/#/demo/map/event/marker/01eventMarkerOne) - [多个点标记事件](https://vjmap.com/demo/#/demo/map/event/marker/02eventMarkerSome) - [点标记悬浮弹框](https://vjmap.com/demo/#/demo/map/event/marker/03eventMarkerHover) - [点标记拖拽](https://vjmap.com/demo/#/demo/map/marker/03markerSetDrag) - [地图上绑定多个点标注记](https://vjmap.com/demo/#/demo/map/marker/03markerSetMany) - [呼吸的光圈](https://vjmap.com/demo/#/demo/map/marker/03markerzBreathingAperture) - [扩散的点](https://vjmap.com/demo/#/demo/map/marker/03markerzDiffusedAperture) - [荧光点](https://vjmap.com/demo/#/demo/map/marker/03markerzFluorescence) - [发光的光环](https://vjmap.com/demo/#/demo/map/marker/03markerzHaloRing) - [旋转的光环](https://vjmap.com/demo/#/demo/map/marker/03markerzRotatingAperture) - [旋转的文本框](https://vjmap.com/demo/#/demo/map/marker/03markerzRotatingTextBorder) - [标记动画图层](https://vjmap.com/demo/#/demo/map/marker/03markerzzAnimateLayer) - [vue3创建的Marker对象](https://vjmap.com/demo/#/demo/map/marker/04markerMvvm) - [在指定级别上显示的点标记](https://vjmap.com/demo/#/demo/map/marker/04markerZoom) - [随缩放级别缩小的点标记](https://vjmap.com/demo/#/demo/map/marker/05markerScaleZoom) - [随地图视图范围变化自动移除增加的点标记](https://vjmap.com/demo/#/demo/map/marker/06markerRemoveNoInMapView) - [一个有高度的点标记](https://vjmap.com/demo/#/demo/map/marker/07markerHeight) - [和地图对齐的点标记](https://vjmap.com/demo/#/demo/map/marker/08markerAlignmentMap) - [标记Marker聚合(重叠时显示总数)](https://vjmap.com/demo/#/demo/map/marker/14markerClusterShowCount) - [标记Marker聚合(重叠时只显示一个)](https://vjmap.com/demo/#/demo/map/marker/15markerNoOverlap) - [点符号聚合](https://vjmap.com/demo/#/demo/map/marker/markerClusterer) - [点符号聚合自定义图标](https://vjmap.com/demo/#/demo/map/marker/markerClustererCustomIcon) - [点标记Marker聚合(聚合通过Symbol绘制)](https://vjmap.com/demo/#/demo/map/marker/markerClustererMarker) ## 类型定义 ```ts export interface MarkerOptions { /** 用作标记的DOM元素。默认值是淡蓝色、液滴形状的SVG标记 */ element?: HTMLElement | undefined; /** 以像素为单位偏移为PointLike对象,以相对于元素的中心应用。负数表示向左和向上。*/ offset?: PointLike | undefined; /** 一个字符串,指示标记中应通过Marker.setLngLat定位到最接近描点的部分。 * 选项有 `'center'`, `'top'`, `'bottom'`, `'left'`, `'right'`, `'top-left'`, `'top-right'`, `'bottom-left'`, and `'bottom-right'`. * The default value os `'center'` */ anchor?: Anchor | undefined; /** 如果未提供options.element,则使用默认标记的颜色。默认值为浅蓝色(#3FB1CE) */ color?: string | undefined; /** 一个布尔值,指示标记是否能够被拖动到地图上的新位置。默认值为false */ draggable?: boolean | undefined; /** * 用户在点击标记时可以移动鼠标指针以使其被视为有效点击的最大像素数 * (与标记拖动相反)。默认值(0)是继承地图的clickTolerance。 */ clickTolerance?: number | null | undefined; /** 标记相对于其“旋转对齐”设置的旋转角度(度)。正值将使标记顺时针旋转. * The default value is 0. */ rotation?: number | undefined; /** map 对齐 Marker 相对于地图的旋转,在地图旋转时保持方位。 *`viewport`使`Marker`的旋转相对于视口对齐,与地图旋转无关。 *`auto`相当于`viewport`。 */ rotationAlignment?: Alignment | undefined; /** map 将 Marker 与地图平面对齐. * `viewport`将`Marker`与视口平面对齐. * `auto`自动匹配`rotationAlignment`的值` * The default value is `auto`. */ pitchAlignment?: Alignment | undefined; /** 如果未提供options.element,则使用默认标记的比例. * 默认比例(1)对应于高度“41px”和宽度“27px”`. */ scale?: number | undefined; } /** * Marker */ export class Marker extends Evented { constructor(options?: vjmap.MarkerOptions); constructor(element?: HTMLElement, options?: vjmap.MarkerOptions); /** * 设置动画 * @param animationType * MAP_ANIMATION_NONE 无动画 * MAP_ANIMATION_BOUNCE 弹跳 * MAP_ANIMATION_DROP 坠落 */ setAnimation(animationType: string): void; /** * 设置颜色 * @param color 颜色 */ setColor(color: string): void; /** * 显示 */ show(): void; /** * 隐藏 */ hide(): void; /** * 获取是否隐藏了,当设置了hide或缩放级别或视图范围外不可见时,返回true,否则返回false */ isHidden(): boolean; /** * 设置光标 * @param cur 光标名称 */ setCursor(cur: string): void; /** * 设置高度 * @param height 高度值 */ setHeight(height: number): Marker; /** * 设置显示的最小缩放级别 * @param zoom 缩放级别 */ setMinZoom(zoom: number): Marker; /** * 设置显示的最大缩放级别 * @param zoom 缩放级别 */ setMaxZoom(zoom: number): Marker; /** * 设置当marker不在当前地图视图范围内时,将自动移除。进入视图范围内时,将自动增加上,当自动移除或增加时,将触发`autoAdd`和`autoRemove`事件 * @param autoRemove true不在当前地图视图范围内时,将自动移除,false不会自动移除 * @param padding 范围向外扩的像素范围,默认500px,向视图范围往外扩些像素,在平移的时候,能看到marker,体验效果好些。 */ setRemoveWhenNoInMapView(autoRemove: boolean, padding?: number): Marker; /** * 设置能缩放的最大级别。如果小于这个级别,div将根据缩小级别自动缩小比例。默认不会自动缩放,当自动移除或增加时,将触发`autoAdd`和`autoRemove`事件 * @param zoom 缩放级别 */ setScaleMaxZoom(zoom: number): Marker; /** * 得到高度值 */ getHeight(): number | undefined; /** * 得到显示的最小缩放级别 */ getMinZoom(): number | undefined; /** * 得到显示的最大缩放级别 */ getMaxZoom(): number | undefined; /** * 得到能缩放的最大级别。如果小于这个级别,div将根据缩小级别自动缩小比例。默认不会自动缩放 */ getScaleMaxZoom(zoom: number): number | undefined; /** * 得到当marker不在当前地图视图范围内时,将自动移除。进入视图范围内时,将自动增加上 */ getRemoveWhenNoInMapView(): boolean; /** * 设置是否显示隐藏 * @param visible 是否显示 * @param isDisplay true的话,表示用style的display去控制隐藏显示,dom还在文档中。false的话,会从文档动态清空增加 */ setVisible(visible?: boolean, isDisplay?: boolean): Marker; addTo(map: Map): this; remove(): this; getLngLat(): LngLat; setLngLat(lngLat: LngLatLike): this; getElement(): HTMLElement; setPopup(popup?: Popup): this; getPopup(): Popup; togglePopup(): this; getOffset(): PointLike; setOffset(offset: PointLike): this; setDraggable(shouldBeDraggable: boolean): this; isDraggable(): boolean; getRotation(): number; setRotation(rotation: number): this; getRotationAlignment(): Alignment; setRotationAlignment(alignment: Alignment): this; getPitchAlignment(): Alignment; setPitchAlignment(alignment: Alignment): this; } ``` --- # Popup > Popup的样式与常规的css样式一致,不过唯一需要注意的是,如何`针对某个特定图层`或者是`特定marker`添加对应的Popup事件。 ## 构造函数参数 |名称| 类型 | 描述| |:---|:---|:---| |options.closeButton|boolean(default true)|该popup的右上角的关闭x图标按钮| |options.closeOnClick|boolean(default true)|为true时,表示点击地图其他位置时,该Popup会消失| |options.anchor|String|'top' , 'bottom' , 'left' , 'right' , 'top-left' , 'top-right' , 'bottom-left' , and 'bottom-right'表示停靠的方位| |options.offset|(number/PointLike/Object)|number表示简单的距离,pointlike表示类似点数组的意思,第三个object是特指的`针对上面提到的anchor`属性的对象,示例如下| ``` javascript var markerHeight = 50, markerRadius = 10, linearOffset = 25; var popupOffsets = { 'top': [0, 0], 'top-left': [0, 0], 'top-right': [0, 0], 'bottom': [0, -markerHeight], 'bottom-left': [linearOffset, (markerHeight - markerRadius + linearOffset) * -1], 'bottom-right': [-linearOffset, (markerHeight - markerRadius + linearOffset) * -1], 'left': [markerRadius, (markerHeight - markerRadius) * -1], 'right': [-markerRadius, (markerHeight - markerRadius) * -1] }; var popup = new vjmap.Popup({offset:popupOffsets}) .setLngLat(e.lngLat) .setHTML("

Hello World!

") .addTo(map); ``` ## 常用方法 |方法名|参数|返回值|说明| |:---|:---|:---|:---| |addTo(map)|map|Popup.this|将这个Popup标注添加到地图上| |isOpen|无|boolean|打开状态返回`true`,关闭状态返回`false`| |remove()|无|Popup.this |将这个Popup标注添加到地图上| |getLngLat()|无|Lnglat|返回该标注的经纬度对象| |setLngLat(lnglat)|Lnglat|Popup.this|设置该标注的经纬度信息| |setText(text)|String|Popup.this|设置该Popup的文字信息| |setHTML(html)|String|Popup.this|将一些简单的HTML描述传入| |setDOMContent(htmlNode)|[Node](https://developer.mozilla.org/docs/Web/API/Node/nextSibling)|Popup.this|`var div = window.document.createElement('div');div.innerHTML = 'Hello, world!';`| ## 基本用法 ```js // 参数说明: // (1)closeButton,true表示会展示一个关闭按钮; // (2)closeOnClick,设置为true表示当地图被点击时该信息窗体会被关闭; // (3)offset,参数为点位置相对于其左上角偏移像素大小; // (4)anchor,停靠点,值域为[top,bottom,left,right,top-left,top-right,bottom-left,bottom-right],如果不设置该参数,则会根据map container动态调整。 // (5)autoPan,设置为true时,当地图拖动到看不到popup的时候,自动将地图平移到可以看到popup,此参数只对固定popup有效; const popup = new vjmap.Popup({ closeOnClick: false, closeButton: true, anchor: "bottom" }); popup.setHTML('Hello, VJMap!') .setLngLat(map.getCenter()) .addTo(map); // 过段时间后删除 // setTimeout(() => popup.remove(), 30000); // 点击地图,弹出popup const popup2 = new vjmap.Popup(); popup2.setHTML("唯杰地图"); map.on("click", (e) => popup2.setLngLat(e.lngLat).addTo(map)); ```
// --信息窗口新增与删除--
// 地图服务对象
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: env.exampleMapId, // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    message.error(res.error)
}
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(res.bounds);
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), //svc.rasterBlankStyle(0, 24), // 样式,这里是栅格样式
    center: prj.toLngLat(prj.getMapExtent().center()), // 设置地图中心点
    zoom: 2, // 设置地图缩放级别
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
// 根据地图本身范围缩放地图至全图显示
map.fitMapBounds();
await map.onLoad();
// 参数说明:
// (1)closeButton,true表示会展示一个关闭按钮;
// (2)closeOnClick,设置为true表示当地图被点击时该信息窗体会被关闭;
// (3)offset,参数为点位置相对于其左上角偏移像素大小;
// (4)anchor,停靠点,值域为[top,bottom,left,right,top-left,top-right,bottom-left,bottom-right],如果不设置该参数,则会根据map container动态调整。
// (5)autoPan,设置为true时,当地图拖动到看不到popup的时候,自动将地图平移到可以看到popup,此参数只对固定popup有效;
const popup = new vjmap.Popup({ closeOnClick: false, closeButton: true, anchor: "bottom" });
popup.setHTML('Hello, VJMap!')
    .setLngLat(map.getCenter())
    .addTo(map);
// 过段时间后删除
setTimeout(() => popup.remove(), 30000);
// 点击地图,弹出popup
const popup2 = new vjmap.Popup();
popup2.setHTML("唯杰地图");
map.on("click", (e) => popup2.setLngLat(e.lngLat).addTo(map));


## Popup相关示例 ![image-20240928093430395](https://vjmap.com/blogimages/202409280934552.png) [信息窗口新增与删除](https://vjmap.com/demo/#/demo/map/popup/01popupadd) [跟随鼠标的信息窗口](https://vjmap.com/demo/#/demo/map/popup/02popupTrackPointer) [点标记绑定信息窗口](https://vjmap.com/demo/#/demo/map/popup/03markerPopup) [有高度的信息窗口](https://vjmap.com/demo/#/demo/map/popup/04popupHeight) [Vue2创建信息窗口](https://vjmap.com/demo/#/demo/map/popup/05popupMvvm) ## 类型定义 ```ts /** * Popup */ export class Popup extends Evented { constructor(options?: { closeButton?: boolean | undefined; closeOnClick?: boolean | undefined; /** * @param {boolean} [options.closeOnMove=false] 如果为“true”,则当地图移动时,弹出窗口将关闭。 */ closeOnMove?: boolean | undefined; /** * @param {boolean} [options.focusAfterOpen=true] 如果为true,弹出窗口将尝试聚焦弹出窗口内的第一个可聚焦元素。 */ focusAfterOpen?: boolean | null | undefined; anchor?: Anchor | undefined; offset?: Offset | null | undefined; className?: string | undefined; maxWidth?: string | undefined; }); /** * 设置高度 * @param height 高度值 */ setHeight(height: number): Popup; /** * 得到高度值 */ getHeight(): number | undefined; addTo(map: vjmap.Map): this; isOpen(): boolean; remove(): this; getLngLat(): vjmap.LngLat; /** * 设置弹出窗口锚点的位置,并将弹出窗口移动到该锚点。替换trackPointer()行为。 * * @param lnglat 将位置设置为弹出窗口的锚点。 */ setLngLat(lnglat: LngLatLike): this; /** * 在带有指针设备的屏幕上(将在触摸屏上隐藏),将弹出锚点跟踪到光标位置。替换setLngLat行为。 * 对于大多数情况,此处还应将“closeOnClick”和“closeButton”设置为“false”。 */ trackPointer(): this; /** Returns the `Popup`'s HTML element. */ getElement(): HTMLElement; setText(text: string): this; setHTML(html: string): this; setDOMContent(htmlNode: Node): this; getMaxWidth(): string; setMaxWidth(maxWidth: string): this; /** * 将CSS类添加到弹出容器元素中。 * * @param {string} className 要添加到弹出容器中的具有CSS类名的非空字符串 * * @example * let popup = new vjmap.Popup() * popup.addClassName('some-class') */ addClassName(className: string): void; /** * 从弹出容器元素中删除CSS类。 * * @param {string} className 从弹出容器中删除具有CSS类名的非空字符串 * * @example * let popup = new vjmap.Popup() * popup.removeClassName('some-class') */ removeClassName(className: string): void; /** * 设置偏移量 * * @param offset Sets the popup's offset. * @returns {Popup} `this` */ setOffset(offset?: Offset | null): this; /** * 在弹出容器上添加或删除给定的CSS类,具体取决于容器当前是否具有该类。 * * @param {string} className Non-empty string with CSS class name to add/remove * * @returns {boolean} if the class was removed return false, if class was added, then return true * * @example * let popup = new vjmap.Popup() * popup.toggleClassName('toggleClass') */ toggleClassName(className: string): void; } ``` --- # 右键菜单 `ContextMenu` 上下文菜单用于右键弹出菜单界面显示。 ## 常用方法 |方法|描述| |:---|:---| |add|增加菜单项| |show|显示菜单 | |hide|隐藏菜单| |toggle|切换菜单状态| ## 菜单项类型 |类型|描述| |:---|:---| |seperator|分隔线| |custom|自定义菜单项| |multi|多个按钮| |submenu|子菜单项| |hovermenu|悬浮菜单项| |normal|缺省正常菜单项| ## 示例 ### 地图上下文菜单 [地图上下文菜单](https://vjmap.com/demo/#/demo/map/contextmenu/01contextmenuMap) ```js // 选中的图标,也可以直接指定src路径 const checkIcon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAANpJREFUOE9jZKAQMFKon2FgDKjZHVHf4rqiEeR6kl1QuzOyn+E/4/lmj2WLSDagekdkASMjg1aL+/I0WNgR7YLaHRG2/xgZYlrdV6QjBzzRBtTsijzzl4nVvd1l0VucBtTsjlz5/z/jt1a3ZYnIimp2RZ7//+9fQ6vHyo3o0Q53Qc32CAcGpv88DIxMxQyMDB9aXJcHghTX7IxcyMjAeLTZfdksbGkGqxdqdkUuZ2BgkPjPyDCFgeG/W6srqr+JCoPqXZG9jAwM9iyszy0aHA/8wZViiQ7EYWwAAPShQBF/1IlVAAAAAElFTkSuQmCC"; map.setMenu(event => { return new vjmap.ContextMenu({ event: event.originalEvent, theme: "dark", //light width: "250px", items: [ {type: 'custom', markup: `菜单右键功能演示`}, {type: 'multi', items: [ {label: '全图', onClick: () => { map.fitMapBounds(); }}, {label: '不旋转', onClick: () => { map.setBearing(0); }}, {label: '不倾斜', onClick: () => { map.setPitch(0); }}, ]}, {label: '获取此位置的坐标', onClick: () => { let point = map.fromLngLat(event.lngLat); map.logInfo(`当前坐标为 x: ${point.x}, y: ${point.y}`); }}, {type: 'seperator'}, {type: 'submenu', label: '地图缩放', items: [ {label: '放大一级', onClick: () => { map.setZoom(map.getZoom() + 1)}}, {label: '缩小一级', onClick: () => { map.setZoom(map.getZoom() - 1)}, enabled: map.getZoom() - 1 > 0}, {label: '缩小至最小级', onClick: () => { map.setZoom(0)}}, {label: '飞行至此位置', onClick: () => { map.flyTo({ center: event.lngLat, pitch: 60, zoom: 5 }) }} ]}, {type: 'hovermenu', label: '地图设置', items: [ { label: '双击鼠标进行缩放', icon: map.doubleClickZoom.isEnabled() ? checkIcon : "", onClick: () => { if (map.doubleClickZoom.isEnabled()) { map.doubleClickZoom.disable() } else { map.doubleClickZoom.enable() } } }, { label: '允许地图旋转', icon: map.dragRotate.isEnabled() ? checkIcon : "", onClick: () => { if (map.dragRotate.isEnabled()) { map.dragRotate.disable(); } else { map.dragRotate.enable(); } } }, { label: '允许地图倾斜', icon: map.getMaxPitch() != 0 ? checkIcon : "", onClick: () => { if (map.getMaxPitch() == 0) { map.setMaxPitch(85) } else { map.setMaxPitch(0) } } } ]} ] }); }) ```
// --地图上下文菜单--
// 地图服务对象
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: env.exampleMapId, // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    message.error(res.error)
}
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(res.bounds);
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(prj.getMapExtent().center()), // 设置地图中心点
    zoom: 2, // 设置地图缩放级别
    renderWorldCopies: false
});
// 关联服务对象和投影对象
map.attach(svc, prj);
let mapBounds = map.getGeoBounds(); // 得到地图地理范围
// 选中的图标,也可以直接指定src路径
const checkIcon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAANpJREFUOE9jZKAQMFKon2FgDKjZHVHf4rqiEeR6kl1QuzOyn+E/4/lmj2WLSDagekdkASMjg1aL+/I0WNgR7YLaHRG2/xgZYlrdV6QjBzzRBtTsijzzl4nVvd1l0VucBtTsjlz5/z/jt1a3ZYnIimp2RZ7//+9fQ6vHyo3o0Q53Qc32CAcGpv88DIxMxQyMDB9aXJcHghTX7IxcyMjAeLTZfdksbGkGqxdqdkUuZ2BgkPjPyDCFgeG/W6srqr+JCoPqXZG9jAwM9iyszy0aHA/8wZViiQ7EYWwAAPShQBF/1IlVAAAAAElFTkSuQmCC";
map.setMenu(event => {
    return new vjmap.ContextMenu({
        event: event.originalEvent,
        theme: "dark", //light
        width: "250px",
        items: [
            {type: 'custom', markup: `<span style="color: #ffff00; padding-left: 30px">菜单右键功能演示</span>`},
            {type: 'multi', items: [
                    {label: '全图', onClick: () => { map.fitMapBounds(); }},
                    {label: '不旋转', onClick: () => { map.setBearing(0); }},
                    {label: '不倾斜', onClick: () => { map.setPitch(0); }},
                ]},
            {label: '获取此位置的坐标', onClick: () => {
                    let point = map.fromLngLat(event.lngLat);
                    map.logInfo(`当前坐标为 x: ${point.x}, y: ${point.y}`);
                }},
            {type: 'seperator'},
            {type: 'submenu', label: '地图缩放', items: [
                    {label: '放大一级', onClick: () => { map.setZoom(map.getZoom() + 1)}},
                    {label: '缩小一级', onClick: () => { map.setZoom(map.getZoom() - 1)}, enabled: map.getZoom() - 1 > 0},
                    {label: '缩小至最小级', onClick: () => { map.setZoom(0)}},
                    {label: '飞行至此位置', onClick: () => {
                            map.flyTo({
                                center: event.lngLat,
                                pitch: 60,
                                zoom: 5
                            })
                        }}
                ]},
            {type: 'hovermenu', label: '地图设置', items: [
                    {
                        label: '双击鼠标进行缩放',
                        icon: map.doubleClickZoom.isEnabled() ? checkIcon : "",
                        onClick: () => {
                            if (map.doubleClickZoom.isEnabled()) {
                                map.doubleClickZoom.disable()
                            } else {
                                map.doubleClickZoom.enable()
                            }
                        }
                    },
                    {
                        label: '允许地图旋转',
                        icon: map.dragRotate.isEnabled() ? checkIcon : "",
                        onClick: () => {
                            if (map.dragRotate.isEnabled()) {
                                map.dragRotate.disable();
                            } else {
                                map.dragRotate.enable();
                            }
                        }
                    },
                    {
                        label: '允许地图倾斜',
                        icon: map.getMaxPitch() != 0 ? checkIcon : "",
                        onClick: () => {
                            if (map.getMaxPitch() == 0) {
                                map.setMaxPitch(85)
                            } else {
                                map.setMaxPitch(0)
                            }
                        }
                    }
                ]}
        ]
    });
})
map.logInfo('请点击右键弹出菜单');


### 覆盖物图层右键菜单 [覆盖物图层右键菜单](https://vjmap.com/demo/#/demo/map/contextmenu/02contextmenuOverlay) ```js // 设置点击一个覆盖物时弹出菜单,key值可以自定义设置。如果函数返回空时,将触发上面的地图右键菜单。如果函数不返回空,则会弹出此函数的菜单 map.setMenu(event => { let features = map.queryRenderedFeatures(event.point, { layers: [polygon.layerId] }); if (features.length == 0) { // 如果点击位置没有上面多边形图层的实体,则将回空,这样将会弹出上面的默认地图菜单 return null; } // 如果点击位置有上面多边形图层的实体,则弹出此图层的自定义右键菜单 return new vjmap.ContextMenu({ event: event.originalEvent, theme: "dark", //light width: "250px", items: [ {type: 'custom', markup: `我是多边形图层的右键菜单`}, { label: '查看选择的多边形信息', onClick: () => { map.logInfo(`当前多边形ID: ${features[0].properties.id}, 当前多边形颜色: ${features[0].properties.color}`); } }, { label: '删除', onClick: () => { let data = polygon.getData(); data.features = data.features.filter(f => f.id != features[0].properties.id); polygon.setData(data); } } ] }); }, "overlay") ```
// --覆盖物图层右键菜单--
// 地图服务对象
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: env.exampleMapId, // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    message.error(res.error)
}
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(res.bounds);
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(prj.getMapExtent().center()), // 设置地图中心点
    zoom: 2, // 设置地图缩放级别
    renderWorldCopies: false
});
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();
// 下面加很多多边形做测试,在多边形上右键时将弹出多边形的右键菜单,否则弹出地图的右键菜单
let mapBounds = map.getGeoBounds(0.6);
let len = mapBounds.width() / 50;
let polygons = []
for(let i = 0; i < 200; i++) {
    const p1 = mapBounds.randomPoint();
    const p2 = vjmap.geoPoint([p1.x, p1.y + len]);
    const p3 = vjmap.geoPoint([p1.x + len, p1.y]);
    polygons.push({
        points: map.toLngLat([p1, p2, p3]),
        properties: {
            name: "polygon" + (i + 1),
            color: vjmap.randomColor()
        }
    })
}
let polygon = new vjmap.Polygon({
    data: polygons,
    // 如果是hover状态时,用红色,非hover状态时,取属性中的'color'做为颜色值
    fillColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', ['get', 'color']],
    fillOpacity: 0.8,
    fillOutlineColor: "#f00",
    isHoverPointer: true,
    isHoverFeatureState: true
});
polygon.addTo(map);
polygon.clickLayer(e => map.logInfo(`您点击了第 ${e.features[0].id} 个,名称为 ${e.features[0].properties.name},颜色为 ${e.features[0].properties.color} 的多边形`))
// 默认情况下显示的是地图菜单,因为这里的key是空,如果多次设置setMenu时,根据key值排序倒序,空值会在最后面
map.setMenu(event => {
    return new vjmap.ContextMenu({
        event: event.originalEvent,
        theme: "dark", //light
        width: "250px",
        items: [
            {type: 'custom', markup: `<span style="color: #ffff00; padding-left: 30px">我是地图的右键菜单</span>`},
            {
                label: '全图范围显示',
                onClick: () => {
                    map.setBearing(0);
                    map.setPitch(0);
                    map.fitMapBounds();
                }
            },
            {
                label: '获取此位置的坐标',
                onClick: () => {
                    let point = map.fromLngLat(event.lngLat);
                    map.logInfo(`当前坐标为 x: ${point.x}, y: ${point.y}`);
                }
            }
        ]
    });
})
// 设置点击一个覆盖物时弹出菜单,key值可以自定义设置。如果函数返回空时,将触发上面的地图右键菜单。如果函数不返回空,则会弹出此函数的菜单
map.setMenu(event => {
    let features = map.queryRenderedFeatures(event.point, {
        layers: [polygon.layerId]
    });
    if (features.length == 0) {
        // 如果点击位置没有上面多边形图层的实体,则将回空,这样将会弹出上面的默认地图菜单
        return null;
    }
    // 如果点击位置有上面多边形图层的实体,则弹出此图层的自定义右键菜单
    return new vjmap.ContextMenu({
        event: event.originalEvent,
        theme: "dark", //light
        width: "250px",
        items: [
            {type: 'custom', markup: `<span style="color: #ffff00; padding-left: 30px">我是多边形图层的右键菜单</span>`},
            {
                label: '查看选择的多边形信息',
                onClick: () => {
                    map.logInfo(`当前多边形ID: ${features[0].properties.id}, 当前多边形颜色: ${features[0].properties.color}`);
                }
            },
            {
                label: '删除',
                onClick: () => {
                    let data = polygon.getData();
                    data.features = data.features.filter(f => f.id != features[0].properties.id);
                    polygon.setData(data);
                }
            }
        ]
    });
}, "overlay")
// 绘图控件右键菜单
const draw = new vjmap.Draw.Tool();
map.addControl(draw, 'top-right');
map.on("draw.simpleselect.contextmenu", function (e) {
    // 如果不是此draw对象的事件,则不用管
    if (e.styleId != draw.options.styleId) return;
    let feature = e.event.featureTarget
    if (!feature) return
    //if (draw.getMode() != "simple_select") return
    let selectFeatureId = feature.properties.id
    // 需要选中的才能右键
    let selectIds = draw.getSelectedIds() || []
    if (selectIds.indexOf(selectFeatureId)== -1) return
    let event = e.event.originalEvent
    // 阻止地图默认右键菜单触发
    event.preventDefault();
    event.stopPropagation();
    return new vjmap.ContextMenu({
        event: event,
        theme: "dark", //light
        width: "250px",
        items: [
            {
                label: "删除",
                onClick: () => {
                    draw.delete(selectFeatureId)
                }
            }
        ]
    });
});


### 标记Marker右键菜单 [标记Marker右键菜单](https://vjmap.com/demo/#/demo/map/contextmenu/03contextmenuMarker) ```js // marker与多边形等覆盖物图层不同的时,marker是div,而覆盖物图层是canvas渲染与map属于同一个div // 所以marker的右键,得由自己的div的上下文菜单来触发 marker.getElement().addEventListener("mousedown", event => { if (event.button != 2) return;//不是右键 // 阻止地图默认右键菜单触发 event.preventDefault(); event.stopPropagation(); return new vjmap.ContextMenu({ event: event, theme: "dark", //light width: "250px", items: [ {type: 'custom', markup: `我是标注Marker的右键菜单`}, { label: '获取此Marker的信息', onClick: () => { map.logInfo(`当前MarkerId为 ${marker.id}`); } } ] }); }) ```
// --标记Marker右键菜单--
// 地图服务对象
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: env.exampleMapId, // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    message.error(res.error)
}
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(res.bounds);
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(prj.getMapExtent().center()), // 设置地图中心点
    zoom: 2, // 设置地图缩放级别
    renderWorldCopies: false
});
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();
map.setMenu(event => {
    return new vjmap.ContextMenu({
        event: event.originalEvent,
        theme: "dark", //light
        width: "250px",
        items: [
            {type: 'custom', markup: `<span style="color: #ffff00; padding-left: 30px">我是地图的右键菜单</span>`},
            {
                label: '全图范围显示',
                onClick: () => {
                    map.setBearing(0);
                    map.setPitch(0);
                    map.fitMapBounds();
                }
            },
            {
                label: '获取此位置的坐标',
                onClick: () => {
                    let point = map.fromLngLat(event.lngLat);
                    app.logInfo(`当前坐标为 x: ${point.x}, y: ${point.y}`);
                }
            }
        ]
    });
})
let mapBounds = map.getGeoBounds(); // 得到地图地理范围
for(let i = 0; i < 50; i++) {
    let point = mapBounds.randomPoint(); // 在地理范围里随机生成一个点
    // marker
    let marker = new vjmap.Marker({
        color: vjmap.randomColor()
    });
    marker.setLngLat(map.toLngLat(point))
    marker.addTo(map);
    marker.id = i + 1;
    // marker与多边形等覆盖物图层不同的时,marker是div,而覆盖物图层是canvas渲染与map属于同一个div
    // 所以marker的右键,得由自己的div的上下文菜单来触发
    marker.getElement().addEventListener("mousedown", event => {
        if (event.button != 2) return;//不是右键
        // 阻止地图默认右键菜单触发
        event.preventDefault();
        event.stopPropagation();
        return new vjmap.ContextMenu({
            event: event,
            theme: "dark", //light
            width: "250px",
            items: [
                {type: 'custom', markup: `<span style="color: #ffff00; padding-left: 30px">我是标注Marker的右键菜单</span>`},
                {
                    label: '获取此Marker的信息',
                    onClick: () => {
                        map.logInfo(`当前MarkerId为 ${marker.id}`);
                    }
                }
            ]
        });
    })
}


# 类型定义文件 ```ts /** * 上下文菜单. * **/ export class ContextMenu { private options; private menuControl; private position; /** * Creates a new ContextMenu menu * @param {object} opts options which build the menu e.g. position and items * @param {number} opts.width sets the width of the menu including children * @param {object} opts.event 事件对象 * @param {theme} opts.theme 自定义的样式主题,支持dark和light,默认dark * @param {boolean} opts.isSticky sets how the menu apears, follow the mouse or sticky * @param {Array} opts.items sets the default items in the menu */ constructor(opts: ContextMenuOptions); /** * Adds item to this ContextMenu menu instance * @param {ContextMenuItem} item item to add to the ContextMenu menu */ add(item: any): void; /** * Makes this ContextMenu menu visible */ show(): void; /** * Hides this ContextMenu menu */ hide(): void; /** * Toggle visibility of menu */ toggle(): void; } /** * 上下文菜单选项. * **/ export interface ContextMenuOptions { /** 事件对象. */ event: Event; /** 菜单宽度(包括子菜单),像素,默认150px. */ width?: string; /** 菜单主题色.(dark和light,默认dark)*/ theme?: string; /** 菜单外面容器的内部宽,用于菜单超时范围时自动调位置,默认是window大小宽 可通过 map.getContainer().getBoundingClientRect().width来设置 */ innerWidth?: number; /** 菜单外面容器的内部宽,用于菜单超时范围时自动调位置,默认是window大小高 可通过 map.getContainer().getBoundingClientRect().height来设置 */ innerHeight?: number; /** 子项 */ items: ContextMenuSubItemOptions[]; } /** * 上下文菜单子项选项. * **/ export interface ContextMenuSubItemOptions { /** 子菜单类型. */ type?: "custom" | "multi" | "Button" | "seperator" | "submenu" | "hovermenu" | "normal"; /** 类型为'custom'时的自定义html内容 */ markup?: string; /** 类型为'multi'时子菜单项*/ items?: ContextMenuSubItemOptions[]; /** 点击事件*/ onClick?: Function; /** 菜单名称*/ label?: string; /** 快捷菜单*/ shortcut?: string; /** 是否能用*/ enabled?: boolean; /** css图标*/ cssIcon?: string; /** 图标*/ icon?: string; } ``` --- # div覆盖物 `DivOverlay` 在一个地理范围内创建一个随缩放而缩放的div的覆盖物 如果是svg,则需设置为 viewBox="0 0 width height" preserveAspectRatio="xMinYMin meet", updateDivSize选项设置为true ## 常用方法 |方法|描述| |:---|:---| |setVisible|设置是否显示隐藏| |addTo|增加至地图 | |remove|移除 | |updateBounds|更新范围| |updateSize|更新大小| ## 菜单项类型 |类型|描述| |:---|:---| |bounds|范围,四个点坐标| |element|html元素| |width|元素宽| |height|元素高| |minZoom|显示最大级别| |maxZoom|显示最小级别| |maxZoom|显示最小级别| |maxPitch|显示最大倾斜角| |updateDivSize|自动更新div大小,(如果需要svg放大,需要设置为true) | |maxDivSize|放大div时,最大的div大小,超过了就像素放大了| ## 用法 ```js const imageWidth = 1280; // 图像宽 const imageHeight = 905; // 图像高 const image = document.createElement( "img" ); image.style.position = "absolute"; // 有多个divoverlay时,一定要加定位,否则会导致其他绘制不对 image.style.left = '0px'; image.style.top = '0px'; image.style.width = imageWidth + "px"; image.style.height = imageHeight + "px"; image.style.opacity = '0.8'; image.style.zIndex = '-1'; image.src = "https://vjmap.com/static/assets/images/park.jpg" image.style.pointerEvents = "none" // 增加一个DIV的Image覆盖物 const divOverlay = new vjmap.DivOverlay({ bounds: points, // 四个点的位置 element: image, // DIV 元素 width: imageWidth,// DIV 宽 height: imageHeight,// DIV 高 minZoom: 5, // 能显示的最小级别 (可以不填) maxZoom: 15 // 能显示的最大级别 (可以不填) }) divOverlay.addTo(map); ```
// --div覆盖物绘制图片--
// 地图服务对象
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: env.exampleMapId, // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    message.error(res.error)
}
// 获取地图范围
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapExtent);
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapExtent.center()), // 设置地图中心点
    zoom: 9, // 设置地图缩放级别,
    pitch: 30, // 倾斜角度
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
// 根据地图本身范围缩放地图至全图显示
//map.fitMapBounds();
await map.onLoad();
const mapBounds = map.getGeoBounds(0.001);
const colors = ["#FF0000", "#00FF00", "#0000FF", "#FFFF00"];
const points = [[mapBounds.min.x, mapBounds.min.y], [mapBounds.min.x, mapBounds.max.y], [mapBounds.max.x, mapBounds.max.y], [mapBounds.max.x, mapBounds.min.y]]
const markers = []
// 增加四个覆盖物用来调节位置
for(let c = 0; c <  colors.length; c++) {
    const marker = new vjmap.Marker({
        color: colors[c],
        draggable: true
    });
    marker.setLngLat(map.toLngLat(points[c])).addTo(map);
    marker.on('drag', e => {
        updateBounds();
    });
    markers.push(marker);
}
const imageWidth = 1280; // 图像宽
const imageHeight = 905; // 图像高
const image = document.createElement( "img" );
image.style.position = "absolute"; // 有多个divoverlay时,一定要加定位,否则会导致其他绘制不对
image.style.left = '0px';
image.style.top = '0px';
image.style.width = imageWidth + "px";
image.style.height = imageHeight + "px";
image.style.opacity = '0.8';
image.style.zIndex = '-1';
image.src = "https://vjmap.com/static/assets/images/park.jpg"
image.style.pointerEvents = "none"
// 增加一个DIV的Image覆盖物
const divOverlay = new vjmap.DivOverlay({
    bounds: points, // 四个点的位置
    element: image, // DIV 元素
    width: imageWidth,// DIV 宽
    height: imageHeight,// DIV 高
    minZoom: 5, // 能显示的最小级别 (可以不填)
    maxZoom: 15 // 能显示的最大级别 (可以不填)
})
divOverlay.addTo(map);
const updateBounds = () => {
    // 更新位置
    divOverlay.updateBounds([map.fromLngLat(markers[0].getLngLat()), map.fromLngLat(markers[1].getLngLat()), map.fromLngLat(markers[2].getLngLat()), map.fromLngLat(markers[3].getLngLat())])
}
map.logInfo("拖动四个点来调节图片位置");


## 示例 ![image-20240928102640288](https://vjmap.com/blogimages/202409281026370.png) [div覆盖物绘制图片](https://vjmap.com/demo/#/demo/map/overlay/div/divOverlay) [div覆盖物绘制渐变多边形](https://vjmap.com/demo/#/demo/map/overlay/div/divOverlayGradientPolygon) [一个有高度的div覆盖物](https://vjmap.com/demo/#/demo/map/overlay/div/divOverlayHeight) [div覆盖物绘制雷达效果](https://vjmap.com/demo/#/demo/map/overlay/div/divOverlayRadar) [div覆盖物绘制SVG图形](https://vjmap.com/demo/#/demo/map/overlay/div/divOverlaySvg) [地图加载组态SVG图形](https://vjmap.com/demo/#/demo/map/overlay/div/divOverlaySvgScada) ## 类型定义文件 ```ts /** * 在一个地理范围内创建一个随缩放而缩放的div的覆盖物 * 注:如果是svg,则需设置为 viewBox="0 0 width height" preserveAspectRatio="xMinYMin meet", updateDivSize选项设置为true **/ export class DivOverlay { options: DivOverlayOptions; _map?: Map; isShow: boolean; minZoom: number; maxZoom: number; maxPitch: number; isRemoved: boolean; parentContainer?: HTMLElement; constructor(options: DivOverlayOptions); addTo(map: Map, insertId?: string | HTMLElement): void; private _isShow; private _add; private _remove; /** * 设置是否显示隐藏 * @param visible 是否显示 * @param isDisplay true的话,表示用style的display去控制隐藏显示,dom还在文档中。false的话,会从文档动态清空增加 */ setVisible(visible?: boolean, isDisplay?: boolean): void; remove(): void; updateBounds(bounds: [GeoPointLike, GeoPointLike, GeoPointLike, GeoPointLike] | GeoBounds): void; updateSize(width: number, height: number): void; private _updateZoom; private _updateDivSize; private _adjustCoord; private _update; } export interface DivOverlayOptions { /** 范围,四个点坐标 */ bounds: [GeoPointLike, GeoPointLike, GeoPointLike, GeoPointLike] | GeoBounds; /** html元素 */ element: HTMLElement; /** 元素宽 */ width: number; /** 元素高 */ height: number; /** 显示最大级别 */ minZoom?: number; /** 显示最小级别 */ maxZoom?: number; /** 显示最大倾斜角 */ maxPitch?: number; /** 自动更新div大小,(如果需要svg放大,需要设置为true) */ updateDivSize?: boolean; /** 放大div时,最大的div大小,超过了就像素放大了 */ maxDivSize?: number; } ``` --- # svg覆盖物 `SvgOverlay` 在一个地理范围内创建一个随缩放而缩放的svg的覆盖物 svg覆盖物性能低,不建议大量使用,同时在级别特别大时会导致失真,在倾斜角很大时会导致不可见 ## 类型定义 ```ts // 在一个地理范围内创建一个随缩放而缩放的svg的覆盖物 export class SvgOverlay { constructor(options?: { /** 显示最大级别 */ minZoom?: number; /** 显示最小级别 */ maxZoom?: number; /** 显示最大倾斜角 */ maxPitch?: number; /** 自动更新div大小,(如果需要svg放大,需要设置为true) */ updateDivSize?: boolean; /** 放大div时,最大的div大小,超过了就像素放大了 */ maxDivSize?: number; /** 当移动结束时不自动更新范围 */ noUpdateBoundsWhenMoveend?: boolean; /** 类名 */ divClassName?: string; /** svg初始化时最大像素宽,默认1000 */ svgMaxWidth?: number; /** svg初始化时最大像素高,默认1000 */ svgMaxHeight?: number; /** svg初始化时Offset像素距离,默认100 */ svgOffset?: number; }); addTo(map: Map, insertId?: string | HTMLElement): void; remove(): void; /** * 增加svg元素 * @param element 要增加的元素 * @param noUpdate 不立即更新 */ addElement(element: SvgElementOptions | string, noUpdate?: boolean): void; /** * 增加svg元素 * @param elements 多个元素内容 * @param noUpdate 不立即更新 */ addElements(elements: SvgElementOptions[], noUpdate?: boolean): void; /** * 获取svg获取 */ getSvgContainer(): SVGSVGElement; /** * 获取所有的元素 */ getElements(): SvgElementOptions[]; /** * 移动一个元素 */ removeElements(id: string[] | string): SvgElementOptions[]; /** * 更新一个元素 */ updateElements(elements: SvgElementOptions[] | SvgElementOptions): SvgElementOptions[]; /** * 圆心半径属性字符串 */ static attr_cx_cy_r(cx: number, cy: number, r: number): string; /** * 椭圆心半径属性字符串 */ static attr_cx_cy_rx_ry(cx: number, cy: number, rx: number, ry: number): string; /** * 直线坐标属性字符串 */ static attr_x1_y1_x2_y2(x1: number, y1: number, x2: number, y2: number): string; /** * 坐标宽高属性字符串 */ static attr_x_y_w_h(x: number, y: number, w: number, h: number): string; /** * 坐标属性字符串 */ static attr_x_y(x: number, y: number): string; /** * 字体属性字符串 */ static attr_fontsize(fontsize: number): string; /** * 和长度有关属性字符串 */ static attr_length(len: number): string; /** * 和点坐标有关属性字符串 */ static attr_point(point: GeoPoint, joinComma?: boolean): string; /** * 坐标序列属性字符串 */ static attr_points(points: GeoPoint[]): string; /** * 路径序列属性字符串 */ static attr_path(points: GeoPoint[]): string; } ``` ## 用法 ```js let mapBounds = map.getGeoBounds(); let len = mapBounds.width() / 5; let svgOverlay = new vjmap.SvgOverlay({ maxZoom: 5, // 第5级时全部不显示了 maxPitch: 60 // 倾角超过60度就不显示了 }); svgOverlay.addTo(map); // 增加一个渐变色 svgOverlay.addElement(` `) const getStyle = (style = {}) => { return Object.keys(style).map(key => `${key}:${style[key]}`).join(";") } const svgCircleHtml = (id, point, r, style) => { return ` ` } const addCircle = ()=> { let id = "circle" + vjmap.RandomID(); return svgOverlay.addElement({ html: svgCircleHtml(id, mapBounds.randomPoint(), len / 4, {fill: "url(#grad1)", stroke: vjmap.randomColor(), "stroke-width": 3, cursor: "pointer"}), event: svgParentElement => { // 事件回调 let ele = svgParentElement.getElementById(id); if (!ele) return; ele.addEventListener("mouseover", e => ele.style.opacity = 0.6); ele.addEventListener("mouseout", e => ele.style.opacity = 1.0); ele.addEventListener("click", e => message.info("点击了圆" + id)); } }) } for(let i = 0; i < 10; i++) { addCircle(); } ```
// 新建地图服务对象,传入服务地址和token
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: env.exampleMapId, // 地图ID,(请确保此ID已存在,可上传新图形新建ID)
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    message.error(res.error)
}
// 获取地图的范围
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
// 建立坐标系
let prj = new vjmap.GeoProjection(mapExtent);
// 新建地图对象
let map = new vjmap.Map({
    container: 'map', // container ID
    style: svc.rasterStyle(), // 栅格瓦片样式
    center: prj.toLngLat(mapExtent.center()), // 中心点
    zoom: 2,
    renderWorldCopies: false
});
// 地图关联服务对象和坐标系
map.attach(svc, prj);
await map.onLoad();
let mapBounds = map.getGeoBounds();
let len = mapBounds.width() / 5;
let svgOverlay = new vjmap.SvgOverlay({
    maxZoom: 5, // 第5级时全部不显示了
    maxPitch: 60 // 倾角超过60度就不显示了
});
svgOverlay.addTo(map);
// 增加一个渐变色
svgOverlay.addElement(`
          <defs>
            <radialGradient id="grad1" cx="50%" cy="50%" r="50%" fx="50%" fy="50%">
              <stop offset="0%" style="stop-color:${vjmap.randomColor()};stop-opacity:0.6" />
              <stop offset="100%" style="stop-color:${vjmap.randomColor()};stop-opacity:1" />
            </radialGradient>
          </defs>
        `)
const getStyle = (style = {}) => {
    return Object.keys(style).map(key => `${key}:${style[key]}`).join(";")
}
const svgCircleHtml = (id, point, r, style) => {
    return ` <circle id="${id}" ${vjmap.SvgOverlay.attr_cx_cy_r(point.x, point.y, r)} style="${getStyle(style)}"/>`
}
const addCircle = ()=> {
    let id = "circle" + vjmap.RandomID();
    return svgOverlay.addElement({
        html: svgCircleHtml(id, mapBounds.randomPoint(), len / 4, {fill: "url(#grad1)", stroke: vjmap.randomColor(), "stroke-width": 3, cursor: "pointer"}),
        event: svgParentElement => {
            // 事件回调
            let ele = svgParentElement.getElementById(id);
            if (!ele) return;
            ele.addEventListener("mouseover", e => ele.style.opacity = 0.4);
            ele.addEventListener("mouseout", e => ele.style.opacity = 0.6);
            ele.addEventListener("click", e => map.logInfo("点击了圆" + id));
        }
    })
}
for(let i = 0; i < 10; i++) {
    addCircle();
}


## 示例 ![image-20240928104939832](https://vjmap.com/blogimages/202409281049871.png) [SVG覆盖物](https://vjmap.com/demo/#/demo/map/overlay/svg/svgOverlay) [SVG事件](https://vjmap.com/demo/#/demo/map/overlay/svg/svgOverlayEvent) [SVG动画](https://vjmap.com/demo/#/demo/map/overlay/svg/svgOverlayFlow) --- # 文本 `Text` 用`div`的方式创建一个文本组件, 是里面引用的是`Marker`对象。 ## 类型定义 ```ts /** * 创建文本组件. * * @param {Object} [options] * @param {object} [options.text] 文字内容 * @param {object} [options.style] 文字样式 * @param {HTMLElement} [options.element] 用作文本的 DOM 元素。 * @param {string} [options.anchor='center'] 一个字符串,指示应该放置在最靠近坐标的标记部分。选项有“center”、“top”、“bottom”、“left”、“right”、“top-left”、“top-right”、“top-right” “左下”和“右下”. * @param {PointLike} [options.offset] 作为要应用的对象相对于元素中心的偏移量(以像素为单位)。负数表示向左和向上. * @param {string} [options.color='#3FB1CE'] 如果未提供`options.element`. * @param {number} [options.scale=1] 如果未提供`options.element`,则用于默认文本的比例。. * @param {boolean} [options.draggable=false] 一个布尔值,指示是否可以将文本拖动到地图上的新位置. * @param {number} [options.clickTolerance=0] 用户在单击文本期间可以移动鼠标指针以将其视为有效单击(与文本拖动相反)的最大像素数。默认是继承地图的`clickTolerance`. * @param {number} [options.rotation=0] 文本相对于其各自的“rotationAlignment”设置的旋转角度(以度为单位)。正值将顺时针旋转文本. * @param {string} [options.pitchAlignment='auto'] `map` 将 `Text` 与地图平面对齐。 `viewport` 将 `Text` 与视口平面对齐。 `auto` 自动匹配 `rotationAlignment` 的值. * @param {string} [options.rotationAlignment='auto'] `map` 对齐 `Text` 相对于地图的旋转,在地图旋转时保持方位。 `viewport` 对齐 `Text` 相对于视口的旋转,与地图旋转无关。 `auto` 等价于 `viewport`. * @example * // Create a new Text. * const text = new vjmap.Text({text: "abc“}) * .setLngLat([30.5, 50.5]) * .addTo(map); * @example * // Set text options. * const text = new vjmap.Text({ * text: "abc“, * draggable: true * }).setLngLat([30.5, 50.5]) * .addTo(map); */ class Text extends Evented { private readonly _marker; _textContainerDom: HTMLElement; _style: object; /** * 构造函数 * @param options */ constructor(options?: { /** 文本内容. */ text?: string; /** 文本样式. */ style?: object; /** 用作标记的DOM元素。默认值是淡蓝色、液滴形状的SVG标记 */ element?: HTMLElement | undefined; /** 以像素为单位偏移为PointLike对象,以相对于元素的中心应用。负数表示向左和向上。*/ offset?: PointLike | undefined; /** 一个字符串,指示标记中应通过Marker.setLngLat定位到最接近描点的部分。 * 选项有 `'center'`, `'top'`, `'bottom'`, `'left'`, `'right'`, `'top-left'`, `'top-right'`, `'bottom-left'`, and `'bottom-right'`. * The default value os `'center'` */ anchor?: Anchor | undefined; /** 如果未提供options.element,则使用默认标记的颜色。默认值为浅蓝色(#3FB1CE) */ color?: string | undefined; /** 一个布尔值,指示标记是否能够被拖动到地图上的新位置。默认值为false */ draggable?: boolean | undefined; /** * 用户在点击标记时可以移动鼠标指针以使其被视为有效点击的最大像素数 * (与标记拖动相反)。默认值(0)是继承地图的clickTolerance。 */ clickTolerance?: number | null | undefined; /** 标记相对于其“旋转对齐”设置的旋转角度(度)。正值将使标记顺时针旋转. * The default value is 0. */ rotation?: number | undefined; /** map 对齐 Marker 相对于地图的旋转,在地图旋转时保持方位。 *`viewport`使`Marker`的旋转相对于视口对齐,与地图旋转无关。 *`auto`相当于`viewport`。 */ rotationAlignment?: Alignment | undefined; /** map 将 Marker 与地图平面对齐. * `viewport`将`Marker`与视口平面对齐. * `auto`自动匹配`rotationAlignment`的值` * The default value is `auto`. */ pitchAlignment?: Alignment | undefined; /** 如果未提供options.element,则使用默认标记的比例. * 默认比例(1)对应于高度“41px”和宽度“27px”`. */ scale?: number | undefined; }); /** * 将 `Text` 附加到 `Map` 对象。 * * @param {Map} map to add the text to. * @returns {Text} Returns itself to allow for method chaining. * @example * const text = new vjmap.Text({text: "abc"}) * .setLngLat([30.5, 50.5]) * .addTo(map); // add the text to the map */ addTo(map: Map): Text; /** * 从地图中删除文本。 * * @example * const text = new vjmap.Text().addTo(map); * text.remove(); * @returns {Text} Returns itself to allow for method chaining. */ remove(): Text; /** * 得到经纬度 * @return {any} */ getLngLat(): LngLatLike; /** * 设置经纬度 * @param lnglat * @return {this} */ setLngLat(lnglat: LngLatLike): Text; /** * 返回 `Text` 的 HTML 元素。 * * @returns {HTMLElement} Returns the text element. * @example * const element = text.getElement(); */ getElement(): HTMLElement; /** * 返回 `Text` 的 Marker 对象。 */ getMarker(): Marker; /** * Binds a `Popup` to the `Text` * @param popup * @return {this} */ setPopup(popup?: Popup): Text; /** * 返回绑定到 `Text` 的 `Popup` 实例。 * * @returns {Popup} Returns the popup. */ getPopup(): Popup; /** * 根据 `Popup` 的当前状态打开或关闭绑定到 `Text` 的 `Popup` 实例. * * @returns {Text} Returns itself to allow for method chaining. */ togglePopup(): Text; /** * 获取文本的偏移量. * * @returns {Point} The text's screen coordinates in pixels. */ getOffset(): number; /** * 设置文本的偏移量. * * @param {PointLike} offset 以像素为单位的偏移量,作为 PointLike 对象相对于元素的中心应用。负数表示向左和向上。 * @returns {Text} Returns itself to allow for method chaining. */ setOffset(offset: PointLike): Text; /** * 设置文本的 `draggable` 属性和功能. * * @param {boolean} [shouldBeDraggable=false] Turns drag functionality on/off. * @returns {Text} Returns itself to allow for method chaining. */ setDraggable(shouldBeDraggable: boolean): Text; /** * 如果可以拖动文本,则返回 true。 * * @returns {boolean} True if the text is draggable. */ isDraggable(): boolean; /** * Sets the `rotation` property of the text. * * @param {number} [rotation=0] 文本相对于其各自设置的旋转角度(顺时针,以度为单位). * @returns {Text} Returns itself to allow for method chaining. */ setRotation(rotation: number): Text; /** * 返回文本的当前旋转角度(以度为单位). * * @returns {number} The current rotation angle of the text. */ getRotation(): number; /** * 设置文本的 `rotationAlignment` 属性. * * @param {string} [alignment='auto'] 设置文本的 `rotationAlignment` 属性. * @returns {Text} Returns itself to allow for method chaining. */ setRotationAlignment(alignment: string): this; /** * 返回文本的当前 `rotationAlignment` 属性. * * @returns {string} The current rotational alignment of the text. */ getRotationAlignment(): string; /** * 设置文本的 `pitchAlignment` 属性. * * @param {string} [alignment] 设置文本的 `pitchAlignment` 属性。如果alignment是'auto',它会自动匹配'rotationAlignment'. * @returns {Text} Returns itself to allow for method chaining. */ setPitchAlignment(alignment: string): Text; /** * 返回文本的当前 `pitchAlignment` 属性. * * @returns {string} The current pitch alignment of the text in degrees. */ getPitchAlignment(): string; /** * 设置动画 * @param animationType * MAP_ANIMATION_NONE 无动画 * MAP_ANIMATION_BOUNCE 弹跳 * MAP_ANIMATION_DROP 坠落 */ setAnimation(animationType: string): void; /** * 显示 */ show(): void; /** * 隐藏 */ hide(): void; /** * 设置光标 * @param cur 光标名称 */ setCursor(cur: string): void; /** * 设置文本内容 * @param txt */ setText(txt: string): Text; /** * 得到文本内容 */ getText(): string; /** * 设置文本样式 * @param style */ setStyle(style?: object): Text; /** * 得到文本样式 */ getStyle(): object; } ``` ## 用法 ```js let text = new vjmap.Text({ text: "中国ABC", anchor: "top", draggable: true, offset: [0, 0], // x,y 方向像素偏移量 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' } }); text.setLngLat(map.getCenter()).addTo(map); ```
// --添加文本标记--这是基本dom的文本标记,如果数量多的文本,请用symbol来保证性能
// 地图服务对象
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: env.exampleMapId, // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    message.error(res.error)
}
// 获取地图范围
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapExtent);
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapExtent.center()), // 设置地图中心点
    zoom: 2, // 设置地图缩放级别
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
// 根据地图本身范围缩放地图至全图显示
map.fitMapBounds();
let mapBounds = map.getGeoBounds();
await map.onLoad();
let marker = new vjmap.Marker();
marker.setLngLat(map.getCenter()).addTo(map);
let text = new vjmap.Text({
    text: "中国ABC",
    anchor: "top",
    draggable: true,
    offset: [0, 0], // x,y 方向像素偏移量
    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'
    }
});
text.setLngLat(map.getCenter()).addTo(map);
// 设置一些自定义的属性
let customAttr = { "item": "test"};
text.customAttr = customAttr;
text.on('dragstart', (e) => {
    console.log("dragstart", e);
});
text.on('click', (e) => {
    // 获取自定义属性
    let txt = e.target;
    console.log("click", txt.customAttr.item);
});
text.on('mouseover', (e) => {
    console.log("mouseover", e);
});
text.on('mouseleave', (e) => {
    console.log("mouseleave", e);
});
text.on('dblclick', (e) => {
    console.log("dblclick", e);
});
text.on('contextmenu', (e) => {
    console.log("contextmenu", e);
});
text.on('mousedown', (e) => {
    console.log("mousedown", e);
});
text.on('mouseup', (e) => {
    console.log("mouseup", e);
});


## 示例 ![image-20240928112102269](https://vjmap.com/blogimages/202409281121309.png) [添加文本标记](https://vjmap.com/demo/#/demo/map/overlay/text/01textSingle) [能缩放的DIV文本](https://vjmap.com/demo/#/demo/map/overlay/text/02scaledivtext) [能缩放的符号文本](https://vjmap.com/demo/#/demo/map/overlay/text/02scalesymboltext) [能缩放的文字多边形](https://vjmap.com/demo/#/demo/map/overlay/text/03scalezpolygontext) --- # 多段线 ## 用法 ```js let data = { "type": "FeatureCollection", "features": [ { "id": "1", "type": "Feature", "properties": { "name": "polyline1", "color": "#00ffff" }, "geometry": { "coordinates": [ [ 3006.0952985307595, 15198.972934052355 ], [ 12606.64046359223, 9285.59366571666 ], [ 4745.324495099878, 3720.060236694666 ], [ 3006.0952985307595, 15198.972934052355 ] ], "type": "LineString" } }, { "id": "2", "type": "Feature", "properties": { "name": "polyline2", "color": "#ff00ff" }, "geometry": { "coordinates": [ [ 19841.833921319492, 13807.589576796912 ], [ 19841.833921319492, 4207.0444117340885 ], [ 26172.62819683108, 4207.0444117340885 ], [ 26172.62819683108, 13807.589576796912 ], [ 19841.833921319492, 13807.589576796912 ] ], "type": "LineString" } } ] } let polyline = new vjmap.Polyline({ data: map.toLngLat(data), // CAD几何坐标转渲染经纬度 // 如果是hover状态时,用红色,非hover状态时,取属性中的'color'做为颜色值 lineColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', ['get', 'color']], lineWidth: 3, isHoverPointer: true, isHoverFeatureState: true }); polyline.addTo(map); polyline.clickLayer(e => map.logInfo(`您点击了第 ${e.features[0].id} 个,名称为 ${e.features[0].properties.name},颜色为 ${e.features[0].properties.color} `)) polyline.hoverPopup(f => `

ID: ${f.properties.name}

Color: ${f.properties.color}`, { anchor: 'bottom' }); ```
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: "sys_hello", // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapLightStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    showError(res.error)
    return;
}
// 获取地图范围
let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapBounds);
//
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapBounds.center()), // 设置地图中心点
    zoom: 1, // 设置地图缩放级别
    pitch: 0,
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();
let data = {
    "type": "FeatureCollection",
    "features": [
        {
            "id": "1",
            "type": "Feature",
            "properties": {
                "name": "polyline1",
                "color": "#00ffff"
            },
            "geometry": {
                "coordinates": [
                    [
                        3006.0952985307595,
                        15198.972934052355
                    ],
                    [
                        12606.64046359223,
                        9285.59366571666
                    ],
                    [
                        4745.324495099878,
                        3720.060236694666
                    ],
                    [
                        3006.0952985307595,
                        15198.972934052355
                    ]
                ],
                "type": "LineString"
            }
        },
        {
            "id": "2",
            "type": "Feature",
            "properties": {
                "name": "polyline2",
                "color": "#ff00ff"
            },
            "geometry": {
                "coordinates": [
                    [
                        19841.833921319492,
                        13807.589576796912
                    ],
                    [
                        19841.833921319492,
                        4207.0444117340885
                    ],
                    [
                        26172.62819683108,
                        4207.0444117340885
                    ],
                    [
                        26172.62819683108,
                        13807.589576796912
                    ],
                    [
                        19841.833921319492,
                        13807.589576796912
                    ]
                ],
                "type": "LineString"
            }
        }
    ]
}
let polyline = new vjmap.Polyline({
    data: map.toLngLat(data), // CAD几何坐标转渲染经纬度
    // 如果是hover状态时,用红色,非hover状态时,取属性中的'color'做为颜色值
    lineColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', ['get', 'color']],
    lineWidth: 3,
    isHoverPointer: true,
    isHoverFeatureState: true
});
polyline.addTo(map);
// 点击事件
polyline.clickLayer(e => map.logInfo(`您点击了第 ${e.features[0].id} 个,名称为 ${e.features[0].properties.name},颜色为 ${e.features[0].properties.color} `))
// hover事件
polyline.hoverPopup(f => `<h3>ID: ${f.properties.name}</h3>Color: ${f.properties.color}`, { anchor: 'bottom' });


也可以用先单独创建数据源,再创建线图层的写法,如 ```js map.addSource("lines", { type: "geojson", data: data }); map.addLayer({ id: 'lineLayers', source: 'lines', type: 'line', "layout": { "line-cap": "square", //butt 尖头,round 圆头,square平头 "line-join": "round" //bevel平拐,round 圆拐,miter棱拐 }, "paint": { "line-width": 3, //宽度 "line-color": ['case', ['to-boolean', ['feature-state', 'hover']], 'red', ['get', 'color']] //颜色 } }) ``` 或 属性用驼峰式大小写,不用区分layout还是paint属性 ```js map.addGeoJSONSource("lines", data); map.addLineLayer("route", "lines", { lineCap: "square", //butt 尖头,round 圆头,square平头 lineJoin: "round", //bevel平拐,round 圆拐,miter棱拐 lineWidth: 3, //宽度 lineColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', ['get', 'color']] //颜色 }) ``` 这种通过创建数据源和图层的写法,响应事件时,需要监听`地图的图层事件`进行处理 ```js map.on("click", layerId, e => {}) ``` ## 常用方法 修改数据用 `setData` ```js polyline.setData(newDataJson); ``` 获取图层ID、数据源ID ```js // 获取图层ID polyline.getLayerId() // 获取数据源ID polyline.getSourceId() ``` 显示或隐藏 ```js // 显示 polyline.show() // 隐藏 polyline.hide() ``` 删除 ```js // 删除 polyline.remove() ``` ## 相关示例 ![image-20240928152216876](https://vjmap.com/blogimages/202409281522930.png) [单个多段线](https://vjmap.com/demo/#/demo/map/overlay/polyline/00polylineingle) [批量绘制不同类型的多段线](https://vjmap.com/demo/#/demo/map/overlay/polyline/01polylineManyType) [路径动画](https://vjmap.com/demo/#/demo/map/overlay/polyline/polylineAnimate) [渐变线](https://vjmap.com/demo/#/demo/map/overlay/polyline/polylineGradient) [多段线线型设置](https://vjmap.com/demo/#/demo/map/overlay/polyline/polylineLinetype) [线型填充图案动画](https://vjmap.com/demo/#/demo/map/overlay/polyline/polylinePattern) [多段线转多边形](https://vjmap.com/demo/#/demo/map/overlay/polyline/polylinetoPolygon) ## 类型定义 ```ts /** * 创建多段线. * **/ export class Polyline extends OverlayLayerBase { options: PolylineOptions; constructor(options: PolylineOptions); addTo(map: Map, beforeId?: string): void; /** 替换 GeoJSON 图层的当前数据。 @param {GeoJSON} [data] GeoJSON object to set. If not provided, defaults to an empty FeatureCollection. */ setData(data: PointGeoJsonInput | PointGeoJsonInput[] | GeoJsonGeomertry | GeoPointLike | any): void; setLineCap(value: DataDrivenPropertyValueSpecification<"butt" | "round" | "square">): this; getLineCap(): DataDrivenPropertyValueSpecification<"butt" | "round" | "square">; setLineJoin(value: DataDrivenPropertyValueSpecification<"bevel" | "round" | "miter">): this; getLineJoin(): DataDrivenPropertyValueSpecification<"bevel" | "round" | "miter">; setLineMiterMimit(value: PropertyValueSpecificationEx): this; getLineMiterMimit(): PropertyValueSpecificationEx; setLineRoundLimit(value: PropertyValueSpecificationEx): this; getLineRoundLimit(): PropertyValueSpecificationEx; setLineSortKey(value: DataDrivenPropertyValueSpecification): this; getLineSortKey(): DataDrivenPropertyValueSpecification; setLineOpacity(value: DataDrivenPropertyValueSpecification): this; getLineOpacity(): DataDrivenPropertyValueSpecification; setLineColor(value: DataDrivenPropertyValueSpecification): this; getLineColor(): DataDrivenPropertyValueSpecification; setLineTranslate(value: PropertyValueSpecificationEx<[number, number]>): this; getLineTranslate(): PropertyValueSpecificationEx<[number, number]>; setLineTranslateAnchor(value: PropertyValueSpecificationEx<"map" | "viewport">): this; getLineTranslateAnchor(): PropertyValueSpecificationEx<"map" | "viewport">; setLineWidth(value: DataDrivenPropertyValueSpecification): this; getLineWidth(): DataDrivenPropertyValueSpecification; setLineGapWidth(value: DataDrivenPropertyValueSpecification): this; getLineGapWidth(): DataDrivenPropertyValueSpecification; setLineOffset(value: DataDrivenPropertyValueSpecification): this; getLineOffset(): DataDrivenPropertyValueSpecification; setLineBlur(value: DataDrivenPropertyValueSpecification): this; getLineBlur(): DataDrivenPropertyValueSpecification; setLineDasharray(value: DataDrivenPropertyValueSpecification): this; getLineDasharray(): DataDrivenPropertyValueSpecification; setLinePattern(value: DataDrivenPropertyValueSpecification): this; getLinePattern(): DataDrivenPropertyValueSpecification; setLineGradient(value: ExpressionSpecificationEx): this; getLineGradient(): ExpressionSpecificationEx; } export class OverlayLayerBase { sourceId?: string; layerId?: string; _map?: Map; constructor(); addTo(map: Map, beforeId?: string): void; /** * 获取数据源ID * @return {string | undefined} */ getSourceId(): string | undefined; /** * 获取图层ID * @return {string | undefined} */ getLayerId(): string | undefined; /** * 获取数据源的数据 * @return {GeoJsonGeomertry | undefined} */ getData(): GeoJsonGeomertry | undefined; remove(): void; /** 每当鼠标悬停在这些图层上时,将地图的光标设置为“指针”。 @returns A function to remove the handler. * @param layerOrLayers */ hoverPointer(): void; /** 每当将鼠标悬停在这些图层中的某个特征上时,更新连接源 [s] 中特征的特征状态。 * @param enterCb * @param leaveCb */ hoverFeatureState(enterCb?: (arg0: {}) => void, leaveCb?: (arg0: {}) => void): void; /** 将鼠标悬停在这些图层中的某个要素上时,会显示一个弹出窗口。 @param htmlFunc Function that receives feature and popup, returns HTML. @param {Object} popupOptions Options passed to `Popup()` to customise popup. @example hoverPopup(f => `

${f.properties.Name}

${f.properties.Description}`, { anchor: 'left' }); */ hoverPopup(htmlFunc: any, popupOptions?: PopupOptions): any; /** 每当单击这些图层中的要素时都会显示一个弹出窗口。 @param htmlFunc Function that receives feature and popup, returns HTML. @param {Object} popupOptions Options passed to `Popup()` to customise popup. @returns A function that removes the handler. @example clickPopup(f => `

${f.properties.Name}

${f.properties.Description}`, { maxWidth: 500 }); */ clickPopup(htmlFunc: (arg0: {}) => void, popupOptions?: PopupOptions): any; /** 每当单击这些图层中的要素时都会触发回调。 @param {function} cb Callback that receives event with .features property @returns A function that removes the handler. */ clickLayer(cb: any): any; /** 当鼠标悬停在这些图层中的要素上时触发回调。 @returns A function to remove the handler. */ hoverLayer(cb: any): any; /** * 使给定的图层可见。 * @param layer */ show(): void; /** * 使给定的图层不可见。 * @param layer */ hide(): void; /** 根据参数使给定的图层隐藏或可见。 @param {boolean} state True for visible, false for hidden. */ toggle(state: boolean): boolean; /** 在一个或多个图层上设置绘制或布局属性。 @example setProperty('fillOpacity', 0.5) */ setProperty(prop: string | object, value?: any): void; /** 根据样式规范,获取给定图层 ID 的图层定义。 */ getLayerStyle(): LayerSpecification; /** * 设置图层样式 * @param layer * @param style */ setLayerStyle(style: any): void; /** 替换一个图层的过滤器。 @param {Array} filter New filter to set. @example setFilter(['==','level','0']]); */ setFilter(filter: FilterSpecification): void; } export interface OverlayLayerBaseOptions { sourceId?: string; sourceLayer?: string; layerId?: string; minzoom?: number; maxzoom?: number; filter?: any; visibility?: "visible" | "none"; isHoverPointer?: boolean; isHoverFeatureState?: boolean; } export type LineLayerSpecification = { id: string; type: "line"; metadata?: unknown; source: string; "source-layer"?: string; minzoom?: number; maxzoom?: number; filter?: FilterSpecification; layout?: { "line-cap"?: DataDrivenPropertyValueSpecification<"butt" | "round" | "square">; "line-join"?: DataDrivenPropertyValueSpecification<"bevel" | "round" | "miter">; "line-miter-limit"?: PropertyValueSpecificationEx; "line-round-limit"?: PropertyValueSpecificationEx; "line-sort-key"?: DataDrivenPropertyValueSpecification; visibility?: "visible" | "none"; }; paint?: { "line-opacity"?: DataDrivenPropertyValueSpecification; "line-color"?: DataDrivenPropertyValueSpecification; "line-translate"?: PropertyValueSpecificationEx<[number, number]>; "line-translate-anchor"?: PropertyValueSpecificationEx<"map" | "viewport">; "line-width"?: DataDrivenPropertyValueSpecification; "line-gap-width"?: DataDrivenPropertyValueSpecification; "line-offset"?: DataDrivenPropertyValueSpecification; "line-blur"?: DataDrivenPropertyValueSpecification; "line-dasharray"?: DataDrivenPropertyValueSpecification>; "line-pattern"?: DataDrivenPropertyValueSpecification; "line-gradient"?: ExpressionSpecificationEx; }; }; export type LineLayerStyleProp = { metadata?: unknown; source?: string; sourceLayer?: string; minzoom?: number; maxzoom?: number; filter?: FilterSpecification; lineCap?: DataDrivenPropertyValueSpecification<"butt" | "round" | "square">; lineJoin?: DataDrivenPropertyValueSpecification<"bevel" | "round" | "miter">; lineMiterMimit?: PropertyValueSpecificationEx; lineRoundLimit?: PropertyValueSpecificationEx; lineSortKey?: DataDrivenPropertyValueSpecification; visibility?: "visible" | "none"; lineOpacity?: DataDrivenPropertyValueSpecification; lineColor?: DataDrivenPropertyValueSpecification; lineTranslate?: PropertyValueSpecificationEx<[number, number]>; lineTranslateAnchor?: PropertyValueSpecificationEx<"map" | "viewport">; lineWidth?: DataDrivenPropertyValueSpecification; lineGapWidth?: DataDrivenPropertyValueSpecification; lineOffset?: DataDrivenPropertyValueSpecification; lineBlur?: DataDrivenPropertyValueSpecification; lineDasharray?: DataDrivenPropertyValueSpecification>; linePattern?: DataDrivenPropertyValueSpecification; lineGradient?: ExpressionSpecificationEx; }; ``` --- # 多边形 ## 用法 ```js let data = { "type": "FeatureCollection", "features": [ { "id": "1", "type": "Feature", "properties": { "name": "polygon1", "color": "#00ffff" }, "geometry": { "coordinates": [ [ [ 3006.0952985307595, 15198.972934052355 ], [ 12606.64046359223, 9285.59366571666 ], [ 4745.324495099878, 3720.060236694666 ], [ 3006.0952985307595, 15198.972934052355 ] ] ], "type": "Polygon" } }, { "id": "2", "type": "Feature", "properties": { "name": "polygon2", "color": "#ff00ff" }, "geometry": { "coordinates": [ [ [ 19841.833921319492, 13807.589576796912 ], [ 19841.833921319492, 4207.0444117340885 ], [ 26172.62819683108, 4207.0444117340885 ], [ 26172.62819683108, 13807.589576796912 ], [ 19841.833921319492, 13807.589576796912 ] ] ], "type": "Polygon" } } ] } let polygon = new vjmap.Polygon({ data: map.toLngLat(data), // CAD几何坐标转渲染经纬度 fillColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', ['get', 'color']], fillOpacity: 0.8, fillOutlineColor: "#f00", isHoverPointer: true, isHoverFeatureState: true }); polygon.addTo(map); polygon.clickLayer(e => map.logInfo(`您点击了第 ${e.features[0].id} 个,名称为 ${e.features[0].properties.name},颜色为 ${e.features[0].properties.color} `)) polygon.hoverPopup(f => `

ID: ${f.properties.name}

Color: ${f.properties.color}`, { anchor: 'bottom' }); ```
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: "sys_hello", // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapLightStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    showError(res.error)
    return;
}
// 获取地图范围
let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapBounds);
//
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapBounds.center()), // 设置地图中心点
    zoom: 1, // 设置地图缩放级别
    pitch: 0,
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();
let data = {
    "type": "FeatureCollection",
    "features": [
        {
            "id": "1",
            "type": "Feature",
            "properties": {
                "name": "polygon1",
                "color": "#00ffff"
            },
            "geometry": {
                "coordinates": [
                    [
                        [
                            3006.0952985307595,
                            15198.972934052355
                        ],
                        [
                            12606.64046359223,
                            9285.59366571666
                        ],
                        [
                            4745.324495099878,
                            3720.060236694666
                        ],
                        [
                            3006.0952985307595,
                            15198.972934052355
                        ]
                    ]
                ],
                "type": "Polygon"
            }
        },
        {
            "id": "2",
            "type": "Feature",
            "properties": {
                "name": "polygon2",
                "color": "#ff00ff"
            },
            "geometry": {
                "coordinates": [
                    [
                        [
                            19841.833921319492,
                            13807.589576796912
                        ],
                        [
                            19841.833921319492,
                            4207.0444117340885
                        ],
                        [
                            26172.62819683108,
                            4207.0444117340885
                        ],
                        [
                            26172.62819683108,
                            13807.589576796912
                        ],
                        [
                            19841.833921319492,
                            13807.589576796912
                        ]
                    ]
                ],
                "type": "Polygon"
            }
        }
    ]
}
let polygon = new vjmap.Polygon({
    data: map.toLngLat(data), // CAD几何坐标转渲染经纬度
    fillColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', ['get', 'color']],
    fillOpacity: 0.8,
    fillOutlineColor: "#f00",
    isHoverPointer: true,
    isHoverFeatureState: true
});
polygon.addTo(map);
// 点击事件
polygon.clickLayer(e => map.logInfo(`您点击了第 ${e.features[0].id} 个,名称为 ${e.features[0].properties.name},颜色为 ${e.features[0].properties.color} `))
// hover事件
polygon.hoverPopup(f => `<h3>ID: ${f.properties.name}</h3>Color: ${f.properties.color}`, { anchor: 'bottom' });


也可以用先单独创建数据源,再创建填充图层的写法,如 ```js map.addSource("fillSources", { type: "geojson", data: data }); map.addLayer({ id: 'fillLayers', source: 'fillSources', type: 'fill', "layout": {}, "paint": { "fill-color": ['case', ['to-boolean', ['feature-state', 'hover']], 'red', ['get', 'color']], //颜色 'fill-opacity': 0.8, 'fill-outline-color': "#f00" } }) ``` 或 属性用驼峰式大小写,不用区分layout还是paint属性 ```js map.addGeoJSONSource("fillSources", data); map.addFillLayer("fillLayers", "fillSources", { fillColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', ['get', 'color']], //颜色 fillOpacity: 0.8, fillOutlineColor: "#f00" }) ``` 这种通过创建数据源和图层的写法,响应事件时,需要监听`地图的图层事件`进行处理 ```js map.on("click", layerId, e => {}) ``` ## 常用方法 修改数据用 `setData` ```js polygon.setData(newDataJson); ``` 获取图层ID、数据源ID ```js // 获取图层ID polygon.getLayerId() // 获取数据源ID polygon.getSourceId() ``` 显示或隐藏 ```js // 显示 polygon.show() // 隐藏 polygon.hide() ``` 删除 ```js // 删除 polygon.remove() ``` ## 相关示例 ![image-20240928153704452](https://vjmap.com/blogimages/202409281537546.png) [单个多边形](https://vjmap.com/demo/#/demo/map/overlay/polygon/01polygonSingle) [两个多边形](https://vjmap.com/demo/#/demo/map/overlay/polygon/02polygonTwo) [批量绘制多边形](https://vjmap.com/demo/#/demo/map/overlay/polygon/03polygonMany) [多边形条件过滤](https://vjmap.com/demo/#/demo/map/overlay/polygon/polygonFilter) [批量绘制不同类型的多边形](https://vjmap.com/demo/#/demo/map/overlay/polygon/polygonManyType) [缕空多边形](https://vjmap.com/demo/#/demo/map/overlay/polygon/polygonMulti) [复杂缕空多边形](https://vjmap.com/demo/#/demo/map/overlay/polygon/polygonMultiHole) [多边形图案样式](https://vjmap.com/demo/#/demo/map/overlay/polygon/polygonPattern) [多边形高亮时显示线宽](https://vjmap.com/demo/#/demo/map/overlay/polygon/polygonzHoverLineWidth) ## 类型定义 ```ts /** * 创建多边形. * **/ export class Polygon extends OverlayLayerBase { options: PolygonOptions; constructor(options: PolygonOptions); addTo(map: Map, beforeId?: string): void; /** 替换 GeoJSON 图层的当前数据。 @param {GeoJSON} [data] GeoJSON object to set. If not provided, defaults to an empty FeatureCollection. */ setData(data: PointGeoJsonInput | PointGeoJsonInput[] | GeoJsonGeomertry | GeoPointLike | any): void; /** 为一层或多层设置`fill-sort-key`布局属性。 */ setFillSortKey(value: DataDrivenPropertyValueSpecification): this; /** 为一个或多个图层设置`fill-antialias` 绘制属性。 */ setFillAntialias(value: PropertyValueSpecificationEx): this; /** 为一个或多个图层设置`fill-opacity` 绘画属性。 */ setFillOpacity(value: DataDrivenPropertyValueSpecification): this; /** 为一个或多个图层设置`fill-color` 绘画属性。 */ setFillColor(value: DataDrivenPropertyValueSpecification): this; /** 为一个或多个图层设置`fill-outline-color` 绘画属性。*/ setFillOutlineColor(value: DataDrivenPropertyValueSpecification): this; /** 为一个或多个图层设置`fill-translate` 绘制属性。 */ setFillTranslate(value: DataDrivenPropertyValueSpecification): this; /** 为一个或多个图层设置`fill-translate-anchor` 绘制属性。 */ setFillTranslateAnchor(value: PropertyValueSpecificationEx<"map" | "viewport">): this; /** 为一个或多个图层设置`fill-pattern` 绘制属性。 */ setFillPattern(value: DataDrivenPropertyValueSpecification): this; /** 获取图层的`fill-sort-key` 布局属性. */ getFillSortKey(): DataDrivenPropertyValueSpecification; /** 获取图层的“填充抗锯齿”绘制属性。 */ getFillAntialias(): PropertyValueSpecificationEx; /** 获取图层的 `fill-opacity` 绘制属性。 */ getFillOpacity(): DataDrivenPropertyValueSpecification; /** 获取图层的 `fill-color` 绘制属性。 */ getFillColor(): DataDrivenPropertyValueSpecification; /** 获取图层的 `fill-outline-color` 绘制属性。 */ getFillOutlineColor(): DataDrivenPropertyValueSpecification; /** 获取图层的 `fill-translate` 绘制属性。 */ getFillTranslate(): PropertyValueSpecificationEx<[number, number]>; /** 获取图层的 `fill-translate-anchor` 绘制属性。 */ getFillTranslateAnchor(): PropertyValueSpecificationEx<"map" | "viewport">; /** 获取图层的`fill-pattern` 绘制属性。 */ getFillPattern(): DataDrivenPropertyValueSpecification; } export class OverlayLayerBase { sourceId?: string; layerId?: string; _map?: Map; constructor(); addTo(map: Map, beforeId?: string): void; /** * 获取数据源ID * @return {string | undefined} */ getSourceId(): string | undefined; /** * 获取图层ID * @return {string | undefined} */ getLayerId(): string | undefined; /** * 获取数据源的数据 * @return {GeoJsonGeomertry | undefined} */ getData(): GeoJsonGeomertry | undefined; remove(): void; /** 每当鼠标悬停在这些图层上时,将地图的光标设置为“指针”。 @returns A function to remove the handler. * @param layerOrLayers */ hoverPointer(): void; /** 每当将鼠标悬停在这些图层中的某个特征上时,更新连接源 [s] 中特征的特征状态。 * @param enterCb * @param leaveCb */ hoverFeatureState(enterCb?: (arg0: {}) => void, leaveCb?: (arg0: {}) => void): void; /** 将鼠标悬停在这些图层中的某个要素上时,会显示一个弹出窗口。 @param htmlFunc Function that receives feature and popup, returns HTML. @param {Object} popupOptions Options passed to `Popup()` to customise popup. @example hoverPopup(f => `

${f.properties.Name}

${f.properties.Description}`, { anchor: 'left' }); */ hoverPopup(htmlFunc: any, popupOptions?: PopupOptions): any; /** 每当单击这些图层中的要素时都会显示一个弹出窗口。 @param htmlFunc Function that receives feature and popup, returns HTML. @param {Object} popupOptions Options passed to `Popup()` to customise popup. @returns A function that removes the handler. @example clickPopup(f => `

${f.properties.Name}

${f.properties.Description}`, { maxWidth: 500 }); */ clickPopup(htmlFunc: (arg0: {}) => void, popupOptions?: PopupOptions): any; /** 每当单击这些图层中的要素时都会触发回调。 @param {function} cb Callback that receives event with .features property @returns A function that removes the handler. */ clickLayer(cb: any): any; /** 当鼠标悬停在这些图层中的要素上时触发回调。 @returns A function to remove the handler. */ hoverLayer(cb: any): any; /** * 使给定的图层可见。 * @param layer */ show(): void; /** * 使给定的图层不可见。 * @param layer */ hide(): void; /** 根据参数使给定的图层隐藏或可见。 @param {boolean} state True for visible, false for hidden. */ toggle(state: boolean): boolean; /** 在一个或多个图层上设置绘制或布局属性。 @example setProperty('fillOpacity', 0.5) */ setProperty(prop: string | object, value?: any): void; /** 根据样式规范,获取给定图层 ID 的图层定义。 */ getLayerStyle(): LayerSpecification; /** * 设置图层样式 * @param layer * @param style */ setLayerStyle(style: any): void; /** 替换一个图层的过滤器。 @param {Array} filter New filter to set. @example setFilter(['==','level','0']]); */ setFilter(filter: FilterSpecification): void; } export interface OverlayLayerBaseOptions { sourceId?: string; sourceLayer?: string; layerId?: string; minzoom?: number; maxzoom?: number; filter?: any; visibility?: "visible" | "none"; isHoverPointer?: boolean; isHoverFeatureState?: boolean; } export type FillLayerSpecification = { id: string; type: "fill"; metadata?: unknown; source: string; "source-layer"?: string; minzoom?: number; maxzoom?: number; filter?: FilterSpecification; layout?: { "fill-sort-key"?: DataDrivenPropertyValueSpecification; visibility?: "visible" | "none"; }; paint?: { "fill-antialias"?: PropertyValueSpecificationEx; "fill-opacity"?: DataDrivenPropertyValueSpecification; "fill-color"?: DataDrivenPropertyValueSpecification; "fill-outline-color"?: DataDrivenPropertyValueSpecification; "fill-translate"?: PropertyValueSpecificationEx<[number, number]>; "fill-translate-anchor"?: PropertyValueSpecificationEx<"map" | "viewport">; "fill-pattern"?: DataDrivenPropertyValueSpecification; }; }; export type FillLayerStyleProp = { metadata?: unknown; source?: string; sourceLayer?: string; minzoom?: number; maxzoom?: number; filter?: FilterSpecification; visibility?: "visible" | "none"; fillSortKey?: DataDrivenPropertyValueSpecification; fillAntialias?: PropertyValueSpecificationEx; fillOpacity?: DataDrivenPropertyValueSpecification; fillColor?: DataDrivenPropertyValueSpecification; fillOutlineColor?: DataDrivenPropertyValueSpecification; fillTranslate?: PropertyValueSpecificationEx<[number, number]>; fillTranslateAnchor?: PropertyValueSpecificationEx<"map" | "viewport">; fillPattern?: DataDrivenPropertyValueSpecification; }; export type FilterSpecification = ["has", string] | ["!has", string] | ["==", string, string | number | boolean] | ["!=", string, string | number | boolean] | [">", string, string | number | boolean] | [">=", string, string | number | boolean] | ["<", string, string | number | boolean] | ["<=", string, string | number | boolean] | Array; ``` --- # 拉伸 ## 用法 ```js let data = { "type": "FeatureCollection", "features": [ { "id": "1", "type": "Feature", "properties": { "name": "fillExtrusion1", "color": "#00ffff", baseHeight: 0, height: vjmap.randInt(1000000, 2000000) }, "geometry": { "coordinates": [ [ [ 3006.0952985307595, 15198.972934052355 ], [ 12606.64046359223, 9285.59366571666 ], [ 4745.324495099878, 3720.060236694666 ], [ 3006.0952985307595, 15198.972934052355 ] ] ], "type": "Polygon" } }, { "id": "2", "type": "Feature", "properties": { "name": "fillExtrusion2", "color": "#ff00ff", baseHeight: 0, height: vjmap.randInt(1000000, 2000000) }, "geometry": { "coordinates": [ [ [ 19841.833921319492, 13807.589576796912 ], [ 19841.833921319492, 4207.0444117340885 ], [ 26172.62819683108, 4207.0444117340885 ], [ 26172.62819683108, 13807.589576796912 ], [ 19841.833921319492, 13807.589576796912 ] ] ], "type": "Polygon" } } ] } let fillExtrusion = new vjmap.FillExtrusion({ data: map.toLngLat(data), // CAD几何坐标转渲染经纬度 // 如果是hover状态时,用红色,非hover状态时,取属性中的'color'做为颜色值 fillExtrusionColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', ['get', 'color']], fillExtrusionOpacity: 1, fillExtrusionHeight: ['get', 'height'], fillExtrusionBase: ['get', 'baseHeight'], isHoverPointer: true, isHoverFeatureState: true }); fillExtrusion.addTo(map); fillExtrusion.clickLayer(e => map.logInfo(`您点击了第 ${e.features[0].id} 个,名称为 ${e.features[0].properties.name},颜色为 ${e.features[0].properties.color} `)) fillExtrusion.hoverPopup(f => `

ID: ${f.properties.name}

Color: ${f.properties.color}`, { anchor: 'bottom' }); ```
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: "sys_hello", // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapLightStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    showError(res.error)
    return;
}
// 获取地图范围
let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapBounds);
//
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapBounds.center()), // 设置地图中心点
    zoom: 1, // 设置地图缩放级别
    pitch: 45,
    antialias: true,
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();
let data = {
    "type": "FeatureCollection",
    "features": [
        {
            "id": "1",
            "type": "Feature",
            "properties": {
                "name": "fillExtrusion1",
                "color": "#00ffff",
                 baseHeight: 0,
                 height: vjmap.randInt(1000000, 2000000)
            },
            "geometry": {
                "coordinates": [
                    [
                        [
                            3006.0952985307595,
                            15198.972934052355
                        ],
                        [
                            12606.64046359223,
                            9285.59366571666
                        ],
                        [
                            4745.324495099878,
                            3720.060236694666
                        ],
                        [
                            3006.0952985307595,
                            15198.972934052355
                        ]
                    ]
                ],
                "type": "Polygon"
            }
        },
        {
            "id": "2",
            "type": "Feature",
            "properties": {
                "name": "fillExtrusion2",
                "color": "#ff00ff",
                 baseHeight: 0,
                 height: vjmap.randInt(1000000, 2000000)
            },
            "geometry": {
                "coordinates": [
                    [
                        [
                            19841.833921319492,
                            13807.589576796912
                        ],
                        [
                            19841.833921319492,
                            4207.0444117340885
                        ],
                        [
                            26172.62819683108,
                            4207.0444117340885
                        ],
                        [
                            26172.62819683108,
                            13807.589576796912
                        ],
                        [
                            19841.833921319492,
                            13807.589576796912
                        ]
                    ]
                ],
                "type": "Polygon"
            }
        }
    ]
}
let fillExtrusion = new vjmap.FillExtrusion({
    data: map.toLngLat(data), // CAD几何坐标转渲染经纬度
    // 如果是hover状态时,用红色,非hover状态时,取属性中的'color'做为颜色值
    fillExtrusionColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', ['get', 'color']],
    fillExtrusionOpacity: 1,
    fillExtrusionHeight: ['get', 'height'],
    fillExtrusionBase: ['get', 'baseHeight'],
    isHoverPointer: true,
    isHoverFeatureState: true
});
fillExtrusion.addTo(map);
// 点击事件
fillExtrusion.clickLayer(e => map.logInfo(`您点击了第 ${e.features[0].id} 个,名称为 ${e.features[0].properties.name},颜色为 ${e.features[0].properties.color} `))
// hover事件
fillExtrusion.hoverPopup(f => `<h3>ID: ${f.properties.name}</h3>Color: ${f.properties.color}`, { anchor: 'bottom' });


也可以用先单独创建数据源,再创建拉伸图层的写法,如 ```js map.addSource("fillExtrusionSources", { type: "geojson", data: data }); map.addLayer({ id: 'fillExtrusionLayer', source: 'fillExtrusionSources', type: 'fill-extrusion', "layout": {}, "paint": { "fill-extrusion-opacity": 1.0, "fill-extrusion-color": ['case', ['to-boolean', ['feature-state', 'hover']], 'red', ['get', 'color']], //颜色 "fill-extrusion-height": ['get', 'height'], "fill-extrusion-base": ['get', 'baseHeight'] } }) ``` 或 属性用驼峰式大小写,不用区分layout还是paint属性 ```js map.addGeoJSONSource("fillExtrusionSources", data); map.addFillExtrusionLayer("fillExtrusionLayer", "fillExtrusionSources", { fillExtrusionColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', ['get', 'color']], fillExtrusionOpacity: 1, fillExtrusionHeight: ['get', 'height'], fillExtrusionBase: ['get', 'baseHeight'] }) ``` 这种通过创建数据源和图层的写法,响应事件时,需要监听`地图的图层事件`进行处理 ```js map.on("click", layerId, e => {}) ``` ## 常用方法 修改数据用 `setData` ```js fillExtrusion.setData(newDataJson); ``` 获取图层ID、数据源ID ```js // 获取图层ID fillExtrusion.getLayerId() // 获取数据源ID fillExtrusion.getSourceId() ``` 显示或隐藏 ```js // 显示 fillExtrusion.show() // 隐藏 fillExtrusion.hide() ``` 删除 ```js // 删除 fillExtrusion.remove() ``` ## 相关示例 ![image-20240928162206302](https://vjmap.com/blogimages/202409281622363.png) [拉伸多边形](https://vjmap.com/demo/#/demo/map/overlay/fillextrusion/fillextrusion) [拉伸多边形指定级别显示](https://vjmap.com/demo/#/demo/map/overlay/fillextrusion/fillextrusionInterpolate) [拉伸多段线](https://vjmap.com/demo/#/demo/map/overlay/fillextrusion/fillExtrusionsPolyline) [拉伸像素多段线](https://vjmap.com/demo/#/demo/map/overlay/fillextrusion/fillExtrusionsPolylinePixel) [拉伸图层中的多段线](https://vjmap.com/demo/#/demo/map/overlay/fillextrusion/fillExtrusionsZFromMapLayer) ## 类型定义 ```ts /** * 创建拉伸的多边形. * **/ export class FillExtrusion extends OverlayLayerBase { options: FillExtrusionOptions; constructor(options: FillExtrusionOptions); addTo(map: Map, beforeId?: string): void; /** 替换 GeoJSON 图层的当前数据。 @param {GeoJSON} [data] GeoJSON object to set. If not provided, defaults to an empty FeatureCollection. */ setData(data: PointGeoJsonInput | PointGeoJsonInput[] | GeoJsonGeomertry | GeoPointLike | any): void; setFillExtrusionOpacity(value: PropertyValueSpecificationEx): this; getFillExtrusionOpacity(): PropertyValueSpecificationEx; setFillExtrusionColor(value: DataDrivenPropertyValueSpecification): this; getFillExtrusionColor(): DataDrivenPropertyValueSpecification; setFillExtrusionTranslate(value: PropertyValueSpecificationEx<[number, number]>): this; getFillExtrusionTranslate(): PropertyValueSpecificationEx<[number, number]>; setFillExtrusionTranslateAnchor(value: PropertyValueSpecificationEx<"map" | "viewport">): this; getFillExtrusionTranslateAnchor(): PropertyValueSpecificationEx<"map" | "viewport">; setFillExtrusionPattern(value: DataDrivenPropertyValueSpecification): this; getFillExtrusionPattern(): DataDrivenPropertyValueSpecification; setFillExtrusionHeight(value: DataDrivenPropertyValueSpecification): this; getFillExtrusionHeight(): DataDrivenPropertyValueSpecification; setFillExtrusionBase(value: DataDrivenPropertyValueSpecification): this; getFillExtrusionBase(): DataDrivenPropertyValueSpecification; setFillExtrusionVerticalGradient(value: PropertyValueSpecificationEx): this; getFillExtrusionVerticalGradient(): PropertyValueSpecificationEx; } export type FillExtrusionLayerSpecification = { id: string; type: "fill-extrusion"; metadata?: unknown; source: string; "source-layer"?: string; minzoom?: number; maxzoom?: number; filter?: FilterSpecification; layout?: { visibility?: "visible" | "none"; }; paint?: { "fill-extrusion-opacity"?: PropertyValueSpecificationEx; "fill-extrusion-color"?: DataDrivenPropertyValueSpecification; "fill-extrusion-translate"?: PropertyValueSpecificationEx<[number, number]>; "fill-extrusion-translate-anchor"?: PropertyValueSpecificationEx<"map" | "viewport">; "fill-extrusion-pattern"?: DataDrivenPropertyValueSpecification; "fill-extrusion-height"?: DataDrivenPropertyValueSpecification; "fill-extrusion-base"?: DataDrivenPropertyValueSpecification; "fill-extrusion-vertical-gradient"?: PropertyValueSpecificationEx; }; }; export type FillExtrusionLayerStyleProp = { metadata?: unknown; source?: string; sourceLayer?: string; minzoom?: number; maxzoom?: number; filter?: FilterSpecification; visibility?: "visible" | "none"; fillExtrusionOpacity?: PropertyValueSpecificationEx; fillExtrusionColor?: DataDrivenPropertyValueSpecification; fillExtrusionTranslate?: PropertyValueSpecificationEx<[number, number]>; fillExtrusionTranslateAnchor?: PropertyValueSpecificationEx<"map" | "viewport">; fillExtrusionPattern?: DataDrivenPropertyValueSpecification; fillExtrusionHeight?: DataDrivenPropertyValueSpecification; fillExtrusionBase?: DataDrivenPropertyValueSpecification; fillExtrusionVerticalGradient?: PropertyValueSpecificationEx; }; export interface FillExtrusionOptions extends OverlayLayerBaseOptions { data: PointGeoJsonInput | PointGeoJsonInput[] | GeoJsonGeomertry | GeoPointLike | any; fillExtrusionOpacity?: PropertyValueSpecificationEx; fillExtrusionColor?: DataDrivenPropertyValueSpecification; fillExtrusionTranslate?: PropertyValueSpecificationEx<[number, number]>; fillExtrusionTranslateAnchor?: PropertyValueSpecificationEx<"map" | "viewport">; fillExtrusionPattern?: DataDrivenPropertyValueSpecification; fillExtrusionHeight?: DataDrivenPropertyValueSpecification; fillExtrusionBase?: DataDrivenPropertyValueSpecification; fillExtrusionVerticalGradient?: PropertyValueSpecificationEx; } export class OverlayLayerBase { sourceId?: string; layerId?: string; _map?: Map; constructor(); addTo(map: Map, beforeId?: string): void; /** * 获取数据源ID * @return {string | undefined} */ getSourceId(): string | undefined; /** * 获取图层ID * @return {string | undefined} */ getLayerId(): string | undefined; /** * 获取数据源的数据 * @return {GeoJsonGeomertry | undefined} */ getData(): GeoJsonGeomertry | undefined; remove(): void; /** 每当鼠标悬停在这些图层上时,将地图的光标设置为“指针”。 @returns A function to remove the handler. * @param layerOrLayers */ hoverPointer(): void; /** 每当将鼠标悬停在这些图层中的某个特征上时,更新连接源 [s] 中特征的特征状态。 * @param enterCb * @param leaveCb */ hoverFeatureState(enterCb?: (arg0: {}) => void, leaveCb?: (arg0: {}) => void): void; /** 将鼠标悬停在这些图层中的某个要素上时,会显示一个弹出窗口。 @param htmlFunc Function that receives feature and popup, returns HTML. @param {Object} popupOptions Options passed to `Popup()` to customise popup. @example hoverPopup(f => `

${f.properties.Name}

${f.properties.Description}`, { anchor: 'left' }); */ hoverPopup(htmlFunc: any, popupOptions?: PopupOptions): any; /** 每当单击这些图层中的要素时都会显示一个弹出窗口。 @param htmlFunc Function that receives feature and popup, returns HTML. @param {Object} popupOptions Options passed to `Popup()` to customise popup. @returns A function that removes the handler. @example clickPopup(f => `

${f.properties.Name}

${f.properties.Description}`, { maxWidth: 500 }); */ clickPopup(htmlFunc: (arg0: {}) => void, popupOptions?: PopupOptions): any; /** 每当单击这些图层中的要素时都会触发回调。 @param {function} cb Callback that receives event with .features property @returns A function that removes the handler. */ clickLayer(cb: any): any; /** 当鼠标悬停在这些图层中的要素上时触发回调。 @returns A function to remove the handler. */ hoverLayer(cb: any): any; /** * 使给定的图层可见。 * @param layer */ show(): void; /** * 使给定的图层不可见。 * @param layer */ hide(): void; /** 根据参数使给定的图层隐藏或可见。 @param {boolean} state True for visible, false for hidden. */ toggle(state: boolean): boolean; /** 在一个或多个图层上设置绘制或布局属性。 @example setProperty('fillOpacity', 0.5) */ setProperty(prop: string | object, value?: any): void; /** 根据样式规范,获取给定图层 ID 的图层定义。 */ getLayerStyle(): LayerSpecification; /** * 设置图层样式 * @param layer * @param style */ setLayerStyle(style: any): void; /** 替换一个图层的过滤器。 @param {Array} filter New filter to set. @example setFilter(['==','level','0']]); */ setFilter(filter: FilterSpecification): void; } export interface OverlayLayerBaseOptions { sourceId?: string; sourceLayer?: string; layerId?: string; minzoom?: number; maxzoom?: number; filter?: any; visibility?: "visible" | "none"; isHoverPointer?: boolean; isHoverFeatureState?: boolean; } ``` --- # 符号 ## 用法 ```js let data = { "type": "FeatureCollection", "features": [ { "id": "1", "type": "Feature", "properties": { "name": "point1", "color": "#00ffff", "icon": "icon1" }, "geometry": { "coordinates": [ 6971.5378667085715, 14385.014943907285 ], "type": "Point" } }, { "id": "2", "type": "Feature", "properties": { "name": "point2", "color": "#ff00ff", "icon": "icon2" }, "geometry": { "coordinates": [ 21789.770621477073, 14037.169104593304 ], "type": "Point" } } ] } let symbol = new vjmap.Symbol({ data: map.toLngLat(data), // CAD几何坐标转渲染经纬度 // 图标名来源于geojson属性中的icon字段 iconImage: ['get', 'icon'], iconOffset: [0, -12], // 鼠标hover时设置透明度0.8,不悬浮1.0 iconOpacity: ['case', ['to-boolean', ['feature-state', 'hover']], 0.8, 1.0], textField: ['get', 'name'], textFont: ['Arial Unicode MS Regular'], textSize: 14, textColor: '#FFA0FD', textOffset: [0, 0.5], textAnchor: 'top', iconAllowOverlap: false, textAllowOverlap: false, isHoverPointer: true, isHoverFeatureState: true }); symbol.addTo(map); symbol.clickLayer(e => map.logInfo(`您点击了第 ${e.features[0].id} 个,名称为 ${e.features[0].properties.name},颜色为 ${e.features[0].properties.color} `)) symbol.hoverPopup(f => `

ID: ${f.properties.name}

Color: ${f.properties.color}`, { anchor: 'bottom' }); ```
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: "sys_hello", // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapLightStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    showError(res.error)
    return;
}
// 获取地图范围
let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapBounds);
//
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapBounds.center()), // 设置地图中心点
    zoom: 1, // 设置地图缩放级别
    pitch: 0,
    antialias: true,
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();
let data = {
    "type": "FeatureCollection",
    "features": [
        {
            "id": "1",
            "type": "Feature",
            "properties": {
                "name": "point1",
                "color": "#00ffff",
                "icon": "icon1"
            },
            "geometry": {
                "coordinates": [
                    6971.5378667085715,
                    14385.014943907285
                ],
                "type": "Point"
            }
        },
        {
            "id": "2",
            "type": "Feature",
            "properties": {
                "name": "point2",
                "color": "#ff00ff",
                "icon": "icon2"
            },
            "geometry": {
                "coordinates": [
                    21789.770621477073,
                    14037.169104593304
                ],
                "type": "Point"
            }
        }
    ]
}
for (let i = 1; i <= 2; i++) {
    await map.loadImageEx("icon" + i, `https://vjmap.com/static/assets/images/sensor${i + 1}.png`);
}
let symbol = new vjmap.Symbol({
    data: map.toLngLat(data), // CAD几何坐标转渲染经纬度
    // 图标名来源于geojson属性中的icon字段
    iconImage: ['get', 'icon'],
    iconOffset: [0, -12],
    // 鼠标hover时设置透明度0.8,不悬浮1.0
    iconOpacity: ['case', ['to-boolean', ['feature-state', 'hover']], 0.8, 1.0],
    textField: ['get', 'name'],
    textFont: ['Arial Unicode MS Regular'],
    textSize: 14,
    textColor: '#FFA0FD',
    textOffset: [0, 0.5],
    textAnchor: 'top',
    iconAllowOverlap: false,
    textAllowOverlap: false,
    isHoverPointer: true,
    isHoverFeatureState: true
});
symbol.addTo(map);
// 点击事件
symbol.clickLayer(e => map.logInfo(`您点击了第 ${e.features[0].id} 个,名称为 ${e.features[0].properties.name},颜色为 ${e.features[0].properties.color} `))
// hover事件
symbol.hoverPopup(f => `<h3>ID: ${f.properties.name}</h3>Color: ${f.properties.color}`, { anchor: 'bottom' });


也可以用先单独创建数据源,再创建符号图层的写法,如 ```js map.addSource("symbolSources", { type: "geojson", data: data }); map.addLayer({ id: 'symbolLayer', source: 'symbolSources', type: 'symbol', "layout": {}, "paint": { 'icon-image': ['get', 'icon'], 'icon-offset': [0, -12], // 鼠标hover时设置透明度0.8,不悬浮1.0 'icon-opacity': ['case', ['to-boolean', ['feature-state', 'hover']], 0.8, 1.0], 'text-field': ['get', 'name'], 'text-font': ['Arial Unicode MS Regular'], 'text-size': 14, 'text-color': '#FFA0FD', 'text-offset': [0, 0.5], 'text-anchor': 'top', 'icon-allow-overlap': false, 'text-allow-overlap': false, } }) ``` 或 属性用驼峰式大小写,不用区分layout还是paint属性 ```js map.addGeoJSONSource("symbolSources", data); map.addSymbolLayer("symbolLayer", "symbolSources", { iconImage: ['get', 'icon'], iconOffset: [0, -12], // 鼠标hover时设置透明度0.8,不悬浮1.0 iconOpacity: ['case', ['to-boolean', ['feature-state', 'hover']], 0.8, 1.0], textField: ['get', 'name'], textFont: ['Arial Unicode MS Regular'], textSize: 14, textColor: '#FFA0FD', textOffset: [0, 0.5], textAnchor: 'top', iconAllowOverlap: false, textAllowOverlap: false, }) ``` 这种通过创建数据源和图层的写法,响应事件时,需要监听`地图的图层事件`进行处理 ```js map.on("click", layerId, e => {}) ``` ## 常用方法 修改数据用 `setData` ```js symbol.setData(newDataJson); ``` 获取图层ID、数据源ID ```js // 获取图层ID symbol.getLayerId() // 获取数据源ID symbol.getSourceId() ``` 显示或隐藏 ```js // 显示 symbol.show() // 隐藏 symbol.hide() ``` 删除 ```js // 删除 symbol.remove() ``` ## 相关示例 ![image-20240928163951927](https://vjmap.com/blogimages/202409281639985.png) [符号图标](https://vjmap.com/demo/#/demo/map/overlay/symbol/symbol) [颜色随状态值变化的符号图标](https://vjmap.com/demo/#/demo/map/overlay/symbol/symbolColorIcon) [自定义图标图层](https://vjmap.com/demo/#/demo/map/overlay/symbol/symbolCustomImage) [创建自定义图标](https://vjmap.com/demo/#/demo/map/overlay/symbol/symbolCustomText) [绘制批量图标文本(不重叠)](https://vjmap.com/demo/#/demo/map/overlay/symbol/symbolManyNoOverlap) [绘制批量图标文本(允许重叠)](https://vjmap.com/demo/#/demo/map/overlay/symbol/symbolManyOverlap) [可拉伸的文本背景图片](https://vjmap.com/demo/#/demo/map/overlay/symbol/symbolStretchImage) [复杂符号文本效果](https://vjmap.com/demo/#/demo/map/overlay/symbol/symbolzCompose) ## 类型定义 ```ts /** * 创建符号图层. * **/ class Symbol extends OverlayLayerBase { options: SymbolOptions; constructor(options: SymbolOptions); addTo(map: Map, beforeId?: string): void; /** 替换 GeoJSON 图层的当前数据。 @param {GeoJSON} [data] GeoJSON object to set. If not provided, defaults to an empty FeatureCollection. */ setData(data: PointGeoJsonInput | PointGeoJsonInput[] | GeoJsonGeomertry | GeoPointLike | any): void; setSymbolPlacement(value: PropertyValueSpecificationEx<"point" | "line" | "line-center">): this; getSymbolPlacement(): PropertyValueSpecificationEx<"point" | "line" | "line-center">; setSymbolSpacing(value: PropertyValueSpecificationEx): this; getSymbolSpacing(): PropertyValueSpecificationEx; setSymbolAvoidEdges(value: PropertyValueSpecificationEx): this; getSymbolAvoidEdges(): PropertyValueSpecificationEx; setSymbolSortKey(value: DataDrivenPropertyValueSpecification): this; getSymbolSortKey(): DataDrivenPropertyValueSpecification; setSymbolZOrder(value: PropertyValueSpecificationEx<"auto" | "viewport-y" | "source">): this; getSymbolZOrder(): PropertyValueSpecificationEx<"auto" | "viewport-y" | "source">; setIconAllowOverlap(value: PropertyValueSpecificationEx): this; getIconAllowOverlap(): PropertyValueSpecificationEx; setIconIgnorePlacement(value: PropertyValueSpecificationEx): this; getIconIgnorePlacement(): PropertyValueSpecificationEx; setIconOptional(value: PropertyValueSpecificationEx): this; getIconOptional(): PropertyValueSpecificationEx; setIconRotationAlignment(value: PropertyValueSpecificationEx<"map" | "viewport" | "auto">): this; getIconRotationAlignment(): PropertyValueSpecificationEx<"map" | "viewport" | "auto">; setIconSize(value: DataDrivenPropertyValueSpecification): this; getIconSize(): DataDrivenPropertyValueSpecification; setIconTextFit(value: PropertyValueSpecificationEx<"none" | "width" | "height" | "both">): this; getIconTextFit(): PropertyValueSpecificationEx<"none" | "width" | "height" | "both">; setIconTextFitPadding(value: PropertyValueSpecificationEx<[number, number, number, number]>): this; getIconTextFitPadding(): PropertyValueSpecificationEx<[number, number, number, number]>; setIconImage(value: DataDrivenPropertyValueSpecification): this; getIconImage(): DataDrivenPropertyValueSpecification; setIconRotate(value: DataDrivenPropertyValueSpecification): this; getIconRotate(): DataDrivenPropertyValueSpecification; setIconPadding(value: PropertyValueSpecificationEx): this; getIconPadding(): PropertyValueSpecificationEx; setIconKeepUpright(value: PropertyValueSpecificationEx): this; getIconKeepUpright(): PropertyValueSpecificationEx; setIconOffset(value: DataDrivenPropertyValueSpecification<[number, number]>): this; getIconOffset(): DataDrivenPropertyValueSpecification<[number, number]>; setIconAnchor(value: DataDrivenPropertyValueSpecification<"center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right">): this; getIconAnchor(): DataDrivenPropertyValueSpecification<"center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right">; setIconPitchAlignment(value: PropertyValueSpecificationEx<"map" | "viewport" | "auto">): this; getIconPitchAlignment(): PropertyValueSpecificationEx<"map" | "viewport" | "auto">; setTextPitchAlignment(value: PropertyValueSpecificationEx<"map" | "viewport" | "auto">): this; getTextPitchAlignment(): PropertyValueSpecificationEx<"map" | "viewport" | "auto">; setTextRotationAlignment(value: PropertyValueSpecificationEx<"map" | "viewport" | "auto">): this; getTextRotationAlignment(): PropertyValueSpecificationEx<"map" | "viewport" | "auto">; setTextField(value: DataDrivenPropertyValueSpecification): this; getTextField(): DataDrivenPropertyValueSpecification; setTextFont(value: DataDrivenPropertyValueSpecification): this; getTextFont(): DataDrivenPropertyValueSpecification; setTextSize(value: DataDrivenPropertyValueSpecification): this; getTextSize(): DataDrivenPropertyValueSpecification; setTextMaxWidth(value: DataDrivenPropertyValueSpecification): this; getTextMaxWidth(): DataDrivenPropertyValueSpecification; setTextLineHeight(value: DataDrivenPropertyValueSpecification): this; getTextLineHeight(): DataDrivenPropertyValueSpecification; setTextLetterSpacing(value: DataDrivenPropertyValueSpecification): this; getTextLetterSpacing(): DataDrivenPropertyValueSpecification; setTextJustify(value: DataDrivenPropertyValueSpecification<"auto" | "left" | "center" | "right">): this; getTextJustify(): DataDrivenPropertyValueSpecification<"auto" | "left" | "center" | "right">; setTextRadialOffset(value: DataDrivenPropertyValueSpecification): this; getTextRadialOffset(): DataDrivenPropertyValueSpecification; setTextVariableAnchor(value: PropertyValueSpecificationEx>): this; getTextVariableAnchor(): PropertyValueSpecificationEx>; setTextAnchor(value: DataDrivenPropertyValueSpecification<"center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right">): this; getTextAnchor(): DataDrivenPropertyValueSpecification<"center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right">; setTextMaxAngle(value: PropertyValueSpecificationEx): this; getTextMaxAngle(): PropertyValueSpecificationEx; setTextWritingMode(value: PropertyValueSpecificationEx>): this; getTextWritingMode(): PropertyValueSpecificationEx>; setTextRotate(value: DataDrivenPropertyValueSpecification): this; getTextRotate(): DataDrivenPropertyValueSpecification; setTextPadding(value: PropertyValueSpecificationEx): this; getTextPadding(): PropertyValueSpecificationEx; setTextKeepUpright(value: PropertyValueSpecificationEx): this; getTextKeepUpright(): PropertyValueSpecificationEx; setTextTransform(value: DataDrivenPropertyValueSpecification<"none" | "uppercase" | "lowercase">): this; getTextTransform(): DataDrivenPropertyValueSpecification<"none" | "uppercase" | "lowercase">; setTextOffset(value: DataDrivenPropertyValueSpecification<[number, number]>): this; getTextOffset(): DataDrivenPropertyValueSpecification<[number, number]>; setTextAllowOverlap(value: PropertyValueSpecificationEx): this; getTextAllowOverlap(): PropertyValueSpecificationEx; setTextIgnorePlacement(value: PropertyValueSpecificationEx): this; getTextIgnorePlacement(): PropertyValueSpecificationEx; setTextOptional(value: PropertyValueSpecificationEx): this; getTextOptional(): PropertyValueSpecificationEx; setIconOpacity(value: DataDrivenPropertyValueSpecification): this; getIconOpacity(): DataDrivenPropertyValueSpecification; setIconColor(value: DataDrivenPropertyValueSpecification): this; getIconColor(): DataDrivenPropertyValueSpecification; setIconHaloColor(value: DataDrivenPropertyValueSpecification): this; getIconHaloColor(): DataDrivenPropertyValueSpecification; setIconHaloWidth(value: DataDrivenPropertyValueSpecification): this; getIconHaloWidth(): DataDrivenPropertyValueSpecification; setIconHaloBlur(value: DataDrivenPropertyValueSpecification): this; getIconHaloBlur(): DataDrivenPropertyValueSpecification; setIconTranslate(value: PropertyValueSpecificationEx<[number, number]>): this; getIconTranslate(): PropertyValueSpecificationEx<[number, number]>; setIconTranslateAnchor(value: PropertyValueSpecificationEx<"map" | "viewport">): this; getIconTranslateAnchor(): PropertyValueSpecificationEx<"map" | "viewport">; setTextOpacity(value: DataDrivenPropertyValueSpecification): this; getTextOpacity(): DataDrivenPropertyValueSpecification; setTextColor(value: DataDrivenPropertyValueSpecification): this; getTextColor(): DataDrivenPropertyValueSpecification; setTextHaloColor(value: DataDrivenPropertyValueSpecification): this; getTextHaloColor(): DataDrivenPropertyValueSpecification; setTextHaloWidth(value: DataDrivenPropertyValueSpecification): this; getTextHaloWidth(): DataDrivenPropertyValueSpecification; setTextHaloBlur(value: DataDrivenPropertyValueSpecification): this; getTextHaloBlur(): DataDrivenPropertyValueSpecification; setTextTranslate(value: PropertyValueSpecificationEx<[number, number]>): this; getTextTranslate(): PropertyValueSpecificationEx<[number, number]>; setTextTranslateAnchor(value: PropertyValueSpecificationEx<"map" | "viewport">): this; getTextTranslateAnchor(): PropertyValueSpecificationEx<"map" | "viewport">; } export class OverlayLayerBase { sourceId?: string; layerId?: string; _map?: Map; constructor(); addTo(map: Map, beforeId?: string): void; /** * 获取数据源ID * @return {string | undefined} */ getSourceId(): string | undefined; /** * 获取图层ID * @return {string | undefined} */ getLayerId(): string | undefined; /** * 获取数据源的数据 * @return {GeoJsonGeomertry | undefined} */ getData(): GeoJsonGeomertry | undefined; remove(): void; /** 每当鼠标悬停在这些图层上时,将地图的光标设置为“指针”。 @returns A function to remove the handler. * @param layerOrLayers */ hoverPointer(): void; /** 每当将鼠标悬停在这些图层中的某个特征上时,更新连接源 [s] 中特征的特征状态。 * @param enterCb * @param leaveCb */ hoverFeatureState(enterCb?: (arg0: {}) => void, leaveCb?: (arg0: {}) => void): void; /** 将鼠标悬停在这些图层中的某个要素上时,会显示一个弹出窗口。 @param htmlFunc Function that receives feature and popup, returns HTML. @param {Object} popupOptions Options passed to `Popup()` to customise popup. @example hoverPopup(f => `

${f.properties.Name}

${f.properties.Description}`, { anchor: 'left' }); */ hoverPopup(htmlFunc: any, popupOptions?: PopupOptions): any; /** 每当单击这些图层中的要素时都会显示一个弹出窗口。 @param htmlFunc Function that receives feature and popup, returns HTML. @param {Object} popupOptions Options passed to `Popup()` to customise popup. @returns A function that removes the handler. @example clickPopup(f => `

${f.properties.Name}

${f.properties.Description}`, { maxWidth: 500 }); */ clickPopup(htmlFunc: (arg0: {}) => void, popupOptions?: PopupOptions): any; /** 每当单击这些图层中的要素时都会触发回调。 @param {function} cb Callback that receives event with .features property @returns A function that removes the handler. */ clickLayer(cb: any): any; /** 当鼠标悬停在这些图层中的要素上时触发回调。 @returns A function to remove the handler. */ hoverLayer(cb: any): any; /** * 使给定的图层可见。 * @param layer */ show(): void; /** * 使给定的图层不可见。 * @param layer */ hide(): void; /** 根据参数使给定的图层隐藏或可见。 @param {boolean} state True for visible, false for hidden. */ toggle(state: boolean): boolean; /** 在一个或多个图层上设置绘制或布局属性。 @example setProperty('fillOpacity', 0.5) */ setProperty(prop: string | object, value?: any): void; /** 根据样式规范,获取给定图层 ID 的图层定义。 */ getLayerStyle(): LayerSpecification; /** * 设置图层样式 * @param layer * @param style */ setLayerStyle(style: any): void; /** 替换一个图层的过滤器。 @param {Array} filter New filter to set. @example setFilter(['==','level','0']]); */ setFilter(filter: FilterSpecification): void; } export interface OverlayLayerBaseOptions { sourceId?: string; sourceLayer?: string; layerId?: string; minzoom?: number; maxzoom?: number; filter?: any; visibility?: "visible" | "none"; isHoverPointer?: boolean; isHoverFeatureState?: boolean; } export type SymbolLayerSpecification = { id: string; type: "symbol"; metadata?: unknown; source: string; "source-layer"?: string; minzoom?: number; maxzoom?: number; filter?: FilterSpecification; layout?: { "symbol-placement"?: PropertyValueSpecificationEx<"point" | "line" | "line-center">; "symbol-spacing"?: PropertyValueSpecificationEx; "symbol-avoid-edges"?: PropertyValueSpecificationEx; "symbol-sort-key"?: DataDrivenPropertyValueSpecification; "symbol-z-order"?: PropertyValueSpecificationEx<"auto" | "viewport-y" | "source">; "icon-allow-overlap"?: PropertyValueSpecificationEx; "icon-ignore-placement"?: PropertyValueSpecificationEx; "icon-optional"?: PropertyValueSpecificationEx; "icon-rotation-alignment"?: PropertyValueSpecificationEx<"map" | "viewport" | "auto">; "icon-size"?: DataDrivenPropertyValueSpecification; "icon-text-fit"?: PropertyValueSpecificationEx<"none" | "width" | "height" | "both">; "icon-text-fit-padding"?: PropertyValueSpecificationEx<[number, number, number, number]>; "icon-image"?: DataDrivenPropertyValueSpecification; "icon-rotate"?: DataDrivenPropertyValueSpecification; "icon-padding"?: PropertyValueSpecificationEx; "icon-keep-upright"?: PropertyValueSpecificationEx; "icon-offset"?: DataDrivenPropertyValueSpecification<[number, number]>; "icon-anchor"?: DataDrivenPropertyValueSpecification<"center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right">; "icon-pitch-alignment"?: PropertyValueSpecificationEx<"map" | "viewport" | "auto">; "text-pitch-alignment"?: PropertyValueSpecificationEx<"map" | "viewport" | "auto">; "text-rotation-alignment"?: PropertyValueSpecificationEx<"map" | "viewport" | "auto">; "text-field"?: DataDrivenPropertyValueSpecification; "text-font"?: DataDrivenPropertyValueSpecification>; "text-size"?: DataDrivenPropertyValueSpecification; "text-max-width"?: DataDrivenPropertyValueSpecification; "text-line-height"?: DataDrivenPropertyValueSpecification; "text-letter-spacing"?: DataDrivenPropertyValueSpecification; "text-justify"?: DataDrivenPropertyValueSpecification<"auto" | "left" | "center" | "right">; "text-radial-offset"?: DataDrivenPropertyValueSpecification; "text-variable-anchor"?: PropertyValueSpecificationEx>; "text-anchor"?: DataDrivenPropertyValueSpecification<"center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right">; "text-max-angle"?: PropertyValueSpecificationEx; "text-writing-mode"?: PropertyValueSpecificationEx>; "text-rotate"?: DataDrivenPropertyValueSpecification; "text-padding"?: PropertyValueSpecificationEx; "text-keep-upright"?: PropertyValueSpecificationEx; "text-transform"?: DataDrivenPropertyValueSpecification<"none" | "uppercase" | "lowercase">; "text-offset"?: DataDrivenPropertyValueSpecification<[number, number]>; "text-allow-overlap"?: PropertyValueSpecificationEx; "text-ignore-placement"?: PropertyValueSpecificationEx; "text-optional"?: PropertyValueSpecificationEx; visibility?: "visible" | "none"; }; paint?: { "icon-opacity"?: DataDrivenPropertyValueSpecification; "icon-color"?: DataDrivenPropertyValueSpecification; "icon-halo-color"?: DataDrivenPropertyValueSpecification; "icon-halo-width"?: DataDrivenPropertyValueSpecification; "icon-halo-blur"?: DataDrivenPropertyValueSpecification; "icon-translate"?: PropertyValueSpecificationEx<[number, number]>; "icon-translate-anchor"?: PropertyValueSpecificationEx<"map" | "viewport">; "text-opacity"?: DataDrivenPropertyValueSpecification; "text-color"?: DataDrivenPropertyValueSpecification; "text-halo-color"?: DataDrivenPropertyValueSpecification; "text-halo-width"?: DataDrivenPropertyValueSpecification; "text-halo-blur"?: DataDrivenPropertyValueSpecification; "text-translate"?: PropertyValueSpecificationEx<[number, number]>; "text-translate-anchor"?: PropertyValueSpecificationEx<"map" | "viewport">; }; }; export type SymbolLayerStyleProp = { metadata?: unknown; source?: string; sourceLayer?: string; minzoom?: number; maxzoom?: number; filter?: FilterSpecification; symbolPlacement?: PropertyValueSpecificationEx<"point" | "line" | "line-center">; symbolSpacing?: PropertyValueSpecificationEx; symbolAvoidEdges?: PropertyValueSpecificationEx; symbolSortKey?: DataDrivenPropertyValueSpecification; symbolZOrder?: PropertyValueSpecificationEx<"auto" | "viewport-y" | "source">; iconAllowOverlap?: PropertyValueSpecificationEx; iconIgnorePlacement?: PropertyValueSpecificationEx; iconOptional?: PropertyValueSpecificationEx; iconRotationAlignment?: PropertyValueSpecificationEx<"map" | "viewport" | "auto">; iconSize?: DataDrivenPropertyValueSpecification; iconTextFit?: PropertyValueSpecificationEx<"none" | "width" | "height" | "both">; iconTextFitPadding?: PropertyValueSpecificationEx<[number, number, number, number]>; iconImage?: DataDrivenPropertyValueSpecification; iconRotate?: DataDrivenPropertyValueSpecification; iconPadding?: PropertyValueSpecificationEx; iconKeepUpright?: PropertyValueSpecificationEx; iconOffset?: DataDrivenPropertyValueSpecification<[number, number]>; iconAnchor?: DataDrivenPropertyValueSpecification<"center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right">; iconPitchAlignment?: PropertyValueSpecificationEx<"map" | "viewport" | "auto">; textPitchAlignment?: PropertyValueSpecificationEx<"map" | "viewport" | "auto">; textRotationAlignment?: PropertyValueSpecificationEx<"map" | "viewport" | "auto">; textField?: DataDrivenPropertyValueSpecification; textFont?: DataDrivenPropertyValueSpecification>; textSize?: DataDrivenPropertyValueSpecification; textMaxWidth?: DataDrivenPropertyValueSpecification; textLineHeight?: DataDrivenPropertyValueSpecification; textLetterSpacing?: DataDrivenPropertyValueSpecification; textJustify?: DataDrivenPropertyValueSpecification<"auto" | "left" | "center" | "right">; textRadialOffset?: DataDrivenPropertyValueSpecification; textVariableAnchor?: PropertyValueSpecificationEx>; textAnchor?: DataDrivenPropertyValueSpecification<"center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right">; textMaxAngle?: PropertyValueSpecificationEx; textWritingMode?: PropertyValueSpecificationEx>; textRotate?: DataDrivenPropertyValueSpecification; textPadding?: PropertyValueSpecificationEx; textKeepUpright?: PropertyValueSpecificationEx; textTransform?: DataDrivenPropertyValueSpecification<"none" | "uppercase" | "lowercase">; textOffset?: DataDrivenPropertyValueSpecification<[number, number]>; textAllowOverlap?: PropertyValueSpecificationEx; textIgnorePlacement?: PropertyValueSpecificationEx; textOptional?: PropertyValueSpecificationEx; visibility?: "visible" | "none"; iconOpacity?: DataDrivenPropertyValueSpecification; iconColor?: DataDrivenPropertyValueSpecification; iconHaloColor?: DataDrivenPropertyValueSpecification; iconHaloWidth?: DataDrivenPropertyValueSpecification; iconHaloBlur?: DataDrivenPropertyValueSpecification; iconTranslate?: PropertyValueSpecificationEx<[number, number]>; iconTranslateAnchor?: PropertyValueSpecificationEx<"map" | "viewport">; textOpacity?: DataDrivenPropertyValueSpecification; textColor?: DataDrivenPropertyValueSpecification; textHaloColor?: DataDrivenPropertyValueSpecification; textHaloWidth?: DataDrivenPropertyValueSpecification; textHaloBlur?: DataDrivenPropertyValueSpecification; textTranslate?: PropertyValueSpecificationEx<[number, number]>; textTranslateAnchor?: PropertyValueSpecificationEx<"map" | "viewport">; }; export interface SymbolOptions extends OverlayLayerBaseOptions { data: PointGeoJsonInput | PointGeoJsonInput[] | GeoJsonGeomertry | GeoPointLike | any; symbolPlacement?: PropertyValueSpecificationEx<"point" | "line" | "line-center">; symbolSpacing?: PropertyValueSpecificationEx; symbolAvoidEdges?: PropertyValueSpecificationEx; symbolSortKey?: DataDrivenPropertyValueSpecification; symbolZOrder?: PropertyValueSpecificationEx<"auto" | "viewport-y" | "source">; iconAllowOverlap?: PropertyValueSpecificationEx; iconIgnorePlacement?: PropertyValueSpecificationEx; iconOptional?: PropertyValueSpecificationEx; iconRotationAlignment?: PropertyValueSpecificationEx<"map" | "viewport" | "auto">; iconSize?: DataDrivenPropertyValueSpecification; iconTextFit?: PropertyValueSpecificationEx<"none" | "width" | "height" | "both">; iconTextFitPadding?: PropertyValueSpecificationEx<[number, number, number, number]>; iconImage?: DataDrivenPropertyValueSpecification; iconRotate?: DataDrivenPropertyValueSpecification; iconPadding?: PropertyValueSpecificationEx; iconKeepUpright?: PropertyValueSpecificationEx; iconOffset?: DataDrivenPropertyValueSpecification<[number, number]>; iconAnchor?: DataDrivenPropertyValueSpecification<"center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right">; iconPitchAlignment?: PropertyValueSpecificationEx<"map" | "viewport" | "auto">; textPitchAlignment?: PropertyValueSpecificationEx<"map" | "viewport" | "auto">; textRotationAlignment?: PropertyValueSpecificationEx<"map" | "viewport" | "auto">; textField?: DataDrivenPropertyValueSpecification; textFont?: DataDrivenPropertyValueSpecification; textSize?: DataDrivenPropertyValueSpecification; textMaxWidth?: DataDrivenPropertyValueSpecification; textLineHeight?: DataDrivenPropertyValueSpecification; textLetterSpacing?: DataDrivenPropertyValueSpecification; textJustify?: DataDrivenPropertyValueSpecification<"auto" | "left" | "center" | "right">; textRadialOffset?: DataDrivenPropertyValueSpecification; textVariableAnchor?: PropertyValueSpecificationEx>; textAnchor?: DataDrivenPropertyValueSpecification<"center" | "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right">; textMaxAngle?: PropertyValueSpecificationEx; textWritingMode?: PropertyValueSpecificationEx>; textRotate?: DataDrivenPropertyValueSpecification; textPadding?: PropertyValueSpecificationEx; textKeepUpright?: PropertyValueSpecificationEx; textTransform?: DataDrivenPropertyValueSpecification<"none" | "uppercase" | "lowercase">; textOffset?: DataDrivenPropertyValueSpecification<[number, number]>; textAllowOverlap?: PropertyValueSpecificationEx; textIgnorePlacement?: PropertyValueSpecificationEx; textOptional?: PropertyValueSpecificationEx; iconOpacity?: DataDrivenPropertyValueSpecification; iconColor?: DataDrivenPropertyValueSpecification; iconHaloColor?: DataDrivenPropertyValueSpecification; iconHaloWidth?: DataDrivenPropertyValueSpecification; iconHaloBlur?: DataDrivenPropertyValueSpecification; iconTranslate?: PropertyValueSpecificationEx<[number, number]>; iconTranslateAnchor?: PropertyValueSpecificationEx<"map" | "viewport">; textOpacity?: DataDrivenPropertyValueSpecification; textColor?: DataDrivenPropertyValueSpecification; textHaloColor?: DataDrivenPropertyValueSpecification; textHaloWidth?: DataDrivenPropertyValueSpecification; textHaloBlur?: DataDrivenPropertyValueSpecification; textTranslate?: PropertyValueSpecificationEx<[number, number]>; textTranslateAnchor?: PropertyValueSpecificationEx<"map" | "viewport">; } ``` --- # 圆符号 ## 用法 ```js let data = { "type": "FeatureCollection", "features": [ { "id": "1", "type": "Feature", "properties": { "name": "circle1", "color": "#00ffff" }, "geometry": { "coordinates": [ 6971.5378667085715, 14385.014943907285 ], "type": "Point" } }, { "id": "2", "type": "Feature", "properties": { "name": "circle2", "color": "#ff00ff" }, "geometry": { "coordinates": [ 21789.770621477073, 14037.169104593304 ], "type": "Point" } } ] } let circle = new vjmap.Circle({ data: map.toLngLat(data), // CAD几何坐标转渲染经纬度 // 图标名来源于geojson属性中的icon字段 circleColor: ['case', ["boolean", ["feature-state", "hover"], false], '#ff0000', ['get', 'color']], circleRadius: 5, isHoverPointer: true, isHoverFeatureState: true }); circle.addTo(map); circle.clickLayer(e => map.logInfo(`您点击了第 ${e.features[0].id} 个,名称为 ${e.features[0].properties.name},颜色为 ${e.features[0].properties.color} `)) circle.hoverPopup(f => `

ID: ${f.properties.name}

Color: ${f.properties.color}`, { anchor: 'bottom' }); ```
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: "sys_hello", // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapLightStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    showError(res.error)
    return;
}
// 获取地图范围
let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapBounds);
//
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapBounds.center()), // 设置地图中心点
    zoom: 1, // 设置地图缩放级别
    pitch: 0,
    antialias: true,
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();
let data = {
    "type": "FeatureCollection",
    "features": [
        {
            "id": "1",
            "type": "Feature",
            "properties": {
                "name": "circle1",
                "color": "#00ffff"
            },
            "geometry": {
                "coordinates": [
                    6971.5378667085715,
                    14385.014943907285
                ],
                "type": "Point"
            }
        },
        {
            "id": "2",
            "type": "Feature",
            "properties": {
                "name": "circle2",
                "color": "#ff00ff"
            },
            "geometry": {
                "coordinates": [
                    21789.770621477073,
                    14037.169104593304
                ],
                "type": "Point"
            }
        }
    ]
}
let circle = new vjmap.Circle({
    data: map.toLngLat(data), // CAD几何坐标转渲染经纬度
    // 图标名来源于geojson属性中的icon字段
    circleColor: ['case', ["boolean", ["feature-state", "hover"], false], '#ff0000', ['get', 'color']],
    circleRadius: 10,
    isHoverPointer: true,
    isHoverFeatureState: true
});
circle.addTo(map);
// 点击事件
circle.clickLayer(e => map.logInfo(`您点击了第 ${e.features[0].id} 个,名称为 ${e.features[0].properties.name},颜色为 ${e.features[0].properties.color} `))
// hover事件
circle.hoverPopup(f => `<h3>ID: ${f.properties.name}</h3>Color: ${f.properties.color}`, { anchor: 'bottom' });


也可以用先单独创建数据源,再创建圆图层的写法,如 ```js map.addSource("circleSources", { type: "geojson", data: data }); map.addLayer({ id: 'circleLayer', source: 'circleSources', type: 'symbol', "layout": {}, "paint": { 'circle-color': ['case', ["boolean", ["feature-state", "hover"], false], '#ff0000', ['get', 'color']], 'circle-radius': 10 } }) ``` 或 属性用驼峰式大小写,不用区分layout还是paint属性 ```js map.addGeoJSONSource("circleSources", data); map.addCircleLayer("circleLayer", "circleSources", { circleColor: ['case', ["boolean", ["feature-state", "hover"], false], '#ff0000', ['get', 'color']], circleRadius: 10 }) ``` 这种通过创建数据源和图层的写法,响应事件时,需要监听`地图的图层事件`进行处理 ```js map.on("click", layerId, e => {}) ``` ## 常用方法 修改数据用 `setData` ```js circle.setData(newDataJson); ``` 获取图层ID、数据源ID ```js // 获取图层ID circle.getLayerId() // 获取数据源ID circle.getSourceId() ``` 显示或隐藏 ```js // 显示 circle.show() // 隐藏 circle.hide() ``` 删除 ```js // 删除 circle.remove() ``` ## 类型定义 ```ts /** * 创建圆符号图层. * **/ export class Circle extends OverlayLayerBase { options: CircleOptions; constructor(options: CircleOptions); addTo(map: Map, beforeId?: string): void; /** 替换 GeoJSON 图层的当前数据。 @param {GeoJSON} [data] GeoJSON object to set. If not provided, defaults to an empty FeatureCollection. */ setData(data: PointGeoJsonInput | PointGeoJsonInput[] | GeoJsonGeomertry | GeoPointLike | any): void; setCircleSortKey(value: DataDrivenPropertyValueSpecification): this; getCircleSortKey(): DataDrivenPropertyValueSpecification; setVisibility(value: "visible" | "none"): this; getVisibility(): "visible" | "none"; setCircleRadius(value: DataDrivenPropertyValueSpecification): this; getCircleRadius(): DataDrivenPropertyValueSpecification; setCircleColor(value: DataDrivenPropertyValueSpecification): this; getCircleColor(): DataDrivenPropertyValueSpecification; setCircleBlur(value: DataDrivenPropertyValueSpecification): this; getCircleBlur(): DataDrivenPropertyValueSpecification; setCircleOpacity(value: DataDrivenPropertyValueSpecification): this; getCircleOpacity(): DataDrivenPropertyValueSpecification; setCircleTranslate(value: PropertyValueSpecificationEx<[number, number]>): this; getCircleTranslate(): PropertyValueSpecificationEx<[number, number]>; setCircleTranslateAnchor(value: PropertyValueSpecificationEx<"map" | "viewport">): this; getCircleTranslateAnchor(): PropertyValueSpecificationEx<"map" | "viewport">; setCirclePitchScale(value: PropertyValueSpecificationEx<"map" | "viewport">): this; getCirclePitchScale(): PropertyValueSpecificationEx<"map" | "viewport">; setCirclePitchAlignment(value: PropertyValueSpecificationEx<"map" | "viewport">): this; getCirclePitchAlignment(): PropertyValueSpecificationEx<"map" | "viewport">; setCircleStrokeWidth(value: DataDrivenPropertyValueSpecification): this; getCircleStrokeWidth(): DataDrivenPropertyValueSpecification; setCircleStrokeColor(value: DataDrivenPropertyValueSpecification): this; getCircleStrokeColor(): DataDrivenPropertyValueSpecification; setCircleStrokeOpacity(value: DataDrivenPropertyValueSpecification): this; getCircleStrokeOpacity(): DataDrivenPropertyValueSpecification; } export class OverlayLayerBase { sourceId?: string; layerId?: string; _map?: Map; constructor(); addTo(map: Map, beforeId?: string): void; /** * 获取数据源ID * @return {string | undefined} */ getSourceId(): string | undefined; /** * 获取图层ID * @return {string | undefined} */ getLayerId(): string | undefined; /** * 获取数据源的数据 * @return {GeoJsonGeomertry | undefined} */ getData(): GeoJsonGeomertry | undefined; remove(): void; /** 每当鼠标悬停在这些图层上时,将地图的光标设置为“指针”。 @returns A function to remove the handler. * @param layerOrLayers */ hoverPointer(): void; /** 每当将鼠标悬停在这些图层中的某个特征上时,更新连接源 [s] 中特征的特征状态。 * @param enterCb * @param leaveCb */ hoverFeatureState(enterCb?: (arg0: {}) => void, leaveCb?: (arg0: {}) => void): void; /** 将鼠标悬停在这些图层中的某个要素上时,会显示一个弹出窗口。 @param htmlFunc Function that receives feature and popup, returns HTML. @param {Object} popupOptions Options passed to `Popup()` to customise popup. @example hoverPopup(f => `

${f.properties.Name}

${f.properties.Description}`, { anchor: 'left' }); */ hoverPopup(htmlFunc: any, popupOptions?: PopupOptions): any; /** 每当单击这些图层中的要素时都会显示一个弹出窗口。 @param htmlFunc Function that receives feature and popup, returns HTML. @param {Object} popupOptions Options passed to `Popup()` to customise popup. @returns A function that removes the handler. @example clickPopup(f => `

${f.properties.Name}

${f.properties.Description}`, { maxWidth: 500 }); */ clickPopup(htmlFunc: (arg0: {}) => void, popupOptions?: PopupOptions): any; /** 每当单击这些图层中的要素时都会触发回调。 @param {function} cb Callback that receives event with .features property @returns A function that removes the handler. */ clickLayer(cb: any): any; /** 当鼠标悬停在这些图层中的要素上时触发回调。 @returns A function to remove the handler. */ hoverLayer(cb: any): any; /** * 使给定的图层可见。 * @param layer */ show(): void; /** * 使给定的图层不可见。 * @param layer */ hide(): void; /** 根据参数使给定的图层隐藏或可见。 @param {boolean} state True for visible, false for hidden. */ toggle(state: boolean): boolean; /** 在一个或多个图层上设置绘制或布局属性。 @example setProperty('fillOpacity', 0.5) */ setProperty(prop: string | object, value?: any): void; /** 根据样式规范,获取给定图层 ID 的图层定义。 */ getLayerStyle(): LayerSpecification; /** * 设置图层样式 * @param layer * @param style */ setLayerStyle(style: any): void; /** 替换一个图层的过滤器。 @param {Array} filter New filter to set. @example setFilter(['==','level','0']]); */ setFilter(filter: FilterSpecification): void; } export interface OverlayLayerBaseOptions { sourceId?: string; sourceLayer?: string; layerId?: string; minzoom?: number; maxzoom?: number; filter?: any; visibility?: "visible" | "none"; isHoverPointer?: boolean; isHoverFeatureState?: boolean; } export type CircleLayerSpecification = { id: string; type: "circle"; metadata?: unknown; source: string; "source-layer"?: string; minzoom?: number; maxzoom?: number; filter?: FilterSpecification; layout?: { "circle-sort-key"?: DataDrivenPropertyValueSpecification; visibility?: "visible" | "none"; }; paint?: { "circle-radius"?: DataDrivenPropertyValueSpecification; "circle-color"?: DataDrivenPropertyValueSpecification; "circle-blur"?: DataDrivenPropertyValueSpecification; "circle-opacity"?: DataDrivenPropertyValueSpecification; "circle-translate"?: PropertyValueSpecificationEx<[number, number]>; "circle-translate-anchor"?: PropertyValueSpecificationEx<"map" | "viewport">; "circle-pitch-scale"?: PropertyValueSpecificationEx<"map" | "viewport">; "circle-pitch-alignment"?: PropertyValueSpecificationEx<"map" | "viewport">; "circle-stroke-width"?: DataDrivenPropertyValueSpecification; "circle-stroke-color"?: DataDrivenPropertyValueSpecification; "circle-stroke-opacity"?: DataDrivenPropertyValueSpecification; }; }; export type CircleLayerStyleProp = { metadata?: unknown; source?: string; sourceLayer?: string; minzoom?: number; maxzoom?: number; filter?: FilterSpecification; circleSortKey?: DataDrivenPropertyValueSpecification; visibility?: "visible" | "none"; circleRadius?: DataDrivenPropertyValueSpecification; circleColor?: DataDrivenPropertyValueSpecification; circleBlur?: DataDrivenPropertyValueSpecification; circleOpacity?: DataDrivenPropertyValueSpecification; circleTranslate?: PropertyValueSpecificationEx<[number, number]>; circleTranslateAnchor?: PropertyValueSpecificationEx<"map" | "viewport">; circlePitchScale?: PropertyValueSpecificationEx<"map" | "viewport">; circlePitchAlignment?: PropertyValueSpecificationEx<"map" | "viewport">; circleStrokeWidth?: DataDrivenPropertyValueSpecification; circleStrokeColor?: DataDrivenPropertyValueSpecification; circleStrokeOpacity?: DataDrivenPropertyValueSpecification; }; export interface CircleOptions extends OverlayLayerBaseOptions { data: PointGeoJsonInput | PointGeoJsonInput[] | GeoJsonGeomertry | GeoPointLike | any; circleSortKey?: DataDrivenPropertyValueSpecification; circleRadius?: DataDrivenPropertyValueSpecification; circleColor?: DataDrivenPropertyValueSpecification; circleBlur?: DataDrivenPropertyValueSpecification; circleOpacity?: DataDrivenPropertyValueSpecification; circleTranslate?: PropertyValueSpecificationEx<[number, number]>; circleTranslateAnchor?: PropertyValueSpecificationEx<"map" | "viewport">; circlePitchScale?: PropertyValueSpecificationEx<"map" | "viewport">; circlePitchAlignment?: PropertyValueSpecificationEx<"map" | "viewport">; circleStrokeWidth?: DataDrivenPropertyValueSpecification; circleStrokeColor?: DataDrivenPropertyValueSpecification; circleStrokeOpacity?: DataDrivenPropertyValueSpecification; } ``` --- # 圆(弧)椭圆曲线 ## 圆弧填充 [在线示例](https://vjmap.com/demo/#/demo/map/overlay/circle/circleArcFill) 用法: ```js const mapBounds = map.getGeoBounds(0.2); const center = mapBounds.center(); const radius = mapBounds.width() / 4; let circle = new vjmap.CircleFill({ center: center, radius: radius, startAngle: 120, endAngle: 60, fillColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', 'yellow'], fillOpacity: 0.8, fillOutlineColor: "#f00", isHoverPointer: true, isHoverFeatureState: true }); circle.addTo(map); ```
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: "sys_hello", // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapLightStyle() // div为深色背景颜色时,这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    showError(res.error)
    return;
}
// 获取地图范围
let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapBounds);
//
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式,这里是栅格样式
    center: prj.toLngLat(mapBounds.center()), // 设置地图中心点
    zoom: 1, // 设置地图缩放级别
    pitch: 0,
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();
mapBounds = map.getGeoBounds(0.2);
const center = mapBounds.center();
const radius = mapBounds.width() / 4;
let circle = new vjmap.CircleFill({
    center: center,
    radius: radius,
    startAngle: 120,
    endAngle: 60,
    fillColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', 'yellow'],
    fillOpacity: 0.8,
    fillOutlineColor: "#f00",
    isHoverPointer: true,
    isHoverFeatureState: true
});
circle.addTo(map);


## 圆弧路径 [在线示例](https://vjmap.com/demo/#/demo/map/overlay/circle/circleArcPath) 用法: ```js const cirleArcPath = vjmap.getCirclePolygonCoordinates(mapExtent.center(), mapExtent.width() / 5, 360, 120, 60, false); let path = new vjmap.Polyline({ data: map.toLngLat(cirleArcPath), lineColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', '#FFA0FD'], lineWidth: 3, isHoverPointer: true, isHoverFeatureState: true }); path.addTo(map); ``` ## 圆填充 [在线示例](https://vjmap.com/demo/#/demo/map/overlay/circle/circleFill) 用法: ```js const mapBounds = map.getGeoBounds(0.2); const center = mapBounds.center(); const radius = mapBounds.width() / 4; let circle = new vjmap.CircleFill({ center: center, radius: radius, fillColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', 'yellow'], fillOpacity: 0.8, fillOutlineColor: "#f00", isHoverPointer: true, isHoverFeatureState: true }); circle.addTo(map); ``` ## 圆路径 [在线示例](https://vjmap.com/demo/#/demo/map/overlay/circle/circlePath) 用法: ```js const cirlePath = vjmap.getCirclePolygonCoordinates(mapExtent.center(), mapExtent.width() / 10, 120, 0, 360, false); let path = new vjmap.Polyline({ data: map.toLngLat(cirlePath), lineColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', '#FFA0FD'], lineWidth: 3, isHoverPointer: true, isHoverFeatureState: true }); path.addTo(map); ``` ## 椭圆弧填充 [在线示例](https://vjmap.com/demo/#/demo/map/overlay/ellipse/ellipseArcFill) 用法: ```js const mapBounds = map.getGeoBounds(0.6); const center = mapBounds.center(); const majorAxis = mapBounds.width() / 4; const minorAxis = mapBounds.width() / 6; let ellipseArcFill = new vjmap.EllipseFill({ center: center, majorAxisRadius: majorAxis, minorAxisRadius: minorAxis, startAngle: 60, endAngle: 300, fillColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', 'yellow'], fillOpacity: 0.8, fillOutlineColor: "#f00", isHoverPointer: true, isHoverFeatureState: true }); ellipseArcFill.addTo(map); ``` ## 椭圆弧路径 [在线示例](https://vjmap.com/demo/#/demo/map/overlay/ellipse/ellipseArcPath) 用法: ```js const majorAxisRadius = mapExtent.width() / 5; const minorAxisRadius = mapExtent.width() / 10; const ellipseArcPath = vjmap.getEllipsePolygonCoordinates(mapExtent.center(), majorAxisRadius, minorAxisRadius, 360, 100, 300, false); let path = new vjmap.Polyline({ data: map.toLngLat(ellipseArcPath), lineColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', '#FFA0FD'], lineWidth: 3, isHoverPointer: true, isHoverFeatureState: true }); path.addTo(map); ``` ## 椭圆填充 [在线示例](https://vjmap.com/demo/#/demo/map/overlay/ellipse/ellipseFill) 用法: ```js const mapBounds = map.getGeoBounds(0.6); const center = mapBounds.center(); const majorAxis = mapBounds.width() / 4; const minorAxis = mapBounds.width() / 6; let ellipse = new vjmap.EllipseFill({ center: center, majorAxisRadius: majorAxis, minorAxisRadius: minorAxis, fillColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', 'yellow'], fillOpacity: 0.8, fillOutlineColor: "#f00", isHoverPointer: true, isHoverFeatureState: true }); ellipse.addTo(map); ``` ## 椭圆路径 [在线示例](https://vjmap.com/demo/#/demo/map/overlay/ellipse/ellipsePath) 用法: ```js const majorAxisRadius = mapExtent.width() / 4; const minorAxisRadius = mapExtent.width() / 6; const ellipsePath = vjmap.getEllipsePolygonCoordinates(mapExtent.center(), majorAxisRadius, minorAxisRadius, 360, 0, 360, false); let path = new vjmap.Polyline({ data: map.toLngLat(ellipsePath), lineColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', '#FFA0FD'], lineWidth: 3, isHoverPointer: true, isHoverFeatureState: true }); path.addTo(map); ``` ## 贝塞尔曲线路径 [在线示例](https://vjmap.com/demo/#/demo/map/overlay/curve/curvePath) 用法: ```js const mapBounds = map.getGeoBounds(0.4); const curvePoints = []; curvePoints.push(mapBounds.min.toArray()); curvePoints.push(mapBounds.center().toArray()); curvePoints.push(vjmap.geoPoint([mapBounds.max.x, mapBounds.min.y]).toArray()); curvePoints.push(mapBounds.max.toArray()); // 把曲线上的点转为贝塞尔曲线参数 const c = vjmap.polylineToBezierCurve(curvePoints); // 据贝塞尔曲线参数离散成线 const curvePath = vjmap.bezierCurveToPolyline(c); let path = new vjmap.Polyline({ data: map.toLngLat(curvePath), lineColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', '#FFA0FD'], lineWidth: 3, isHoverPointer: true, isHoverFeatureState: true }); path.addTo(map); ``` ## 贝塞尔曲线填充 [在线示例](https://vjmap.com/demo/#/demo/map/overlay/curve/curvePathFill) 用法: ```js const mapBounds = map.getGeoBounds(0.2); const curvePoints = []; curvePoints.push(mapBounds.min.toArray()); curvePoints.push(mapBounds.center().toArray()); curvePoints.push(vjmap.geoPoint([mapBounds.max.x, mapBounds.min.y]).toArray()); curvePoints.push(mapBounds.max.toArray()); curvePoints.push(vjmap.geoPoint([mapBounds.min.x, mapBounds.max.y]).toArray()); curvePoints.push(mapBounds.min.toArray()); // 把曲线上的点转为贝塞尔曲线参数 const c = vjmap.polylineToBezierCurve(curvePoints); // 据贝塞尔曲线参数离散成线 const curvePath = vjmap.bezierCurveToPolyline(c); let polygon = new vjmap.Polygon({ data: map.toLngLat(curvePath), fillColor: ['case', ['to-boolean', ['feature-state', 'hover']], 'red', 'yellow'], fillOpacity: 0.8, fillOutlineColor: "#f00", isHoverPointer: true, isHoverFeatureState: true }); polygon.addTo(map); ``` --- # 图形绘制工具 ## 开始 ```js const draw = new vjmap.Draw.Tool(); map.addControl(draw, 'top-right'); ``` ## 选项 以下所有选项都是可选的。 - `keybindings`, boolean (default `true`): 是否启用用于绘图的键盘交互。 - `touchEnabled`, boolean (default `true`): 是否为绘图启用触摸交互。 - `boxSelect`,boolean(缺省`true`):是否启用与功能框选择`shift`\+ `click`+阻力。如果`false`,`shift`\+ `click`+drag 放大区域。 - `clickBuffer`, number (default: `2`): 任何特征或顶点(在每个方向)周围的像素数,这些像素会响应点击。 - `touchBuffer`, number (default: `25`): 顶点的任何特征(在每个方向)周围的像素数,这些特征将响应触摸。 - `controls`, Object:隐藏或显示单个控件。每个属性的名称是一个控件,值是一个布尔值,指示控件是打开还是关闭。可用的控制名称`point`,`line_string`,`polygon`,`trash`,`combine_features`,`uncombine_features`,`drawCircle`,`snap_mode_snap`,`snap_mode_grid`,`splitLine`,`cutPolygon`,`drawRectangle`,`redo`,`undo`,`snap_mode_snap`,`snap_mode_grid`。默认情况下,所有控件都打开。要更改该默认值,请使用`displayControlsDefault`. - `displayControlsDefault`, boolean (default: `true`): 的默认值`controls`。例如,如果您希望所有控件默认_关闭_,并使用 指定允许列表`controls`,请使用`displayControlsDefault: false`。 - `styles`, Array:地图样式对象的数组。默认情况下,Draw 为您提供了一种地图样式。 - `modes`, Object:用您自己的方式覆盖默认模式。 - `defaultMode`, String (default: `'simple_select'`):`modes`用户将首先登陆的模式(来自)。 - `snap`, boolean(默认值:)`true`:是否启用捕捉节点 - `guides`, boolean(默认值:)`true`:是否启用捕捉网格 - `snapOptions` Object, 捕捉选项,缺省为({snapPx: 15, snapToMidPoints: false, snapVertexPriorityDistance: 1.25, snapGridPx: 2000(捕捉网格线像素长度)) - `addControls` Array:要增加的控件 - `api` Object api方法,可通过属性`getSnapFeatures`设置额外捕捉的实体 - `isActionDrawMode` 设置为true的时候,按钮工具栏将不会创建,只能通过命令来调用 ::: tip 提示 绘制时,按往 Alt 键,将临时取消捕捉模式 绘制时,按往 Ctrl 键,将临时启用正交模式,只能绘制相对视口的水平或垂直线 ::: 捕捉样式图例说明
## 模式 模式名称字符串可作为枚举使用`Draw.modes`。 ### `static` 非编辑模式,是非编辑模式下,用户不能编辑图形,可以高亮或点击图形。 需要在非编辑模式下高亮实体,需给实体设置`hoverPointer`属性为`true`,如果要自定义高亮的透明度,设置`hover_opacity`属性。默认高亮透明度为`0.55` 非编辑模式下面的事件有 - draw.static.setup 开始 - draw.static.click 点击 - draw.static.mousedown - draw.static.mouseup - draw.static.mouseout - draw.static.keyup - draw.static.keydown - draw.static.mouseenter - draw.static.mouseleave - draw.static.contextmenu - draw.static.tap - draw.static.stop 结束 例如可以响应上面事件,在非编辑模式下面提示信息框 ```js // 创建完后修改颜色 map.on("draw.create", function (e) { let color = vjmap.randomColor(); for(let i = 0; i < e.features.length; i++) { let id = e.features[i].id; if (!draw.get(id)) continue; draw.setFeatureProperty(id, "color", color); draw.setFeatureProperty(id, "hoverPointer", true); // 允许非编辑模式下高亮 } }); const popup = new vjmap.Popup({ closeButton: false }); // 非编辑模式下,鼠标悬浮时,显示信息提示内容 map.on("draw.static.mouseenter", e => { if (e.event.featureTarget) { popup.setLngLat(e.event.lngLat); popup.setHTML(JSON.stringify(e.event.featureTarget.properties, null, 4)); popup.addTo(map); } }); map.on("draw.static.mouseleave", e => { if (popup) popup.remove(); }); map.on("draw.static.click", e => { alert(`您点击了id为:${e.event.featureTarget.properties.id} 的实体`) }); ``` ### `simple_select` `Draw.modes.SIMPLE_SELECT === 'simple_select'` 用于选择、删除和拖动要素。 在此模式下,用户可以更改功能的选定状态。 绘图`simple_select`默认处于`simple_select`模式,每次用户完成绘制特征或退出`direct_select`模式时,都会自动再次转换为模式。 ### `direct_select` `Draw.modes.DIRECT_SELECT === 'direct_select'` 允许您选择、删除和拖动顶点;和拖动功能。 `direct_select` 模式不适用于点要素,因为它们没有顶点。 `direct_select`当用户单击所选线或多边形的顶点时,Draw 进入模式。所以`direct_select`模式通常遵循`simple_select`模式。 ### `draw_line_string` `Draw.modes.DRAW_LINE_STRING === 'draw_line_string'` 允许您绘制 LineString 要素。按 `退格键backspace`会删除上一个点 ### `draw_polygon` `Draw.modes.DRAW_POLYGON === 'draw_polygon'` 允许您绘制多边形特征。按 `退格键backspace`会删除上一个点 ### `draw_point` `Draw.modes.DRAW_POINT === 'draw_point'` 用于绘制点要素。 ### `draw_circle` 用于绘制圆要素。 ### `draw_rectangle` 用于绘制矩形要素。 ### `splitLineMode` 用于分割线要素。 ### `cutPolygonMode` 用于分割多边形要素。 ```js var addControls = [ { id: "drawCircle", title: '绘制圆', className: "vjmap-map-draw_circle", onActivate: function (ctx){ ctx.api.changeMode('draw_circle'); } }, { id: "drawRectangle", title: '绘制矩形', className: "vjmap-map-draw_rectangle", onActivate: function (ctx){ ctx.api.changeMode('draw_rectangle'); } }, { id: "splitLine", title: '分割线', className: "vjmap-map-draw-cut-line", onActivate: function (ctx){ ctx.api.changeMode('splitLineMode', { spliter: 'line_string' }); } }, { id: "cutPolygon", title: '分割多边形', className: "vjmap-map-draw-cut-polygon", onActivate: function (ctx){ ctx.api.changeMode('cutPolygonMode'); } }, { id: "undo", title: '撤销上一步操作', className: "vjmap-map-draw_undo", onActivate: function (ctx){ ctx.api.undo(); } }, { id: "redo", title: '重复上一步操作', className: "vjmap-map-draw_redo", onActivate: function (ctx){ ctx.api.redo(); } }, { id: "snap_mode_snap", title: '捕捉点', className: "snap_mode_snap", isCheckbox: true, checked: true, onActivate: function (ctx, e){ ctx.options.snap = e.target.checked; } }, { id: "snap_mode_grid", title: '捕捉网格', isCheckbox: true, checked: true, className: "snap_mode_grid", onActivate: function (ctx, e){ ctx.options.guides = e.target.checked; } } ] ``` ## API 方法 `var draw = new vjmap.Draw.Tool()` 将返回以下API: ### `add(geojson: Object) => Array` 此方法采用 GeoJSON Feature、FeatureCollection 或 Geometry 并将其添加到 Draw。它返回一个 id 数组,用于与添加的功能进行交互。如果某个功能没有自己的 id,则会自动生成一个。 支持的以GeoJSON特征类型的支持:`Point`,`LineString`,`Polygon`,`MultiPoint`, `MultiLineString`,和`MultiPolygon`。 如果您`add()`的功能的 id 已在使用中,则现有功能将被更新,并且不会添加新功能。 没有指定功能 ID 的示例: ```js var feature = { type: 'Point', coordinates: [0, 0] }; var featureIds = draw.add(feature); console.log(featureIds); //=> ['some-random-string'] ``` 具有指定功能 ID 的示例: ```js var feature = { id: 'unique-id', type: 'Feature', properties: {}, geometry: { type: 'Point', coordinates: [0, 0] } }; var featureIds = draw.add(feature); console.log(featureIds) //=> ['unique-id'] ``` --- ### `get(featureId: string): ?Feature` 返回 Draw 中具有指定 id 的 GeoJSON 功能,或者`undefined`如果 id 不匹配任何功能。 例子: ```js var featureIds = draw.add({ type: 'Point', coordinates: [0, 0] }); var pointId = featureIds[0]; console.log(draw.get(pointId)); //=> { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] } } ``` --- ### `getFeatureIdsAt(point: { x: number, y: number }): Array` 返回当前在指定点呈现的功能的功能 ID 数组。 请注意,该`point`参数需要`x`,`y`从像素空间,而不是经度,纬度坐标的坐标。 有了这个函数,你可以使用鼠标事件提供的坐标从 Draw 中获取信息。 ```js var featureIds = Draw.getFeatureIdsAt({x: 20, y: 20}); console.log(featureIds) //=> ['top-feature-at-20-20', 'another-feature-at-20-20'] ``` --- ### `getSelectedIds(): Array` 返回当前所选功能的功能 ID 数组。 --- ### `getSelected(): FeatureCollection` 返回当前选择的所有功能的 FeatureCollection。 --- ### `getSelectedPoints(): FeatureCollection` 返回代表当前选择的所有顶点的 FeatureCollection 点。 --- ### `getAll(): FeatureCollection` 返回所有功能的 FeatureCollection。 Example: ```js draw.add({ type: 'Point', coordinates: [0, 0] }); draw.add({ type: 'Point', coordinates: [1, 1] }); draw.add({ type: 'Point', coordinates: [2, 2] }); console.log(draw.getAll()); // { // type: 'FeatureCollection', // features: [ // { // id: 'random-0' // type: 'Feature', // geometry: { // type: 'Point', // coordinates: [0, 0] // } // }, // { // id: 'random-1' // type: 'Feature', // geometry: { // type: 'Point', // coordinates: [1, 1] // } // }, // { // id: 'random-2' // type: 'Feature', // geometry: { // type: 'Point', // coordinates: [2, 2] // } // } // ] // } ``` --- ### `delete(ids: string | Array): draw` 删除具有指定 ID 的功能。返回用于链接的绘制实例。 在`direct_select`模式下,删除活动特征将退出该模式并恢复到该`simple_select`模式。 例子: ```js var feature = { type: 'Point', coordinates: [0, 0] }; var ids = draw.add(feature); draw .delete(ids) .getAll(); // { type: 'FeatureCollection', features: [] } ``` --- ### `deleteAll(): draw` 删除所有功能。返回用于链接的绘制实例。 例子: ```js draw.add({ type: 'Point', coordinates: [0, 0] }); draw .deleteAll() .getAll(); // { type: 'FeatureCollection', features: [] } ``` --- ### `forceRefresh` 强制刷新 例子: ```js draw.forceRefresh(); ``` --- ### `undo` 撤销 例子: ```js draw.undo(); ``` --- ### `redo` 重做 例子: ```js draw.redo(); ``` --- ### `clearHistory` 清空历史记录 例子: ```js draw.clearHistory(); ``` --- ### `set(featureCollection: FeatureCollection): Array` 将 Draw 的功能设置为指定 FeatureCollection 中的功能。 执行任何必要的删除、创建和更新操作,以使 Draw 的功能与指定的 FeatureCollection 匹配。实际上,这与`Draw.deleteAll()`后跟相同,`Draw.add(featureCollection)`只是它不会对性能产生太大影响。 例子: ```js var ids = draw.set({ type: 'FeatureCollection', features: [{ type: 'Feature', properties: {}, id: 'example-id', geometry: { type: 'Point', coordinates: [0, 0] } }] }); // ['example-id'] ``` --- ### `trash(): draw` 调用当前模式的`trash`操作。返回用于链接的绘制实例。 在`simple_select`模式下,这将删除所有选定的功能。 在`direct_select`模式下,这将删除选定的顶点。 在绘图模式下,这会取消绘图并将绘图恢复为`simple_select`模式。 如果您想删除特征而不考虑当前模式,请使用`delete`或`deleteAll`功能。 --- ### `combineFeatures(): draw` 调用当前模式的`combineFeatures`操作。返回用于链接的绘制实例。 在`simple_select`模式下,这会将所有选定的特征组合成一个 Multi\* 特征,_只要它们都是相同的几何类型_。例如: - 选择是两个 LineStrings => MultiLineString - 选择是一个 MultiLineString 和一个 LineString => MultiLineString - 选择是两个 MultiLineStrings => MultiLineString 在选择不同几何类型的特征时调用此函数不会导致任何更改。例如: - 选择是一个点和一个 LineString => 没有采取任何行动 - 选择是一个 MultiLineString 和一个 MultiPoint => 没有采取任何行动 在`direct_select`模式和绘图模式下,不执行任何操作。 --- ### `uncombineFeatures(): draw` 调用当前模式的`uncombineFeatures`操作。返回用于链接的绘制实例。 在`simple_select`模式下,这会将每个选定的 Multi\* 特征拆分为其组成特征部分,并保持非多特征不变。例如: - 选择是两部分的 MultiLineString => LineString, LineString - 选择是三部分的 MultiLineString => LineString, LineString, LineString - 选择是由两部分和一个 Point 组成的 MultiLineString => LineString, LineString, Point - 选择是 LineString => LineString 在`direct_select`和 绘图模式下,不执行任何操作。 --- ### `getMode(): string` 返回 Draw 的当前模式。 --- ### `changeMode(mode: string, options?: Object): draw` 将绘图更改为另一种模式。返回用于链接的绘制实例。 所述`mode`参数必须是以上描述并在所列举的模式名称之一`Draw.modes`。 ```js // `simple_select` options { // Array of ids of features that will be initially selected featureIds: Array } ``` ```js // `direct_select` options { // The id of the feature that will be directly selected (required) featureId: string } ``` ```js // `draw_line_string` options { // The id of the LineString to continue drawing featureId: string, // The point to continue drawing from from: Feature|Point|Array } ``` --- ### `setFeatureProperty(featureId: string, property: string, value: any): draw` 设置具有指定 id 的要素的属性值。返回用于链接的绘制实例。 如果您使用 Draw 的功能作为应用程序中的主要数据存储,这将很有帮助。 例如设置颜色,可以通过 ```js draw.setFeatureProperty(featureId, "color", color); ``` 默认样式下,支持的属性有 点 ```js // 圆符号 draw.setFeatureProperty(featureId, "stroke_color_active", "#fff"); // 编辑模式活动状态下边框圆的颜色 draw.setFeatureProperty(featureId, "stroke_radius_active", 7); // 编辑模式活动状态下边框圆的大小 draw.setFeatureProperty(featureId, "color_active", "#fbb03b"); // 编辑模式活动状态下填充圆的颜色 draw.setFeatureProperty(featureId, "radius_active", 5); // 编辑模式活动状态下填充圆的大小 draw.setFeatureProperty(featureId, "stroke_color_inactive", "#fff"); // 编辑模式非活动状态下边框圆的颜色 draw.setFeatureProperty(featureId, "stroke_radius_inactive", 5); // 编辑模式非活动状态下边框圆的大小 draw.setFeatureProperty(featureId, "color_inactive", "#3bb2d0"); // 编辑模式非活动状态下填充圆的颜色 draw.setFeatureProperty(featureId, "radius_inactive", 3); // 编辑模式非活动状态下填充圆的大小 draw.setFeatureProperty(featureId, "color_static", "#3bb2d0"); // 非编辑模式下填充圆的颜色 draw.setFeatureProperty(featureId, "radius_static", 5); // 非编辑模式下边框圆的大小 // 图标符号 draw.setFeatureProperty(featureId, "icon_image", "xxx"); draw.setFeatureProperty(featureId, "icon_size", 1); // 放大倍数 draw.setFeatureProperty(featureId, "icon_rotate", "xxx"); draw.setFeatureProperty(featureId, "text", "xxx"); // 文本值 draw.setFeatureProperty(featureId, "text_rotate", "xxx");// 文本旋转角度 draw.setFeatureProperty(featureId, "text_opacity", "xxx"); // 文本透明度 draw.setFeatureProperty(featureId, "text_size", "xxx"); //文本大小(默认22) draw.setFeatureProperty(featureId, "symbol", true|false); // 有图标和文本 draw.setFeatureProperty(featureId, "onlyText", true|false); // 没有图标,只有文本 ``` 线 ```js draw.setFeatureProperty(featureId, "color", "xxx"); draw.setFeatureProperty(featureId, "line_width", "xxx"); ``` 面 ```js draw.setFeatureProperty(featureId, "opacity", "xxx"); draw.setFeatureProperty(featureId, "color", "xxx"); draw.setFeatureProperty(featureId, "line_width", "xxx"); ``` 修改坐标 ```js draw.setFeatureProperty(featureId, "coordinates", [xxx]); ``` 内置特殊属性如下 - 设置实体显示或隐藏 隐藏设置 ```js draw.setFeatureProperty(featureId, "isOff", true); // isOff属性设置为true,即为隐藏了 ``` 显示设置 ```js draw.setFeatureProperty(feature.id, "isOff", undefined); // isOff属性移除了。默认就是显示 ``` - 设置实体锁定与不锁定,(锁定的实体只可见,不可编辑) 锁定设置 ```js draw.setFeatureProperty(featureId, "isLocked", true); // isLocked属性设置为true,即为锁定了 ``` 不锁定设置 ```js draw.setFeatureProperty(feature.id, "isLocked", undefined); // isLocked属性移除了。默认就是不锁定了 ``` 设置绘图时不对此实体对象进行捕捉 ```js draw.setFeatureProperty(featureId, "noSnap", true); // noSnap属性设置为true,设置绘图时不对此实体对象进行捕捉 ``` 设置绘图时选择实体后,再次点击时不允许进行编辑.(如想编辑,可同时按Ctrl键点击) 允许拖动 ```js draw.setFeatureProperty(featureId, "disableDirectEdit", true); //disableDirectEdit属性设置为true,选择实体后,再次点击时不允许进行编辑 ``` 设置绘图时选择实体后不允许进行编辑.(不允许拖动,也不允许编辑) ```js draw.setFeatureProperty(featureId, "disable_edit", true); //disable_edit属性设置为true,不允许编辑。设置为undefined允许编辑 ``` 和`disableDirectEdit` 的区别在于 disableDirectEdit 允许拖动 默认不允许编辑夹点,按Ctrl键可允许编辑 disable_edit 不允许拖动 也不允许编辑夹点 - 设置实体在不同缩放级别下显示 ```js draw.setFeatureProperty(id, "minZoom", 5); // 表示5级以下才显示此实体 draw.setFeatureProperty(id, "maxZoom", 10); // 表示10级以上不会显示此实体 ``` - 设置实体颜色 ```js draw.setFeatureProperty(id, "color", color); draw.setFeatureProperty(id, "fillColor", color); // 填充色,只对多边形有效(如果不填,将使用color属性的颜色) draw.setFeatureProperty(id, "outlineColor", color);// 边框色,只对多边形有效(如果不填,将使用color属性的颜色) draw.setFeatureProperty(id, "noneOutline", true);// 不显示多边形边框,只对多边形有效 ``` - 设置实体允许浏览模式(非编辑模式)下高亮 ```js draw.setFeatureProperty(id, "hoverPointer", true); // 允许非编辑模式下高亮 draw.setFeatureProperty(id, "hover_color", "#f00"); // 设置高亮颜色 draw.setFeatureProperty(id, "hover_opacity", 0.3); // 设置高亮透明度 ``` - 设置多边形为拉伸实体 ```js draw.setFeatureProperty(id, "extrusionHeight", 100000); // 如果多边形有高度属性,则会变为拉伸实体 // draw.setFeatureProperty(id, "extrusionHeight", undefined); // 去掉高度属性,还原为多边形 ``` - 符号文本有关属性 ```js draw.setFeatureProperty(featureId, "symbol", true); // 设置为symbol为true时,还会显示符号文本,否则是一个小圆点 draw.setFeatureProperty(featureId, "text", ""); // 文本内容 draw.setFeatureProperty(featureId, "icon_image", ""); // 图标 // 设置旋转角度 draw.setFeatureProperty(featureId, "text_rotate", angle); draw.setFeatureProperty(featureId, "icon_rotate", angle); ``` ```js map.setCustomKeyValue("_disable_drawkey_backspacekey", true); // 绘图时禁用删除键 map.setCustomKeyValue("_disable_drawkey_escapekey", true); // 绘图时禁用esc取消键 map.setCustomKeyValue("_disable_drawkey_enterkey", true); // 绘图时禁用回车键 ``` 如果需要扩展更多的属性,需要自定义样式,默认样式见本文最后面 ## 事件 Draw 会触发许多事件。所有这些事件都以命名空间命名,`draw.`并从 地图对象发出。所有事件都是由用户交互触发的。 ```js map.on('draw.create', function (e) { console.log(e.features); }); ``` **如果您以编程方式调用 Draw API中的函数,则不会触发_与_该函数_直接对应的_任何事件。**例如,如果您调用`draw.delete()`,则不会有相应的`draw.delete`事件,因为您已经知道自己做了什么。但是,可能会触发不直接对应于调用函数的后续事件。例如,如果您选择了一个功能然后调用`draw.changeMode('draw_polygon')`,您将_不会_看到一个`draw.modechange`事件(因为它直接对应于调用的函数),但您_会_看到一个`draw.selectionchange`事件,因为通过更改模式您间接取消了一个功能的选择。 ### `draw.create` 创建特征时触发。以下交互将触发此事件: - 完成绘制要素。只需单击即可创建一个点。LineString 或 Polygon 仅在用户完成绘制时创建——即双击最后一个顶点或按 Enter——并且绘制的特征有效。 事件数据是一个具有以下形状的对象: ```js { // Array of GeoJSON objects representing the features that were created features: Array } ``` ### `draw.delete` 删除一项或多项功能时触发。以下交互将触发此事件: - 在`simple_select`模式中选择一项或多项功能时,单击垃圾箱按钮。 - 在模式中选择一项或多项功能时,按Backspace或Delete键`simple_select`。 - `draw.trash()`当您在`simple_select`mode 中选择了一个功能时调用。 事件数据是一个具有以下形状的对象: ```js { // Array of GeoJSON objects representing the features that were deleted features: Array } ``` ### `draw.combine` 组合特征时触发。以下交互将触发此事件: - 当在`simple_select`模式中选择了多个特征时,单击组合按钮。 - `draw.combineFeatures()`在`simple_select`mode中选择多个功能时调用。 事件数据是一个具有以下形状的对象: ```js { deletedFeatures: Array, // Array of deleted features (those incorporated into new multifeatures) createdFeatures: Array // Array of created multifeatures } ``` ### `draw.uncombine` 当特征未组合时触发。以下交互将触发此事件: - 在`simple_select`模式中选择一个或多个多重特征时,单击取消组合按钮。也可以选择非多重特征。 - `draw.uncombineFeatures()`在`simple_select`mode中选择一个或多个多特征时调用。也可以选择非多重特征。 事件数据是一个具有以下形状的对象: ```js { deletedFeatures: Array, // Array of deleted multifeatures (split into features) createdFeatures: Array // Array of created features } ``` ### `draw.update` 当一项或多项功能更新时触发。以下交互将触发此事件,可以按以下方式进行细分`action`: - `action: 'move'` - 在`simple_select`模式中完成移动一个或多个选定要素。该事件只会在移动完成时触发(即当用户释放鼠标按钮或点击时Enter)。 - `action: 'change_coordinates'` - 在`direct_select`模式中完成移动选定要素的一个或多个顶点。该事件只会在移动完成时触发(即当用户释放鼠标按钮或点击Enter,或者她的鼠标离开地图容器时)。 - 在`direct_select`mode 中删除选定要素的一个或多个顶点,这可以通过按Backspace或Delete键、单击垃圾箱按钮或调用 来完成`draw.trash()`。 - 通过在`direct_select`模式中单击该要素上的中点,将顶点添加到所选要素。 创建或删除要素时_不会_触发此事件。要跟踪这些交互,请侦听`draw.create`和`draw.delete`事件。 事件数据是一个具有以下形状的对象: ```js { features: Array, // Array of features that were updated action: string // Name of the action that triggered the update } ``` ### `draw.selectionchange` 更改选择时(即选择或取消选择一个或多个功能)。以下交互将触发此事件: - 单击一个特征以选择它。 - 当一个特征已被选中时,按住 Shift 键并单击另一个特征以将其添加到选择中。 - 单击一个顶点以选择它。 - 当一个顶点已被选中时,按住 Shift 键并单击另一个顶点以将其添加到选择中。 - 创建一个包含至少一个特征的框选择。 - 单击所选要素外部以取消选择。 - 在远离选定顶点的地方单击以取消选择。 - 完成绘制要素(要素在创建后立即被选中)。 - 当一个功能已经被选中时,调用`draw.changeMode()`使得该功能被取消选中。 - 使用`draw.changeMode('simple_select', { featureIds: [..] })`切换到`simple_select`模式,并立即选择指定的功能。 - 使用`draw.delete`,`draw.deleteAll`或`draw.trash`删除特征。 事件数据是一个具有以下形状的对象: ```js { features: Array // Array of features that are selected after the change } ``` ### `draw.modechange` 当模式改变时触发。以下交互将触发此事件: - 单击点、线或多边形按钮开始绘制(输入`draw_*`模式)。 - 完成绘制要素(进入`simple_select`模式)。 - 在`simple_select`模式下,单击已选择的功能(进入`direct_select`模式)。 - 在`direct_select`模式下,在所有要素外单击(进入`simple_select`模式)。 此事件在当前模式停止之后和下一个模式开始之前触发。直到所有事件处理程序都被触发后才会发生渲染,因此您可以通过`draw.changeMode()`在`draw.modechange`处理程序内部调用来强制模式重定向。 事件数据是一个具有以下形状的对象: ```js { mode: string // The next mode, i.e. the mode that Draw is changing to } ``` `simple_select`和`direct_select`模式可以使用特定于该模式的选项启动(见上文)。 ### `draw.render` `setData()`在 地图上的Draw 调用之后触发。这并不意味着 set data 调用已完成更新地图,只是地图正在更新。 ### `draw.actionable` 当 Draw 的状态改变以启用和禁用不同的动作时触发。在这个事件将让你知道,如果`draw.trash()`,`draw.combineFeatures()`并`draw.uncombineFeatures()`会产生效果。 ```js { actions: { trash: true combineFeatures: false, uncombineFeatures: false } } ``` ### `draw.updatecoordinate` 绘制过程中实时更新点坐标 ### `draw.simpleselect.contextmenu` 选择模式下上下文菜单事件 ### `draw.directselect.contextmenu` 选中编辑夹点模式下上下文菜单事件 ### `draw.custom` 其他自定义事件,如要点击锁定的图形,需提示为锁定图形,可用如下代码 ``` map.on('draw.custom', e=> { if (e.customName === "clickedlocked") { console.log(e) } }) ``` ## 绘图动作Action ### actionDraw 根据参数绘制不同的图形动作 - draw_point 绘制点 - draw_line_string 绘制线 - draw_polygon 绘制多边形 - draw_rectangle 绘制矩形 - draw_slant_rectangle 绘制斜矩形 - draw_circle 绘制圆 如调用绘制点 ```js await vjmap.Draw.actionDraw(map, 'draw_point', {}) ``` 在绘制过程中,按`ESC`键或`Enter`键可以取消或结束绘制,如果也可以通过代码来主动取消或结束 ```js // 给地图发送ESC键消息即可取消,模拟按ESC键 map.fire("keyup", {keyCode:27}) // 给地图发送Enter键消息即可确认,模拟按Enter键 map.fire("keyup", {keyCode:13}) ``` ### actionDrawPoint 绘制一个点,返回一个Promise,直到绘制结束,可通过传递回调参数 `updatecoordinate` 得到实时更新的坐标,如果取消,则返回 `cancel`为 `true`, 如 ```js let ellipse = new vjmap.EllipseFill({ center: center, majorAxisRadius: 0, minorAxisRadius: 0, fillColor: 'green', fillOpacity: 0.8, fillOutlineColor: "#f00" }); ellipse.addTo(map); let ellipseMajorAxisPt = await vjmap.Draw.actionDrawPoint(map, { updatecoordinate: (e) => { if (!e.lnglat) return; const co = map.fromLngLat(e.lnglat); ellipse.setMinorAxisRadius(center.distanceTo(co)); ellipse.setMajorAxisRadius(center.distanceTo(co)); } }); if (ellipseMajorAxisPt.cancel) { ellipse.remove() return ;// 取消操作 } ``` ### actionDrawLineSting 绘制一条线,返回一个Promise,直到绘制结束,可通过传递回调参数 `updatecoordinate` 得到实时更新的坐标,如果取消,则返回 `cancel`为 `true`, 如 如果要绘制直线,可以设置 `pointCount` 点数为2。(也可以自定义要绘制的点数,当达到这个点数时,自动结束) 如果要绘制绘制一条闭合的折线,可以设置 `isClosed` 为true ```js let res = await vjmap.Draw.actionDrawLineSting(map, { //pointCount: 2, // 绘制直线,有两个点时,自动结束 // isClosed: true, // 自动闭合 api: { getSnapFeatures: snapObj //要捕捉的数据项在后面,通过属性features赋值 }, updatecoordinate: (e) => { // 点坐标实时更新回调 if (!e.lnglat) return; },contextMenu: (e) => { // 上下文右键菜单回调 } }); if (res.cancel) { return ;// 取消操作 } ``` 如需要设置绘制时的线宽和颜色,可用如下代码 ```js // 获取默认的样式 const opts = vjmap.cloneDeep(vjmap.Draw.defaultOptions()); let styles = opts.styles // 修改线宽 styles[7]["paint"]["line-width"] = ["case",['has', 'line_width'],['get', 'line_width'], 8] // 修改颜色 styles[7]["paint"]["line-color"] = "#00ffff" let line = await vjmap.Draw.actionDrawLineSting(map, { styles }); if (line.cancel) { return } // 获取绘制的点坐标 let coords = map.fromLngLat(line.features[0].geometry.coordinates).map(pt => pt.toArray()) ``` ### actionDrawPolygon 绘制一个多边形,返回一个Promise,直到绘制结束,可通过传递回调参数 `updatecoordinate` 得到实时更新的坐标,如果取消,则返回 `cancel`为 `true`, 如 如果要绘制直线,可以设置 `pointCount` 点数为3。(也可以自定义要绘制的点数,当达到这个点数时,自动结束) ```js let res = await vjmap.Draw.actionDrawPolygon(map, { //pointCount: 3, // 绘制三角形,有三个点时,自动结束 api: { getSnapFeatures: snapObj //要捕捉的数据项在后面,通过属性features赋值 }, updatecoordinate: (e) => { // 点坐标实时更新回调 if (!e.lnglat) return; },contextMenu: (e) => { // 上下文右键菜单回调 } }); if (res.cancel) { return ;// 取消操作 } ``` ### actionDrawRectangle 绘制一个矩形,返回一个Promise,直到绘制结束,可通过传递回调参数 `updatecoordinate` 得到实时更新的坐标,如果取消,则返回 `cancel`为 `true`, 如 ```js let res = await vjmap.Draw.actionDrawRectangle(map, { api: { getSnapFeatures: snapObj //要捕捉的数据项在后面,通过属性features赋值 }, updatecoordinate: (e) => { // 点坐标实时更新回调 if (!e.lnglat) return; },contextMenu: (e) => { // 上下文右键菜单回调 } }); if (res.cancel) { return ;// 取消操作 } ``` ### actionDrawSlantRectangle 绘制一个斜矩形,返回一个Promise,直到绘制结束,可通过传递回调参数 `updatecoordinate` 得到实时更新的坐标,如果取消,则返回 `cancel`为 `true`, 如 ```js let res = await vjmap.Draw.actionDrawSlantRectangle(map, { api: { getSnapFeatures: snapObj //要捕捉的数据项在后面,通过属性features赋值 }, updatecoordinate: (e) => { // 点坐标实时更新回调 if (!e.lnglat) return; },contextMenu: (e) => { // 上下文右键菜单回调 } }); if (res.cancel) { return ;// 取消操作 } ``` ### actionDrawCircle 绘制一个圆,返回一个Promise,直到绘制结束,可通过传递回调参数 `updatecoordinate` 得到实时更新的坐标,如果取消,则返回 `cancel`为 `true`, 如 ```js let res = await vjmap.Draw.actionDrawCircle(map, { api: { getSnapFeatures: snapObj //要捕捉的数据项在后面,通过属性features赋值 }, updatecoordinate: (e) => { // 点坐标实时更新回调 if (!e.lnglat) return; },contextMenu: (e) => { // 上下文右键菜单回调 } }); if (res.cancel) { return ;// 取消操作 } ``` ### actionSelect 选择实体,返回一个Promise,直到右键选择结束,可通过传递回调参数 `selectSingle` 来决定只选择一个实体就返回。选择结束后返回选择的实体数组, 如 ```js // draw为要选择的DrawTool对象,选择这里面的实体 let selected = await vjmap.Draw.actionSelect(map, draw, { selectSingle: false // 可以选择多个,直至右键结果;如果true,则选择一个就返回 } ); console.log(selected.features) // 返回选中的实体数组 ``` 在上面的方法绘制过程中,如果需要手动输入坐标,可以给地图map对象,发送`draw.input.click`事件,格式如 ```js map.fire("draw.input.click", { lngLat: map.toLngLat([x, 1]), styleId: "当前的样式id" // 可以通过options中写回调函数 statusChange 来获取 }) 如 await vjdraw.actionDrawPoint(map, { statusChange: e => { if (e.isStart) { // 开始绘制了,可获取当前的样式id let styleId = e.styleId } else if (e.isEnd) { // 结束绘制了,可获取当前的样式id let styleId = e.styleId } } }) ``` ## 默认样式 ```js const opts = vjmap.Draw.defaultOptions(); opts.styles = "新的样式" ``` ## 修改默认样式 如修改默认样式,不允许编辑的时候显示线的中点 ```js const opts = vjmap.Draw.defaultOptions() opts.midpoints = false; const draw = new vjmap.Draw.Tool(opts); // 或 map.getDrawLayer(opts); ``` --- # 图纸批注 图纸批注此功能作为图纸审核工作的一个重要工作,下面介绍如何实现在线Web端图纸批注功能。 # 实现效果图 ![annotate.gif](https://vjmap.com/blogimages/annotate.gif) # 图纸批注方案比较 图纸批注功能做为web端图纸显示的一个图层,一般的技术思路可分为通过`矢量图形绘制`和`svg图形`这两种方式把图纸要批注的内容叠加到地图图层上。两个方案的优缺点如下: `矢量图形绘制`: 优点:性能高,叠加效果好,支持无级放大不失真; 缺点:编辑功能较弱,表达内容有限 `SVG图形`: 优点:编辑功能强,表达内容丰富 缺点:大量svg图形会导致性能低下,放大到一定级别后会失真 总结: 鉴于图纸批注的需求,是一个要求表达内容丰富, 同时不会存在很大数据量的需求,所以用 svg图形方案叠加是不错的选择。 # 图纸批注实现 ## SVG图形编辑库选型 svg图形编辑库采用的是github上star多达22k的开源库`Fabric.js`。 Fabric.js 是一个功能强大且简洁的 canvas 库。它提供了一系列可操作 canvas 的 API。让你轻松的操作 canvas。并且还提供了 SVG 转 canvas 和 canvas 转 SVG 的解析器供使用。 Fabric.js官网地址为http://fabricjs.com/ ,github仓库地址为https://github.com/fabricjs/fabric.js # 图纸批注绘图相关代码 ```js //绘画方法 function drawing() { if (drawingObject) { canvas.remove(drawingObject); } var canvasObject = null; switch (drawType) { case "arrow": //箭头 canvasObject = new fabric.Path(drawArrow(mouseFrom.x, mouseFrom.y, mouseTo.x, mouseTo.y, 30, 30), { stroke: strokeColor.value, fill: "rgba(255,255,255,0)", strokeWidth: strokeWidth.value }); break; case "line": //直线 canvasObject = new fabric.Line([mouseFrom.x, mouseFrom.y, mouseTo.x, mouseTo.y], { stroke: strokeColor.value, strokeWidth: strokeWidth.value }); break; case "dottedline": //虚线 canvasObject = new fabric.Line([mouseFrom.x, mouseFrom.y, mouseTo.x, mouseTo.y], { strokeDashArray: [3, 1], stroke: strokeColor.value, strokeWidth: strokeWidth.value }); break; case "circle": //正圆 var left = mouseFrom.x, top = mouseFrom.y; var radius = Math.sqrt((mouseTo.x - left) * (mouseTo.x - left) + (mouseTo.y - top) * (mouseTo.y - top)) / 2; canvasObject = new fabric.Circle({ left: left, top: top, stroke: strokeColor.value, fill: "rgba(255, 255, 255, 0)", radius: radius, strokeWidth: strokeWidth.value }); break; case "ellipse": //椭圆 var left = mouseFrom.x, top = mouseFrom.y; var radius = Math.sqrt((mouseTo.x - left) * (mouseTo.x - left) + (mouseTo.y - top) * (mouseTo.y - top)) / 2; canvasObject = new fabric.Ellipse({ left: left, top: top, stroke: strokeColor.value, fill: "rgba(255, 255, 255, 0)", originX: "center", originY: "center", rx: Math.abs(left - mouseTo.x), ry: Math.abs(top - mouseTo.y), strokeWidth: strokeWidth.value }); break; case "rectangle": //长方形 var path = "M " + mouseFrom.x + " " + mouseFrom.y + " L " + mouseTo.x + " " + mouseFrom.y + " L " + mouseTo.x + " " + mouseTo.y + " L " + mouseFrom.x + " " + mouseTo.y + " L " + mouseFrom.x + " " + mouseFrom.y + " z"; canvasObject = new fabric.Path(path, { left: left, top: top, stroke: strokeColor.value, strokeWidth: strokeWidth.value, fill: "rgba(255, 255, 255, 0)" }); //也可以使用fabric.Rect break; case "rightangle": //直角三角形 var path = "M " + mouseFrom.x + " " + mouseFrom.y + " L " + mouseFrom.x + " " + mouseTo.y + " L " + mouseTo.x + " " + mouseTo.y + " z"; canvasObject = new fabric.Path(path, { left: left, top: top, stroke: strokeColor.value, strokeWidth: strokeWidth.value, fill: "rgba(255, 255, 255, 0)" }); break; case "equilateral": //等边三角形 var height = mouseTo.y - mouseFrom.y; canvasObject = new fabric.Triangle({ top: mouseFrom.y, left: mouseFrom.x, width: Math.sqrt(Math.pow(height, 2) + Math.pow(height / 2.0, 2)), height: height, stroke: strokeColor.value, strokeWidth: strokeWidth.value, fill: "rgba(255,255,255,0)" }); break; case "text": textbox = new fabric.Textbox("", { left: mouseFrom.x - 60, top: mouseFrom.y - 20, width: 150, fontSize: 30, borderColor: "yellow", fill: strokeColor.value, hasControls: true }); canvas.add(textbox); textbox.enterEditing(); textbox.hiddenTextarea.focus(); break; case "remove": break; default: break; } if (canvasObject) { canvas.add(canvasObject); drawingObject = canvasObject; } } ``` ## SVG图形与地图叠加显示 `唯杰地图` `vjmap`提供的sdk中有在一个地理范围内创建一个随缩放而缩放的div的覆盖物类`DivOverlay`,其类型定义如下 ```typescript /** * 在一个地理范围内创建一个随缩放而缩放的div的覆盖物 * 注:如果是svg,则需设置为 viewBox="0 0 width height" preserveAspectRatio="xMinYMin meet", updateDivSize选项设置为true **/ export class DivOverlay { options: DivOverlayOptions; _map?: Map; isShow: boolean; minZoom: number; maxZoom: number; isRemoved: boolean; parentContainer?: HTMLElement; constructor(options: DivOverlayOptions); addTo(map: Map, insertId?: string | HTMLElement): void; private _isShow; private _add; private _remove; /** * 设置是否显示隐藏 * @param visible 是否显示 * @param isDisplay true的话,表示用style的display去控制隐藏显示,dom还在文档中。false的话,会从文档动态清空增加 */ setVisible(visible?: boolean, isDisplay?: boolean): void; remove(): void; updateBounds(bounds: [GeoPointLike, GeoPointLike, GeoPointLike, GeoPointLike] | GeoBounds): void; updateSize(width: number, height: number): void; private _updateZoom; private _updateDivSize; private _adjustCoord; private _update; } export interface DivOverlayOptions { /** 范围,四个点坐标 */ bounds: [GeoPointLike, GeoPointLike, GeoPointLike, GeoPointLike] | GeoBounds; /** html元素 */ element: HTMLElement; /** 元素宽 */ width: number; /** 元素高 */ height: number; /** 显示最大级别 */ minZoom?: number; /** 显示最小级别 */ maxZoom?: number; /** 自动更新div大小,(如果需要svg放大,需要设置为true) */ updateDivSize?: boolean; /** 放大div时,最大的div大小,超过了就像素放大了 */ maxDivSize?: number; } ``` 我们把fabricjs中绘制的图形导出为svg,然后通过DivOverlay叠加至地图上,就可以了。相关代码如下 ```typescript export const createDivSvg = (map: Map, eleId: string, pt1: GeoPoint, pt2: GeoPoint, width: number, height: number, svg: string) => { svg = svg.substring(svg.indexOf("")); const div = document.createElement( "div" ); div.id = eleId; div.innerHTML = ` ${svg} `; div.style.position = 'absolute'; div.style.pointerEvents = "none" div.style.width = width + "px"; div.style.height = height + "px"; //div.style.opacity = '0.8'; const divOverlay = new DivOverlay({ bounds: [[pt1.x, pt2.y], [pt1.x, pt1.y], [pt2.x, pt1.y], [pt2.x, pt2.y]], element: div, width: width, height: height, updateDivSize: true // 如果svg需要放大,需要加此参数 }) divOverlay.addTo(map); return divOverlay; } let center = map.getCenter(); let anno = { mapId: app.curMapId, version: app.curVersion, name: name, darkTheme: svc.currentMapParam()?.darkMode === true ? true : false, imgSrc: img, zoom: map.getZoom(), centerX: center.lng, centerY: center.lat, bearing: map.getBearing(), pitch: map.getPitch(), id: id, pt1: canvasCoord1, pt2: canvasCoord2, width: canvasWidth, height: canvasHeight, svg: fabricCanvas.toSVG(), json: fabricCanvas.toJSON(), } // 增加divSvg let divOverlay = createDivSvg(map, id, canvasCoord1, canvasCoord2, canvasWidth, canvasHeight, anno.svg); ``` ![image-20220724211826407](https://vjmap.com/blogimages/image-20220724211826407.png) 上面的案例代码已开源。访问 ([唯杰地图云端图纸管理平台](https://vjmap.com/app/cloud) https://vjmap.com/app/cloud) ,点击`下载此案例源码`即可。 --- # 互联网地图叠加 一些CAD图纸有实际地理坐标位置时,如地形图等,有时候需要和互联网地图叠加进行显示。其主要需求有: - CAD图有坐标系时,能自动叠加到互联网地图上 - CAD没有坐标系时,能自定义几个公共点,通过公共点叠加到互联网地图上 - CAD图与互联网地图能相互叠加。(底图是哪个,就以底图为坐标系) # 理论知识 ## GIS中常用坐标系 **(一)WGS84坐标系** WGS-84坐标系(World Geodetic System一1984 Coordinate System) 一种国际上采用的地心坐标系。坐标原点为地球质心,其地心空间直角坐标系的Z轴指向BIH (国际时间服务机构)1984.O定义的协议地球极(CTP)方向,X轴指向BIH 1984.0的零子午面和CTP赤道的交点,Y轴与Z轴、X轴垂直构成右手坐标系,称为1984年世界大地坐标系统。 属于wgs84坐标系的图有如天地图等。 **(二)WGS84 Web墨卡托** Web墨卡托是2005年谷歌在谷歌地图中首次使用的,当时或更早的Web墨卡托使用者还是称其为世界墨卡托 World Mercator – Spherical Mercator (unofficial deprecated ESRI),代号 WKID 54004 (在 EPSG:54004 或 ESRI:54004 中,非官方)。 EPSG于2009年2月9号使用新代号 EPSG:3857 ,给谷歌地图投影方法命名为“公共可视化伪墨卡托投影”(PVPM) **(三)GCJ02经纬度投影** GCJ-02是由中国国家测绘局(G表示Guojia国家,C表示Cehui测绘,J表示Ju局)制订的地理信息系统的坐标系统。 它其实就是对真实坐标系统进行人为的加偏处理,按照特殊的算法,将真实的坐标加密成虚假的坐标,而这个加偏并不是线性的加偏,所以各地的偏移情况都会有所不同。而加密后的坐标也常被大家称为“火星坐标系统”。 该坐标系的坐标值为经纬度格式,单位为度。 这里的GCJ02经纬度投影,也就是在WGS84经纬度的基础之上,进行GCJ-02加偏。 如高德地图,就采用的是GCJ02坐标系,也就是俗称的**火星坐标系**。 **(四)GCJ02 Web 墨卡托投影** GCJ-02是由中国国家测绘局(G表示Guojia国家,C表示Cehui测绘,J表示Ju局)制订的地理信息系统的坐标系统。 它其实就是对真实坐标系统进行人为的加偏处理,按照特殊的算法,将真实的坐标加密成虚假的坐标,而这个加偏并不是线性的加偏,所以各地的偏移情况都会有所不同。而加密后的坐标也常被大家称为“火星坐标系统”。 该坐标系的坐标值为Web墨卡托格式,单位为米。 这里的GCJ02 Web 墨卡托,也就是在标准Web墨卡托的基础之上,进行GCJ-02加偏。 **(五)BD09 经纬度投影** BD09经纬度投影属于百度坐标系,它是在标准经纬度的基础上进行GCJ-02加偏之后,再加上百度自身的加偏算法,也就是在标准经纬度的基础之上进行了两次加偏。 该坐标系的坐标值为经纬度格式,单位为度。 **(六)BD09 Web 墨卡托影** BD09 Web 墨卡托属于百度坐标系,它是在标准Web墨卡托的基础上进行GCJ-02加偏之后,再加上百度自身的加偏算法,也就是在Web墨卡托的基础之上进行了两次加偏。 该坐标系的坐标值为Web墨卡托格式,单位为米。 **(七)北京54坐标系** 中国成立以后,我国大地测量进入了全面发展时期,在全国范围内开展了正规的,全面的大地测量和测图工作,迫切需要建立一个参心大地坐标系。由于当时的”一边倒”政治趋向,故我国采用了前苏联的克拉索夫斯基椭球参数,并与前苏联1942年坐标系进行联测,通过计算建立了我国大地坐标系,定名为1954年北京坐标系。因此,1954年北京坐标系可以认为是前苏联1942年坐标系的延伸。T.A的原点不在北京而是在前苏联的普尔科沃。 自北京54坐标系统建立以来,在该坐标系内进行了许多地区的局部平差,其成果得到了广泛的应用。但是随着测绘新理论·新技术的不断发展,人们发现该坐标系存在很多缺点,为此,我国在1978年在西安召开了”全国天文大地网整体平差会议”,提出了建立属于我国自己的大地坐标系,即后来的1980西安坐标系。 **(八)西安80坐标系** 1978年4月在西安召开全国天文大地网平差会议,确定重新定位,建立我国新的坐标系。为此有了1980年国家大地坐标系。1980年国家大地坐标系采用地球椭球基本参数为1975年国际大地测量与地球物理联合会第十六届大会推荐的数据,即IAG 75地球椭球体。该坐标系的大地原点设在我国中部的陕西省泾阳县永乐镇,位于西安市西北方向约60公里。 **(九)CGCS2000坐标系** 2000中国大地坐标系(China Geodetic Coordinate System 2000,CGCS2000),又称之为2000国家大地坐标系,是中国新一代大地坐标系,21世纪初已在中国正式实施。 # 投影坐标系6位和8位的区别 一般投影坐标系X为8位,Y为7位。但有些图中X只有6位,这是因为省去了前2位的带系的原因。具体原因可查看此博客文章。https://blog.csdn.net/thinkpang/article/details/124172626 # EPSG是什么 EPSG(The European Petroleum Survey Group, http://www.epsg.org/ )维护着空间参照对象的数据集,OGC标准中空间参照系统的SRID(Spatial Reference System Identifier)与EPSG的空间参照系统ID相一致。 EPSG成立于1986年,并已于2005年“重组”为国际石油和天然气生产商协会(The OGP Surveying and Positioning Committee),维护并发布坐标参照系统的数据集参数,以及坐标转换描述。该数据集被广泛接受并使用,通过一个Web发布平台进行分发,同时提供了微软Acess数据库的存储文件,通过SQL 脚本文件,mySQL, Oracle 和PostgreSQL等数据库也可使用。 该组织发布了一个坐标参照系统的数据集,并维护坐标参照系统的数据集参数,以及坐标转换描述,数据集对全球收录到的坐标参照系统进行了编码。 EPSG查询网站: [https://epsg.io/](https://epsg.io/) **常用EPSG坐标系对照表:** 是一个机构,它为常用坐标系做了编号,有了编号编程时候直接使用编号即可定义坐标系,常用坐标系及其EPSG编号有: ### EPSG:4326 地理坐标系,坐标单位为经纬度。它是以地球质心为椭球中心的世界地理坐标系,GPS用的坐标系就是这个。 ### EPSG:4490 地理坐标系,坐标单位为经纬度。名字叫做CGCS2000坐标系,它是最适合中国地理位置的地理坐标系。 ### EPSG:3857 投影坐标系,坐标单位为x,y。伪墨卡托投影,也被称为球体墨卡托,Web Mercator。它是基于墨卡托投影的。 图内常见的EPSG代码如下: ```json 投影坐标 Projected Coordinate System (根据国家规定,推荐使用CGCS2000的投影坐标系) 1、Xian_1980 6度分 EPSG代号 2327 Xian_1980_GK_Zone_13 2328 Xian_1980_GK_Zone_14 2329 Xian_1980_GK_Zone_15 2330 Xian_1980_GK_Zone_16 2331 Xian_1980_GK_Zone_17 2332 Xian_1980_GK_Zone_18 2333 Xian_1980_GK_Zone_19 2334 Xian_1980_GK_Zone_20 2335 Xian_1980_GK_Zone_21 2336 Xian_1980_GK_Zone_22 2337 Xian_1980_GK_Zone_23 2338 Xian_1980_GK_CM_75E 2339 Xian_1980_GK_CM_81E 2340 Xian_1980_GK_CM_87E 2341 Xian_1980_GK_CM_93E 2342 Xian_1980_GK_CM_99E 2343 Xian_1980_GK_CM_105E 2344 Xian_1980_GK_CM_111E 2345 Xian_1980_GK_CM_117E 2346 Xian_1980_GK_CM_123E 2347 Xian_1980_GK_CM_129E 2348 Xian_1980_GK_CM_135E 2、Xian_1980 3度分 2349 Xian_1980_3_Degree_GK_Zone_25 2350 Xian_1980_3_Degree_GK_Zone_26 2351 Xian_1980_3_Degree_GK_Zone_27 2352 Xian_1980_3_Degree_GK_Zone_28 2353 Xian_1980_3_Degree_GK_Zone_29 2354 Xian_1980_3_Degree_GK_Zone_30 2355 Xian_1980_3_Degree_GK_Zone_31 2356 Xian_1980_3_Degree_GK_Zone_32 2357 Xian_1980_3_Degree_GK_Zone_33 2358 Xian_1980_3_Degree_GK_Zone_34 2359 Xian_1980_3_Degree_GK_Zone_35 2360 Xian_1980_3_Degree_GK_Zone_36 2361 Xian_1980_3_Degree_GK_Zone_37 2362 Xian_1980_3_Degree_GK_Zone_38 2363 Xian_1980_3_Degree_GK_Zone_39 2364 Xian_1980_3_Degree_GK_Zone_40 2365 Xian_1980_3_Degree_GK_Zone_41 2366 Xian_1980_3_Degree_GK_Zone_42 2367 Xian_1980_3_Degree_GK_Zone_43 2368 Xian_1980_3_Degree_GK_Zone_44 2369 Xian_1980_3_Degree_GK_Zone_45 2370 Xian_1980_3_Degree_GK_CM_75E 2371 Xian_1980_3_Degree_GK_CM_78E 2372 Xian_1980_3_Degree_GK_CM_81E 2373 Xian_1980_3_Degree_GK_CM_84E 2374 Xian_1980_3_Degree_GK_CM_87E 2375 Xian_1980_3_Degree_GK_CM_90E 2376 Xian_1980_3_Degree_GK_CM_93E 2377 Xian_1980_3_Degree_GK_CM_96E 2378 Xian_1980_3_Degree_GK_CM_99E 2379 Xian_1980_3_Degree_GK_CM_102E 2380 Xian_1980_3_Degree_GK_CM_105E 2381 Xian_1980_3_Degree_GK_CM_108E 2382 Xian_1980_3_Degree_GK_CM_111E 2383 Xian_1980_3_Degree_GK_CM_114E 2384 Xian_1980_3_Degree_GK_CM_117E 2385 Xian_1980_3_Degree_GK_CM_120E 2386 Xian_1980_3_Degree_GK_CM_123E 2387 Xian_1980_3_Degree_GK_CM_126E 2388 Xian_1980_3_Degree_GK_CM_129E 2389 Xian_1980_3_Degree_GK_CM_132E 2390 Xian_1980_3_Degree_GK_CM_135E 3、Beijing_1954 3度分 2401 Beijing_1954_3_Degree_GK_Zone_25 2402 Beijing_1954_3_Degree_GK_Zone_26 2403 Beijing_1954_3_Degree_GK_Zone_27 2404 Beijing_1954_3_Degree_GK_Zone_28 2405 Beijing_1954_3_Degree_GK_Zone_29 2406 Beijing_1954_3_Degree_GK_Zone_30 2407 Beijing_1954_3_Degree_GK_Zone_31 2408 Beijing_1954_3_Degree_GK_Zone_32 2409 Beijing_1954_3_Degree_GK_Zone_33 2410 Beijing_1954_3_Degree_GK_Zone_34 2411 Beijing_1954_3_Degree_GK_Zone_35 2412 Beijing_1954_3_Degree_GK_Zone_36 2413 Beijing_1954_3_Degree_GK_Zone_37 2414 Beijing_1954_3_Degree_GK_Zone_38 2415 Beijing_1954_3_Degree_GK_Zone_39 2416 Beijing_1954_3_Degree_GK_Zone_40 2417 Beijing_1954_3_Degree_GK_Zone_41 2418 Beijing_1954_3_Degree_GK_Zone_42 2419 Beijing_1954_3_Degree_GK_Zone_43 2420 Beijing_1954_3_Degree_GK_Zone_44 2421 Beijing_1954_3_Degree_GK_Zone_45 2422 Beijing_1954_3_Degree_GK_CM_75E 2423 Beijing_1954_3_Degree_GK_CM_78E 2424 Beijing_1954_3_Degree_GK_CM_81E 2425 Beijing_1954_3_Degree_GK_CM_84E 2426 Beijing_1954_3_Degree_GK_CM_87E 2427 Beijing_1954_3_Degree_GK_CM_90E 2428 Beijing_1954_3_Degree_GK_CM_93E 2429 Beijing_1954_3_Degree_GK_CM_96E 2430 Beijing_1954_3_Degree_GK_CM_99E 2431 Beijing_1954_3_Degree_GK_CM_102E 2432 Beijing_1954_3_Degree_GK_CM_105E 2433 Beijing_1954_3_Degree_GK_CM_108E 2434 Beijing_1954_3_Degree_GK_CM_111E 2435 Beijing_1954_3_Degree_GK_CM_114E 2436 Beijing_1954_3_Degree_GK_CM_117E 2437 Beijing_1954_3_Degree_GK_CM_120E 2438 Beijing_1954_3_Degree_GK_CM_123E 2439 Beijing_1954_3_Degree_GK_CM_126E 2440 Beijing_1954_3_Degree_GK_CM_129E 2441 Beijing_1954_3_Degree_GK_CM_132E 2442 Beijing_1954_3_Degree_GK_CM_135E 4、Beijing_1954 6度分 21413 Beijing_1954_GK_Zone_13 21414 Beijing_1954_GK_Zone_14 21415 Beijing_1954_GK_Zone_15 21416 Beijing_1954_GK_Zone_16 21417 Beijing_1954_GK_Zone_17 21418 Beijing_1954_GK_Zone_18 21419 Beijing_1954_GK_Zone_19 21420 Beijing_1954_GK_Zone_20 21421 Beijing_1954_GK_Zone_21 21422 Beijing_1954_GK_Zone_22 21423 Beijing_1954_GK_Zone_23 21473 Beijing_1954_GK_Zone_13N 21474 Beijing_1954_GK_Zone_14N 21475 Beijing_1954_GK_Zone_15N 21476 Beijing_1954_GK_Zone_16N 21477 Beijing_1954_GK_Zone_17N 21478 Beijing_1954_GK_Zone_18N 21479 Beijing_1954_GK_Zone_19N 21480 Beijing_1954_GK_Zone_20N 21481 Beijing_1954_GK_Zone_21N 21482 Beijing_1954_GK_Zone_22N 21483 Beijing_1954_GK_Zone_23N 5、WGS_1984 墨卡托投影 3395 WGS_1984_World_Mercator 6、CGCS2000 6度分 4491 CGCS2000_GK_Zone_13 4492 CGCS2000_GK_Zone_14 4493 CGCS2000_GK_Zone_15 4494 CGCS2000_GK_Zone_16 4495 CGCS2000_GK_Zone_17 4496 CGCS2000_GK_Zone_18 4497 CGCS2000_GK_Zone_19 4498 CGCS2000_GK_Zone_20 4499 CGCS2000_GK_Zone_21 4500 CGCS2000_GK_Zone_22 4501 CGCS2000_GK_Zone_23 4502 CGCS2000_GK_CM_75E 4503 CGCS2000_GK_CM_81E 4504 CGCS2000_GK_CM_87E 4505 CGCS2000_GK_CM_93E 4506 CGCS2000_GK_CM_99E 4507 CGCS2000_GK_CM_105E 4508 CGCS2000_GK_CM_111E 4509 CGCS2000_GK_CM_117E 4510 CGCS2000_GK_CM_123E 4511 CGCS2000_GK_CM_129E 4512 CGCS2000_GK_CM_135E 7、CGCS2000 3度分 4513 CGCS2000_3_Degree_GK_Zone_25 4514 CGCS2000_3_Degree_GK_Zone_26 4515 CGCS2000_3_Degree_GK_Zone_27 4516 CGCS2000_3_Degree_GK_Zone_28 4517 CGCS2000_3_Degree_GK_Zone_29 4518 CGCS2000_3_Degree_GK_Zone_30 4519 CGCS2000_3_Degree_GK_Zone_31 4520 CGCS2000_3_Degree_GK_Zone_32 4521 CGCS2000_3_Degree_GK_Zone_33 4522 CGCS2000_3_Degree_GK_Zone_34 4523 CGCS2000_3_Degree_GK_Zone_35 4524 CGCS2000_3_Degree_GK_Zone_36 4525 CGCS2000_3_Degree_GK_Zone_37 4526 CGCS2000_3_Degree_GK_Zone_38 4527 CGCS2000_3_Degree_GK_Zone_39 4528 CGCS2000_3_Degree_GK_Zone_40 4529 CGCS2000_3_Degree_GK_Zone_41 4530 CGCS2000_3_Degree_GK_Zone_42 4531 CGCS2000_3_Degree_GK_Zone_43 4532 CGCS2000_3_Degree_GK_Zone_44 4533 CGCS2000_3_Degree_GK_Zone_45 4534 CGCS2000_3_Degree_GK_CM_75E 4535 CGCS2000_3_Degree_GK_CM_78E 4536 CGCS2000_3_Degree_GK_CM_81E 4537 CGCS2000_3_Degree_GK_CM_84E 4538 CGCS2000_3_Degree_GK_CM_87E 4539 CGCS2000_3_Degree_GK_CM_90E 4540 CGCS2000_3_Degree_GK_CM_93E 4541 CGCS2000_3_Degree_GK_CM_96E 4542 CGCS2000_3_Degree_GK_CM_99E 4543 CGCS2000_3_Degree_GK_CM_102E 4544 CGCS2000_3_Degree_GK_CM_105E 4545 CGCS2000_3_Degree_GK_CM_108E 4546 CGCS2000_3_Degree_GK_CM_111E 4547 CGCS2000_3_Degree_GK_CM_114E 4548 CGCS2000_3_Degree_GK_CM_117E 4549 CGCS2000_3_Degree_GK_CM_120E 4550 CGCS2000_3_Degree_GK_CM_123E 4551 CGCS2000_3_Degree_GK_CM_126E 4552 CGCS2000_3_Degree_GK_CM_129E 4553 CGCS2000_3_Degree_GK_CM_132E 4554 CGCS2000_3_Degree_GK_CM_135E 8、New_Beijing_Gauss 6度分 4568 New_Beijing_Gauss_Kruger_Zone_13 4569 New_Beijing_Gauss_Kruger_Zone_14 4570 New_Beijing_Gauss_Kruger_Zone_15 4571 New_Beijing_Gauss_Kruger_Zone_16 4572 New_Beijing_Gauss_Kruger_Zone_17 4573 New_Beijing_Gauss_Kruger_Zone_18 4574 New_Beijing_Gauss_Kruger_Zone_19 4575 New_Beijing_Gauss_Kruger_Zone_20 4576 New_Beijing_Gauss_Kruger_Zone_21 4577 New_Beijing_Gauss_Kruger_Zone_22 4578 New_Beijing_Gauss_Kruger_Zone_23 4579 New_Beijing_Gauss_Kruger_CM_75E 4580 New_Beijing_Gauss_Kruger_CM_81E 4581 New_Beijing_Gauss_Kruger_CM_87E 4582 New_Beijing_Gauss_Kruger_CM_93E 4583 New_Beijing_Gauss_Kruger_CM_99E 4584 New_Beijing_Gauss_Kruger_CM_105E 4585 New_Beijing_Gauss_Kruger_CM_111E 4586 New_Beijing_Gauss_Kruger_CM_117E 4587 New_Beijing_Gauss_Kruger_CM_123E 4588 New_Beijing_Gauss_Kruger_CM_129E 4589 New_Beijing_Gauss_Kruger_CM_135E 9、New_Beijing_Gauss 3度分 4652 New_Beijing_3_Degree_Gauss_Kruger_Zone_25 4653 New_Beijing_3_Degree_Gauss_Kruger_Zone_26 4654 New_Beijing_3_Degree_Gauss_Kruger_Zone_27 4655 New_Beijing_3_Degree_Gauss_Kruger_Zone_28 4656 New_Beijing_3_Degree_Gauss_Kruger_Zone_29 4766 New_Beijing_3_Degree_Gauss_Kruger_Zone_30 4767 New_Beijing_3_Degree_Gauss_Kruger_Zone_31 4768 New_Beijing_3_Degree_Gauss_Kruger_Zone_32 4769 New_Beijing_3_Degree_Gauss_Kruger_Zone_33 4770 New_Beijing_3_Degree_Gauss_Kruger_Zone_34 4771 New_Beijing_3_Degree_Gauss_Kruger_Zone_35 4772 New_Beijing_3_Degree_Gauss_Kruger_Zone_36 4773 New_Beijing_3_Degree_Gauss_Kruger_Zone_37 4774 New_Beijing_3_Degree_Gauss_Kruger_Zone_38 4775 New_Beijing_3_Degree_Gauss_Kruger_Zone_39 4776 New_Beijing_3_Degree_Gauss_Kruger_Zone_40 4777 New_Beijing_3_Degree_Gauss_Kruger_Zone_41 4778 New_Beijing_3_Degree_Gauss_Kruger_Zone_42 4779 New_Beijing_3_Degree_Gauss_Kruger_Zone_43 4780 New_Beijing_3_Degree_Gauss_Kruger_Zone_44 4781 New_Beijing_3_Degree_Gauss_Kruger_Zone_45 4782 New_Beijing_3_Degree_Gauss_Kruger_CM_75E 4783 New_Beijing_3_Degree_Gauss_Kruger_CM_78E 4784 New_Beijing_3_Degree_Gauss_Kruger_CM_81E 4785 New_Beijing_3_Degree_Gauss_Kruger_CM_84E 4786 New_Beijing_3_Degree_Gauss_Kruger_CM_87E 4787 New_Beijing_3_Degree_Gauss_Kruger_CM_90E 4788 New_Beijing_3_Degree_Gauss_Kruger_CM_93E 4789 New_Beijing_3_Degree_Gauss_Kruger_CM_96E 4790 New_Beijing_3_Degree_Gauss_Kruger_CM_99E 4791 New_Beijing_3_Degree_Gauss_Kruger_CM_102E 4792 New_Beijing_3_Degree_Gauss_Kruger_CM_105E 4793 New_Beijing_3_Degree_Gauss_Kruger_CM_108E 4794 New_Beijing_3_Degree_Gauss_Kruger_CM_111E 4795 New_Beijing_3_Degree_Gauss_Kruger_CM_114E 4796 New_Beijing_3_Degree_Gauss_Kruger_CM_117E 4797 New_Beijing_3_Degree_Gauss_Kruger_CM_120E 4798 New_Beijing_3_Degree_Gauss_Kruger_CM_123E 4799 New_Beijing_3_Degree_Gauss_Kruger_CM_126E 4800 New_Beijing_3_Degree_Gauss_Kruger_CM_129E 4822 New_Beijing_3_Degree_Gauss_Kruger_CM_135E ``` * * * ## 坐标转换 ### wgs84坐标、火星坐标 (GCJ-02)也叫国测局坐标系、百度坐标 (BD-09)相互转换 Github地址:[https://github.com/wandergis/coordtransform](https://github.com/wandergis/coordtransform) npm地址:[https://www.npmjs.com/package/coordtransform](https://www.npmjs.com/package/coordtransform) 用法: ```html coordTransform

请按F12打开控制台查看结果

``` ### 不同EPSG之间坐标相互转换 Proj4js是一个JavaScript类库,其主要功能就是将点坐标从一个坐标系转换为另一个坐标系。 前端添加Proj4js有三种方式: 从[http://trac.osgeo.org/proj4js/wiki/Download](http://trac.osgeo.org/proj4js/wiki/Download)下载,获取产品包中dist/proj4.js文件。 引入CDN上的Proj4js:https://cdnjs.com/libraries/proj4js。 也可以直接使用 `npm install proj4` 进行安装。 使用: 首先,定义需要转换的坐标系 ```js proj4.defs([["EPSG:4549","+proj=tmerc +lat_0=0 +lon_0=120 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs"]]) ``` 调用proj4方法 ```js proj4("EPSG:4326", "EPSG:4549", [lat, lng]); ``` 转换完成! **需要说明的是:由于不同坐标系可能椭球体不同,直接转换会有一定误差!!!** # CAD图与互联网地图叠加实现思路 ## 一、有坐标系的CAD图自动叠加到互联网地图上 ### 1、互联图地图叠加CAD图 流程如下: - (1)确定互联图地图坐标系,如果不是wgs84坐标系,是火星坐标或百度坐标,需要转换成wgs86坐标,也就是epsg4326坐标系; - (2)确定CAD图坐标系,通过X的前两位来获取属于EPSG哪个坐标系。代码如下: ```js // 以X前两位为39为例 // 北京54坐标 vjmap.transform.getEpsgParam(vjmap.transform.EpsgCrsTypes.Beijing54, 39).epsg // 西安80坐标 vjmap.transform.getEpsgParam(vjmap.transform.EpsgCrsTypes.Xian80, 39).epsg // 国家2000坐标 vjmap.transform.getEpsgParam(vjmap.transform.EpsgCrsTypes.CGCS2000, 39).epsg ``` - (3)通过调用 Proj4js 将互联网地图坐标转换成CAD图坐标 - (4)通过CAD图坐标,在后台通过WMS获取该瓦片相应的图片,叠加即可。 效果如下:https://vjmap.com/demo/#/demo/map/web/04webWms ![image-20220518083924345](https://vjmap.com/blogimages/image-20220518083924345.png) ### 2、CAD图叠加互联图地图 流程如下: - (1)确定CAD图坐标系,通过X的前两位来获取属于EPSG哪个坐标系。方法同上。 - (2) 通过调用 Proj4js 将CAD图坐标转换成转换成wgs86坐标,也就是epsg4326坐标系; - (3)确定互联图地图坐标系,如果不是wgs84坐标系,是火星坐标或百度坐标,需要将wgs84坐标转换成火星坐标或百度坐标转换 - (4)通过互联网坐标去获取该范围内的瓦片,但这里有个问题就是一般的互联网地图只提供了WMTS服务,没有提供WMS服务,所以如果要实现的话,只能自己去后台写服务实现了。 在唯杰地图vjmap中,可以使用以下代码实现 ```js // 增加天地图图底图 const addTiandituMap = (isRoadway) => { const tileUrl = svc.webMapUrl({ tileCrs: "wgs84", tileUrl: [ isRoadway ? "https://t{s}.tianditu.gov.cn/DataServer?T=vec_w&X={x}&Y={y}&L={z}&tk={t}" : /* 如果用影像 */"https://t{s}.tianditu.gov.cn/DataServer?T=img_w&X={x}&Y={y}&L={z}&tk={t}", "https://t{s}.tianditu.gov.cn/DataServer?T=cva_w&X={x}&Y={y}&L={z}&tk={t}" ], tileSize: 256, tileRetina: 1, tileMaxZoom: 18, tileShards: "1,2,3,4,5,6", tileToken: [ "6d53378dc5f7dbef8d84ffdd2b54139b", // 有可能有一个token会导致请求过多而返回失败,可以多写几个,失败时会尝试后面的token "69eb2fa0de3b2a668f1ef603a3f8bc73" ], tileFlipY: false, mapbounds: res.bounds, srs: "EPSG:4527", // 可通过前两位获取 vjmap.transform.getEpsgParam(vjmap.transform.EpsgCrsTypes.CGCS2000, 39).epsg // 因为sys_cad2000这个图只有6位,没有带系。需要在坐标转换前平移下带系 https://blog.csdn.net/thinkpang/article/details/124172626 fourParameterBefore: "39000000,0,1,0" }) map.addSource('web-tdt-source', { 'type': 'raster', 'tiles': [ tileUrl ], 'tileSize': 256 }); map.addLayer({ 'id': 'web-tdt-layer', 'type': 'raster', 'source': 'web-tdt-source', 'paint': { "raster-opacity": 1 } } ); let layers = map.getStyle().layers // 把这个图层放至所有图层的最下面 map.moveLayer('web-tdt-layer', layers[0].id) } ``` 效果如下:[https://vjmap.com/demo/#/demo/map/web/04webcadmap](https://vjmap.com/demo/#/demo/map/web/04webcadmap) ![image-20220518084706983](https://vjmap.com/blogimages/image-20220518084706983.png) ## 二、没有坐标系的CAD图自动叠加到互联网地图上 没有坐标系的图,不能通过地理坐标转换,这时候就需要人为的选定公共对应点,然后通过四参数来计算平移、旋转、缩放来叠加了。 四参数的原理的相关计算可参考这博文 [https://blog.csdn.net/qq_42811161/article/details/107809777](https://blog.csdn.net/qq_42811161/article/details/107809777) 在唯杰地图vjmap中,可以使用以下代码实现 ```js // cad上面的点坐标 let cadPoints = [ vjmap.geoPoint([39760.07407, 237424.52134]), vjmap.geoPoint([39006.60468, 237808.49494749]), vjmap.geoPoint([38823.92918, 235003.98994]), vjmap.geoPoint([37885.55424, 235452.80893]) ]; // 在百度图上面拾取的与上面的点一一对应的坐标 let baiduPoints = [ vjmap.geoPoint([113.292983, 23.206979]), vjmap.geoPoint([113.285631, 23.210304]), vjmap.geoPoint([113.283897, 23.185047]), vjmap.geoPoint([113.274716, 23.188962]) ] // 百度点坐标转wgs84坐标,如果不是百度图上,或高德上面拾取的,坐标类型应为 CRSTypes.GCJ02,天地图上拾取的,则不需要转换直接为wgs84坐标 let wgs84Points = baiduPoints.map(e => vjmap.geoPoint(vjmap.transform.convert([e.x, e.y],vjmap.transform.CRSTypes.BD09, vjmap.transform.CRSTypes.WGS84))); // 通过坐标参数求出四参数 let fourparam = vjmap.coordTransfromGetFourParamter(wgs84Points, cadPoints, true); // 这里不需要考虑旋转 let wmsurl = svc.wmsTileUrl({ mapid: cadMapId, version:"v1", layers: style.stylename, srs: "EPSG:3857", // 底图是天地图坐标系 crs: "EPSG:4326", // 因为四参数的输入是wgs84,需要先要把3857转4326 fourParameter: [fourparam.dx, fourparam.dy, fourparam.scale, fourparam.rotate] // 转4326后,再调用四参数进行转换,转cad坐标 }) ``` 效果如下:[https://vjmap.com/demo/#/demo/map/web/05fourparam](https://vjmap.com/demo/#/demo/map/web/05fourparam) ![image-20220518085912118](https://vjmap.com/blogimages/image-20220518085912118.png) # 在线体验地址 - (1) 打开 [唯杰地图云端图纸管理平台](https://vjmap.com/app/cloud) https://vjmap.com/app/cloud) - (2) 上传打开 CAD图形 - (3) 在工具栏中选择 与互联地图自动叠加或四参数叠加 ![image-20220518090208344](https://vjmap.com/blogimages/image-20220518090208344.png) - (4) 参数设置 ![image-20220518090249492](https://vjmap.com/blogimages/image-20220518090249492.png) ![image-20220518090523308](https://vjmap.com/blogimages/image-20220518090523308.png) - (5) 效果 ![image-20220518090549428](https://vjmap.com/blogimages/image-20220518090549428.png) 可在 [唯杰地图云端图纸管理平台](https://vjmap.com/app/cloud) 上传想要叠加的CAD图在线体验! --- # 通过CAD图中的坐标确定坐标系 # 国内常见的坐标系 坐标系分为以下两种: 地理坐标系(Geographic Coordinate System, GCS) 投影坐标系(Projected Coordinate System, PCS) ## 那什么是地理坐标系统呢? 地理坐标系:为球面坐标。 参考平面地是椭球面,坐标单位:经纬度。 以地球参考为例,坐标系就相当于整个地球,而地理坐标系就是椭球状的完整的立体地球。所以我们可以看到生活中的数据与地图是一定含有地理坐标系的,因为他是客观上来说确确实实存在的。 ## 那什么是投影坐标系统呢? 投影坐标系:为平面坐标。参考平面地是水平面,坐标单位:米、千米等。 这就相当于把地球当成一个橘子,然后一刀切开,把橘子皮平面铺开,这个过程就是地理投影的过程。严谨一点来说就是建立一个参考系统,按照对应位置把椭球体投射到平面。 ## **那坐标系统主要有那些呢?** - **北京54**是参心坐标系,大地原点是苏联的普尔科沃,且1954年北京坐标系所提供的大地点成功是局部平差的结果;**新北京54坐标系**是在1980西安坐标系的基础上,将基于iugg1975年椭球的1980西安坐标系平差成果整体转换为基于克拉索夫斯基椭球的坐标值,并平移1980西安坐标系坐标原点而建立起来的。 - **西安1980**也属参心坐标系,大地原点为我国中部地区的陕西省泾阳县永乐镇,大地高程以1956年青岛所测的黄海平均海水面为基准; - **国家2000**坐标系是适用于卫星定位系统的坐标系,在精度要求不高的情况下可以定义WGS_84坐标系。 - **WGS-84坐标系** WGS-84坐标系是一种国际上采用的地心坐标系。 - **GCJ02经纬度投影** 也称火星坐标系, 是一种国家保密插件,也叫做加密插件或者加偏或者SM模组,是对真实地图或者导航坐标系统进行人为的加偏处理,按照一定的加偏算法,将真实的坐标加密成虚假的坐标。加偏处理不是线性的加偏,所以各地的偏移情况都会有所不同。加密后的坐标也常被人称为火星坐标系统。比如,在**谷歌地图API,高德地图API,腾讯地图API**上取到的,都是GCJ-02坐标,都是通用的,也适用于大部分地图API产品,以及他们的地图产品 - **BD09经纬度投影** BD09经纬度投影属于百度坐标系,只适用于百度地图的相关产品,它是在标准经纬度的基础上进行GCJ-02加偏之后,再加上百度自身的加偏算法,也就是在标准经纬度的基础之上进行了两次加偏。 ## 投影分带 **投影分带使用的规定** - **6°**:分带在1:2.5万到1:50万时 - **3°**:分带在大于1:1万地形图中 ## EPSG EPSG 用于标识不同的地理空间参考系统,包括坐标系统、地理坐标系、投影坐标系等。这些标识符可用于许多应用程序和地理信息系统软件,以确保数据在不同系统之间的正确转换和处理。现在,EPSG已被Open Geospatial Consortium(OGC)承认并管理,成为了一个全球性的标准。EPSG 是将常用的坐标系、投影、地理坐标系等地理空间参考系统的名称、参数、定义等信息进行标准化,并赋予一个唯一编码。 在CAD图与互联网地址叠加显示或坐标相互转换时,需要确定CAD的EPSG,才能进行坐标转换 。 # 如何根据CAD坐标来确定坐标系 在图内,CAD图中的坐标如果是地理平面坐标的话,坐标一般为投影坐标系统,单位为米。 坐标为`北京54`、`西安80`、`国家2000`的一种(地方坐标系除外,以下所讲不适合地方坐标系) 这时候我们可以按如下流程来确定: 1、获取CAD图的一个点的坐标x,y 2、如果坐标X的位数为8位或6位(整数部分),y的位数为7位(整数部分)。如果不满足此条件,表示此坐标不是地理平面坐标。 3、如果X是8位,表示坐标中包含带号数据,通过确定是`北京54`、`西安80`、`国家2000`的哪一个坐标系就能确定EPSG。 4、如果X是6位,表示坐标中没有带号数据,这时首先要确定是`北京54`、`西安80`、`国家2000`的哪一个坐标系,另外还需要根据地图所在位置的经纬度位置才能确定EPSG。 # 根据坐标在线计算坐标系示例 [https://vjmap.com/demo/#/demo/map/web/03webzgetepsg](https://vjmap.com/demo/#/demo/map/web/03webzgetepsg) ## X是8位,Y是7位的CAD坐标 ![image-20230604191536286](https://vjmap.com/blogimages/image-20230604191536286.png) 在坐标中输入CAD图的一个坐标值37526356.44561,4377447.23355,再选择一个坐标系,如2000坐标系,就能唯一确定EPSGo 4525,为三度带坐标。 ## X是6位,Y是7位的CAD坐标 ![image-20230604191903427](https://vjmap.com/blogimages/image-20230604191903427.png) 这时候需要把地图定位至CAD图所在的互联网地图所在位置,点击获取地图中心点经纬度,获取当前的经纬度坐标,然后选择`北京54`、`西安80`、`国家2000`的哪一个坐标系、再确定是三度带坐标还是六度带坐标,就能确定EPSG了。 可以点击此网址输入坐标在线计算坐标系 [https://vjmap.com/demo/#/demo/map/web/03webzgetepsg](https://vjmap.com/demo/#/demo/map/web/03webzgetepsg) --- # 高德地图与CAD图叠加显示 # 方法一:高德SDK加载CAD的WMS图层 ## 原理 ### 高德SDK 高德SDK提供了行业标准图层符合 OGC 标准或者行业通行规范的的图层类型 #### AMap.TileLayer.WMS 用于加载OGC标准的WMS地图服务的一种图层类,仅支持EPSG3857坐标系统的WMS图层。 [查看 WMS的OGC标准](http://www.opengeospatial.org/standards/wms)。 new AMap.TileLayer.WMS(opts: WMSLayerOptions) 参数说明: opts `(WMSLayerOptions)` 默认图层参数 | Name | Description | | :----------------------------------------------- | :----------------------------------------------------------- | | opts.url `String` | wmts服务的url地址,如:' https://services.arcgisonline.com/arcgis/rest/services/'+ 'Demographics/USA_Population_Density/MapServer/WMTS/' | | opts.blend `Boolean` | 地图级别切换时,不同级别的图片是否进行混合,如图层的图像内容为部分透明请设置为false | | opts.params `Object` | OGC标准的WMS地图服务的GetMap接口的参数,包括VERSION、LAYERS、STYLES、FORMAT、TRANSPARENT等, CRS、BBOX、REQUEST、WIDTH、HEIGHT等参数请勿添加,例如: { LAYERS: 'topp:states', VERSION:'1.3.0', FORMAT:'image/png' } | | opts.zooms `[Number, Number]` (default `[2,30]`) | 支持的缩放级别范围,默认范围 [2-30] | | opts.opacity `Number` (default `1`) | 透明度,默认 1 | | opts.visible `Boolean` (default `true`) | 是否显示,默认 true | | opts.zIndex `Number` (default `4`) | 图层叠加的顺序值,1 表示最底层。默认 zIndex:4 | ### CAD的WMS图层 `唯杰地图VJMAP`为`CAD`图或`自定义地图格式`WebGIS`可视化`显示开发提供的一站式解决方案,支持的格式如常用的`AutoCAD`的`DWG`格式文件、`GeoJSON`等常用`GIS`文件格式,它使用WebGL`矢量图块`和`自定义样式`呈现交互式地图, 提供了全新的`大数据可视化`、`实时流数据`可视化功能。 唯杰地图[https://vjmap.com/](https://vjmap.com/)无需转换可直接打开CAD的DWG格式的图形,并提供了WMS服务接口。 其接口文档为: ```typescript /** * wms服务url地址接口 */ export interface IWmsTileUrl { /** 地图ID(为空时采用当前打开的mapid), 为数组时表时同时请求多个. */ mapid?: string | string[]; /** 地图版本(为空时采用当前打开的地图版本). */ version?: string | string[]; /** 图层名称(为空时采用当前打开的地图图层名称). */ layers?: string | string[]; /** 范围,缺省{bbox-epsg-3857}. (如果要获取地图cad一个范围的wms数据无需任何坐标转换,将此范围填cad范围,srs,crs,mapbounds填为空).*/ bbox?: string; /** 当前坐标系,缺省(EPSG:3857). */ srs?: string; /** cad图的坐标系,为空的时候由元数据坐标系决定. */ crs?: string | string[]; /** 地理真实范围,如有值时,srs将不起作用 */ mapbounds?: string; /** 宽. */ width?: number; /** 高. */ height?: number; /** 是否透明. */ transparent?: boolean; /** 四参数(x偏移,y偏移,缩放,旋转弧度),可选,对坐标最后进行修正*/ fourParameter?: string | string[]; /** 是否是矢量瓦片. */ mvt?: boolean; /** 是否考虑旋转,在不同坐标系中转换是需要考虑。默认自动考虑是否需要旋转. */ useImageRotate?: boolean; /** 旋转时图像处理算法. 1或2,默认自动选择(旋转时有用)*/ imageProcessAlg?: number; /** 当前互联网底图地图类型 WGS84(84坐标,如天地图,osm), GCJ02(火星坐标,如高德,腾讯地图), BD09LL(百度经纬度坐标,如百度地图), BD09MC(百度墨卡托米制坐标,如百度地图)*/ webMapType?: "WGS84" | "GCJ02" | "BD09LL" | "BD09MC"; } ``` ## 实现 参考高德提拱的WMS的示例代码 [https://developer.amap.com/demo/jsapi-v2/example/thirdlayer/wms-layer ](https://developer.amap.com/demo/jsapi-v2/example/thirdlayer/wms-layer ) ,实现代码如下 ```javascript let map = new AMap.Map('map', { zoom: 15, center: [106.166187, 29.44102] }); // 调用唯杰地图服务对象 let svc = new vjmap.Service(env.serviceUrl, env.accessToken) // 打开地图 let mapId = "sys_cadcesium"; let res = await svc.openMap({ mapid: mapId, // 地图ID mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开 style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式 }) if (res.error) { // 如果打开出错 message.error(res.error) } let layer = res.layer;//图层样式名 let cadEpsg = "EPSG:4544";// cad图的espg代号 // 增加唯杰地图中cad的wms图层 let wmsUrl = svc.wmsTileUrl({ mapid: mapId, // 地图id layers: layer, // 图层名称 bbox: '', // bbox这里不需要 srs: "EPSG:3857", // crs: cadEpsg, webMapType: "GCJ02" }) function getQueryStringArgs(url) { let theRequest = {}; let idx = url.indexOf("?"); if (idx != -1) { let str = url.substr(idx + 1); let strs = str.split("&"); for (let i = 0; i < strs.length; i++) { let items = strs[i].split("="); theRequest[items[0]] = items[1]; } } return theRequest; } let wms = new AMap.TileLayer.WMS({ url: wmsUrl.substr(0, wmsUrl.indexOf("?")), blend: true, tileSize: 256, params: getQueryStringArgs(wmsUrl) }); wms.setMap(map); ``` 效果如下 ![image-20220914205123373](https://vjmap.com/blogimages/image-20220914205123373.png) 在线体验地址: [https://vjmap.com/demo/#/demo/map/web/08gaodeCadWmsLayer](https://vjmap.com/demo/#/demo/map/web/08gaodeCadWmsLayer) 优点: (1)能利用高德SDK开发,如果之前的项目是基于高德SDK开发的, 工作量不大 ​ (2) 能用高德原生的地图数据,互联网地图数据丰富效果好 缺点: ​ (1) 不能用唯杰地图vjmap的SDK进行开发,一些与CAD相关的功能开发受限。 ​ (2) 由于高德地图的级别有限,导致CAD图缩放至一定级别后,不能再放大。如果图纸大的话,会导致级别不够的问题 # 方法二: 唯杰地图SDK中高德地图上加载CAD图 ## 原理 ​ 唯杰地图SDK中也可以显示互联网地图。可以利用唯杰地图同时显示高德地图和CAD图。此方法为互联网地图做为底图,CAD做为一个WMS图层进行叠加。唯杰地图本身没有互联网地图数据,加载互联网地图是利用了在线公共地图瓦片数据地址 https://blog.csdn.net/weixin_45728126/article/details/119040004 ## 实现 ```javascript // --高德地图与CAD图叠加[高德地图为底图]--高德地图与CAD图叠加,以高德地图为坐标系 let svc = new vjmap.Service(env.serviceUrl, env.accessToken) // 根据地图范围建立经纬度投影坐标系 let prj = new vjmap.LnglatProjection(); // 地图对象 let map = new vjmap.Map({ container: 'map', // DIV容器ID style: { version: svc.styleVersion(), glyphs: svc.glyphsUrl(), sources: { gaode: { type: 'raster', tiles: ["https://webrd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}"], } }, layers: [{ id: 'gaode', type: 'raster', source: 'gaode', }] }, center: [0, 0], zoom: 12, pitch: 0, renderWorldCopies: false // 不显示多屏地图 }); // 关联服务对象和投影对象 map.attach(svc, prj); await map.onLoad(); // 先打开cad图 let mapId = "sys_cadcesium"; let res = await svc.openMap({ mapid: mapId, // 地图ID mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开 style: vjmap.openMapLightStyle() // div为深色背景颜色时,这里也传深色背景样式 }) if (res.error) { // 如果打开出错 message.error(res.error) } let layer = res.layer;//图层样式名 let cadEpsg = "EPSG:4544";// cad图的espg代号 // 增加cad的wms图层 let wmsUrl = svc.wmsTileUrl({ mapid: mapId, // 地图id layers: layer, // 图层名称 srs: "EPSG:3857", crs: cadEpsg, webMapType: "GCJ02" // 底图是高德地图,所以要选GCJ02,如果是天地图,可以不用填此项 }) map.addSource('wms-test-source', { 'type': 'raster', 'tiles': [ wmsUrl ], 'tileSize': 256 }); map.addLayer({ 'id': 'wms-test-layer', 'type': 'raster', 'source': 'wms-test-source', 'paint': { "raster-opacity": 1 } } ); ``` 在线体验地址: [https://vjmap.com/demo/#/demo/map/web/04webWmsGaode](https://vjmap.com/demo/#/demo/map/web/04webWmsGaode) 优点: (1)能利用唯杰地图的SDK同时加载高德地图和CAD图 (2) CAD相关的功能开发不受限制 缺点: (1)由于加载互联网地图是利用了在线公共地图瓦片数据地址,所以互联网地图数据不如高德SDK开发的效果好 ​ (2) 由于高德地图的级别有限,导致CAD图缩放至一定级别后,不能再放大。如果图纸大的话,会导致级别不够的问题 ​ (3) 因为底图是高德地图,所以整个地图的坐标系是高德的火星坐标,如果业务数据是CAD数据的话,都需要通过坐标转换才能加载到图上。 # 方法三: 唯杰地图SDK中CAD图上加载高德地图 ## 原理 其原理同方法二。不同的是,此方法是以CAD图做为底图,高德地图做为一个图层去叠加CAD图层。 但是唯杰地图本身没有互联网地图数据,加载互联网地图是利用了在线公共地图瓦片数据地址,其地址没有提供互联网地图的WMS服务。所以此方法需要唯杰地图后台根据前端传递的高德瓦片WMTS地址,根据一定的算法转成WMS服务去适配CAD图。 ## 实现 ```js // --CAD图与互联网图叠加--CAD图与互联网图叠加显示,以CAD图为坐标系 // 假设我们希望CAD全图显示是对应级别为8级,这样地图缩小后,互联网地图还会显示 let initZoom = 8; let style = { backcolor: 0, // div为深色背景颜色时,这里也传深色背景样式 clipbounds: Math.pow(2, initZoom) // 只传值,不传范围,表示之前的范围放大多少倍 } let svc = new vjmap.Service(env.serviceUrl, env.accessToken) let res = await svc.openMap({ mapid: "sys_cad2000", mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开 style: style }) if (res.error) { message.error(res.error) } let mapExtent = vjmap.GeoBounds.fromString(res.bounds); let prj = new vjmap.GeoProjection(mapExtent); let center = mapExtent.center(); let map = new vjmap.Map({ container: 'map', // container ID style: svc.rasterStyle(), center: prj.toLngLat(center), zoom: initZoom, renderWorldCopies: false }); map.attach(svc, prj); await map.onLoad(); // 增加高德地图底图 const addGaodeMap = async (isRoadway) => { const tileUrl = svc.webMapUrl({ tileCrs: "gcj02", tileUrl: isRoadway ? [ "https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}" ] : /* 如果用影像 */ [ "https://webst0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=6&x={x}&y={y}&z={z}", "https://webst0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}" ], tileSize: 256, tileRetina: 1, tileMaxZoom: 18, tileShards: "1,2,3,4", tileToken: "", tileFlipY: false, mapbounds: res.bounds, srs: "EPSG:4527",// 可通过前两位获取 vjmap.transform.getEpsgParam(vjmap.transform.EpsgCrsTypes.CGCS2000, 39).epsg // 因为sys_cad2000这个图只有6位,没有带系。需要在坐标转换前平移下带系 https://blog.csdn.net/thinkpang/article/details/124172626 fourParameterBefore: "39000000,0,1,0" }) map.addSource('web-gaode-source', { 'type': 'raster', 'tiles': [ tileUrl ], 'tileSize': 256 }); map.addLayer({ 'id': 'web-gaode-layer', 'type': 'raster', 'source': 'web-gaode-source', 'paint': { "raster-opacity": 1 } } ); let layers = map.getStyle().layers // 把这个图层放至所有图层的最下面 map.moveLayer('web-gaode-layer', layers[0].id) ``` 互联网地图旋转后与CAD地图叠加效果图 ![image-20220914215514374](https://vjmap.com/blogimages/image-20220914215514374.png) 在线体验地址 [https://vjmap.com/demo/#/demo/map/web/04webcadmap](https://vjmap.com/demo/#/demo/map/web/04webcadmap) [https://vjmap.com/demo/#/demo/map/web/05fourparamrotatecad](https://vjmap.com/demo/#/demo/map/web/05fourparamrotatecad) 优点: (1)能利用唯杰地图的SDK同时加载高德地图和CAD图 (2) CAD相关的功能开发不受限制 (3) 因为此方法是以CAD图为底图,所以坐标系和CAD图为坐标系。如果业务数据是CAD的数据,这样无需进行坐标转换就能直接显示在地图上,非常方便。 (4) 此方法不受高德地图的级别限制,即使图纸大的话,也不会造成级别不够的问题 缺点: (1)由于加载互联网地图是利用了在线公共地图瓦片数据地址,所以互联网地图数据不如高德SDK开发的效果好 --- # Cesium与CAD图叠加显示 Cesium是一个非常优秀的三维地球GIS引擎, 开源且免费, 能够加载各种符合标准的地图图层,瓦片图、矢量图等都支持。在实际中经常会遇到需要将CAD的图纸叠加到cesium上面,与地表地形叠加显示查看。那如何实现在Cesium中实现与CAD的DWG图叠加显示分析呢? # 技术分析 业内一般的做法步骤为: - (1) 在AutoCAD中把DWG图导出为DXF明码格式文件 - (2) 利用第三方工具如GDAL,把dxf转为shp等GIS数据格式 - (3) 利用开源的gis服务,如geoserver或mapserver把shp文件发布成gis服务 - (4) 在cesium中加载gis服务的瓦片图层 上面的方案的优点很明显,基本上利用开源的方案能把流程跑通。但在实际项目中缺点也很明显,主要是在步骤 (1)(2), 因为dwg是私有格式,通过dwg转出成dxf再转化成shp文件时,会丢失图中的很多数据,CAD数据类型较为丰富,支持点、块符号、线、面、多段线、椭圆、块、文字等多种数据类型,而转换到GIS中,只转换为点、线、面、注记等类型,这使得CAD图形数据不能很好的满足GIS的要求,如:CAD中的Text数据类型,直接转换后只转换为GIS中的Point。通过这种转换去绘制,会导致和原始CAD图形绘制不太一样, 同时CAD里面有线型、字体也会导致绘制上的差异性。 唯杰地图vjmap完全兼容AutoCAD格式的DWG文件,无需通过转换可直接发布成gis的WMS格式,可有效解决上面问题。实现步骤为: - (1) 上传DWG图形,利用唯杰地图发布为WMS图层 - (2) 在cesium中加载wms瓦片图层 # 实现 先上效果图,(红色的线为cad图层) ![cesiumcad.gif](https://vjmap.com/blogimages/cesiumcad.gif) ## 具体步骤: - 确定CAD图的坐标系 在打开的CAD图中,随便找了图中的一个点坐标,坐标为”614802.89177, 3257472.36997” 那怎么确定这个坐标是哪个坐标系呢? 如果已经知道坐标能不能倒推坐标系呢? 可以通过 [通过CAD图中的坐标确定坐标系](/guide/cadepsg.html) 这个工具。也可以用如下方法,这里我们只讨论高斯克吕格投影。 假设已经知道数据的投影是高斯克吕格,怎样判定是6°带还是3°带?怎样判定中央经线是多少呢? 如果坐标范围是6位,我们可以判定坐标系不带带号的。如果Y值范围在333000m~667000m,可能是3°分带法;如果Y值范围在166 000m~834000m,一定是6°分带法。如果取值范围重合的部分有可能3°分带法也可能在6°分带法。 如果坐标范围是8位,我们可以判定坐标系是以带号命名,并且根据带号可以知道是3°带还是6°带。我们国家的经度范围大致在73°33′E~105°05′E,所以如果是以带号命名,带号在13~23带之间的,说明是6°分带法;带号在25~45带之间的,说明是3°分带法。 在这个图中,坐标为”614802“为6位,大概率能确定为省去了带系的3°分带坐标。并且根据图中图框中坐标系写的为2000坐标系,同时地点为重庆附近。而重庆的经度差不多是以106开头。我们去epsg官网查询得知,该坐标系应该是"EPSG:4544". ![image-20220801192221302](https://vjmap.com/blogimages/image-20220801192221302.png) - 上传打开CAD图 在[唯杰地图云端图纸管理平台](https://vjmap.com/app/cloud) 上传CAD图形。 - 在cesium增加cad图层 ~~~javascript let imageryProvider= new Cesium.WebMapTileServiceImageryProvider({ url: "http://t0.tianditu.gov.cn/img_w/wmts?tk=3346bb6ad29b5013c5952cf1117b80e9", layer: "img", style: "default", tileMatrixSetID: "w", format: "tiles", maximumLevel: 14, }); let viewer = new Cesium.Viewer('cesiumContainer', { imageryProvider: imageryProvider, contextOptions: { webgl: { alpha: true } }, selectionIndicator: false, animation: false, //是否显示动画控件 baseLayerPicker: false, //是否显示图层选择控件 geocoder: false, //是否显示地名查找控件 timeline: false, //是否显示时间线控件 sceneModePicker: false, //是否显示投影方式控件 navigationHelpButton: false, //是否显示帮助信息控件 infoBox: false, //是否显示点击要素之后显示的信息 fullscreenButton: false, shouldAnimate: true //动画播放 }); var layers = viewer.scene.imageryLayers; layers.addImageryProvider( new Cesium.WebMapServiceImageryProvider({ // 通过vjmap的wms加载cad图 url: `https://vjmap.com/server/api/v1/map/cmd/wms/ sys_cadcesium/v1?mapbounds=&format=image/png&service=WMS&version=1.1.1&request=GetMap&srs=EPSG:4326&transparent=true&width=256&height=256&layers=se92105f73&crs=EPSG:4544&fourParameter=&token=your token`, }) ); ~~~ 上面的代码好像简单明了,可实际中发现,在cesium中,cad可以和天地图影像叠加。但在放大的过程中,发现wms生成的瓦片有错开的问题。如下图所示: ![image-20220801193313571](https://vjmap.com/blogimages/image-20220801193313571.png) ## 问题分析 在cesium中,是WGS84坐标系,而CAD图是4544坐标系。两个不同的椭球体之间转换,在WMS中瓦片生成是根据坐标转换生成的瓦片数据,而由于坐标转换是非线性的,导致生成的图片内容不能完全对上。所以有错开的问题。 那怎么解决呢? 最终的方案是: - 先通过四参数,把CAD与墨卡托3857的转换参数求出来 - 再墨卡托3857转wgs4326 通过这样的转换,数据转换是线性转换了,就不会有错开的问题了。 具体代码如下: ```javascript // --Cesium中加载CAD图(WMS图层)-- // 地图服务对象 let svc = new vjmap.Service(env.serviceUrl, env.accessToken) // 打开地图 let mapId = "sys_cadcesium"; let res = await svc.openMap({ mapid: mapId, // 地图ID mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开 style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式 }) if (res.error) { // 如果打开出错 message.error(res.error) } let layer = res.layer;//图层样式名 let mapBounds = vjmap.GeoBounds.fromString(res.bounds); let boundsArray = mapBounds.toPointArray(); // 得到坐标转换后的墨卡托点 let mktPoints = await svc.cmdTransform("EPSG:4544", "EPSG:3857", boundsArray.map(a => vjmap.geoPoint(a))); // cad上面的点坐标 let cadPoints = [...boundsArray]; // 通过坐标参数求出四参数 let fourparam = vjmap.coordTransfromGetFourParamter( mktPoints.map(a => vjmap.geoPoint(a)), cadPoints.map(a => vjmap.geoPoint(a)), true ); if (typeof Cesium !== "object") { // 如果没有环境 await vjmap.addScript([{ src: "js/Cesium/Cesium.js" }, { src: "js/Cesium/Widgets/widgets.css" }]) } let imageryProvider= new Cesium.WebMapTileServiceImageryProvider({ url: "https://t0.tianditu.gov.cn/img_w/wmts?tk=3346bb6ad29b5013c5952cf1117b80e9", layer: "img", style: "default", tileMatrixSetID: "w", format: "tiles", maximumLevel: 14, }); let viewer = new Cesium.Viewer('map', { imageryProvider: imageryProvider, contextOptions: { webgl: { alpha: true } }, selectionIndicator: false, animation: false, //是否显示动画控件 baseLayerPicker: false, //是否显示图层选择控件 geocoder: false, //是否显示地名查找控件 timeline: false, //是否显示时间线控件 sceneModePicker: false, //是否显示投影方式控件 navigationHelpButton: false, //是否显示帮助信息控件 infoBox: false, //是否显示点击要素之后显示的信息 fullscreenButton: false, shouldAnimate: true //动画播放 }); var layers = viewer.scene.imageryLayers; // 加一个高德注记图层 layers.addImageryProvider( new Cesium.UrlTemplateImageryProvider({ url: "https://webst02.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1&style=8", }) ); // 增加cad的wms图层 let wmsUrl = svc.wmsTileUrl({ mapid: mapId, // 地图id layers: layer, // 图层名称 bbox: '', // bbox这里不需要传,cesium会自动加上 srs: "EPSG:4326", // cesium地图是wgs84 crs: "EPSG:3857", // 先把wgs84转3857 fourParameter: [fourparam.dx,fourparam.dy,fourparam.scale,fourparam.rotate] // 转成 3857后,再用四参数去转成cad坐标 }) layers.addImageryProvider( new Cesium.WebMapServiceImageryProvider({ url: wmsUrl, }) ); // cad图坐标转web wgs84坐标 const cadToWebCoordinate = point => { // 先用四参数转成墨卡托3857,再墨卡托3857转wgs84 // 这里需要用四参数反算,因为上面的四参数是3857转cad的,这里是反过来,要cad转3857,所以需要反算 let ptMkt = vjmap.coordTransfromByInvFourParamter(vjmap.geoPoint(point), fourparam); return vjmap.Projection.mercator2LngLat(ptMkt);// 再墨卡托3857转经纬度 } // 转web wgs84坐标转cad图坐标 const webTocadCoordinate = point => { // 先wgs84转墨卡托3857,再用四参数转成cad let ptMkt = vjmap.Projection.lngLat2Mercator(vjmap.geoPoint(point)); return vjmap.coordTransfromByFourParamter(ptMkt, fourparam); } // 根据cad图的中心点,计算wgs84的中心点坐标 let cadCenter = mapBounds.center(); let webCenter = cadToWebCoordinate(cadCenter); //设置初始位置 viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(webCenter[0], webCenter[1], 30000) }); // 如果需要在地图上查询cad的实体坐标,可通过svc.rectQueryFeature来实现,需要传入两个cad的点坐标范围 // 可以通过 webTocadCoordinate 接口把wgs84的坐标转成 cad 的坐标去查询. ``` 出于对数据保密的需要,我们对CAD图进行了脱敏处理,叠加的效果如下 ![image-20220801194426286](https://vjmap.com/blogimages/image-20220801194426286.png) 访问 [https://vjmap.com/demo/#/demo/map/web/07cesiumCadWmsLayer](https://vjmap.com/demo/#/demo/map/web/07cesiumCadWmsLayer) ,查看效果和代码 --- # 百度地图与CAD图叠加显示 目前国内主要有以下三种坐标系: WGS84:为一种大地坐标系,也是目前广泛使用的GPS全球卫星定位系统使用的坐标系。如高德地图、腾讯地图. GCJ02:又称火星坐标系,是由中国国家测绘局制订的地理信息系统的坐标系统。由WGS84坐标系经加密后的坐标系。如OpenStreetMap、天地图。 BD09:为百度坐标系,在GCJ02坐标系基础上再次加密。其中bd09ll表示百度经纬度坐标,bd09mc表示百度墨卡托米制坐标。 主流瓦片地图的地理坐标系可分以下几类: - Web墨卡托投影, 主要有谷歌地图、高德地图、腾讯地图、搜狗地图、必应地图、OpenStreetMap、ArcGIS online、MapQuest、超图、360地图 - 经纬度直投, 主要有谷歌地球、天地图(经纬直投) - 百度墨卡托, 如百度地图 如上所述,百度地图独立采用了一个自己的坐标系,称为百度坐标系,与众不同。那么怎么样在百度地图上面加载CAD图呢? # 实现 ## (1)百度地图叠加第三方标准图层 百度地图 JSAPI GL提供XYZLayer类支持加载EPSG3857 坐标系统的 WMS 图层、WMTS图层、TMS图层以及自定义栅格图层。 https://lbsyun.baidu.com/index.php?title=jspopularGL/guide/trilateralLayer 其中WMS图层的示例示例代码如下 ```js // 创建位置点 var map = new BMapGL.Map("allmap"); var point = new BMapGL.Point(-99.41413316281799,39.82354027110903); map.centerAndZoom(point, 4); // tileUrlTemplate 包含OGC标准的WMS地图服务的GetMap接口的参数 var wms = BMapGL.XYZLayer({ tileUrlTemplate: 'https://ahocevar.com/geoserver/wms?REQUEST=GetMap&SERVICE=WMS&layers=topp:states&version=1.3.0&CRS=EPSG:3857&WIDTH=256&HEIGHT=256&FORMAT=image/png&TRANSPARENT=true&BBOX=[b] ', }); map.addTileLayer(wms); ``` 经过示例和文档发现,只要有服务支持EPSG3857的WMS服务就能通过百度地图的API把此图层做为一个第三方标准图层加载过来。 ## (2)CAD的WMS图层实现 这个在之前的博文中介绍过。 `唯杰地图VJMAP`为`CAD`图或`自定义地图格式`WebGIS`可视化`显示开发提供的一站式解决方案,支持的格式如常用的`AutoCAD`的`DWG`格式文件、`GeoJSON`等常用`GIS`文件格式,它使用WebGL`矢量图块`和`自定义样式`呈现交互式地图, 提供了全新的`大数据可视化`、`实时流数据`可视化功能。 唯杰地图[https://vjmap.com/](https://vjmap.com/)无需转换可直接打开CAD的DWG格式的图形,并提供了WMS服务接口。 其接口文档为: ```typescript /** * wms服务url地址接口 */ export interface IWmsTileUrl { /** 地图ID(为空时采用当前打开的mapid), 为数组时表时同时请求多个. */ mapid?: string | string[]; /** 地图版本(为空时采用当前打开的地图版本). */ version?: string | string[]; /** 图层名称(为空时采用当前打开的地图图层名称). */ layers?: string | string[]; /** 范围,缺省{bbox-epsg-3857}. (如果要获取地图cad一个范围的wms数据无需任何坐标转换,将此范围填cad范围,srs,crs,mapbounds填为空).*/ bbox?: string; /** 当前坐标系,缺省(EPSG:3857). */ srs?: string; /** cad图的坐标系,为空的时候由元数据坐标系决定. */ crs?: string | string[]; /** 地理真实范围,如有值时,srs将不起作用 */ mapbounds?: string; /** 宽. */ width?: number; /** 高. */ height?: number; /** 是否透明. */ transparent?: boolean; /** 四参数(x偏移,y偏移,缩放,旋转弧度),可选,对坐标最后进行修正*/ fourParameter?: string | string[]; /** 是否是矢量瓦片. */ mvt?: boolean; /** 是否考虑旋转,在不同坐标系中转换是需要考虑。默认自动考虑是否需要旋转. */ useImageRotate?: boolean; /** 旋转时图像处理算法. 1或2,默认自动选择(旋转时有用)*/ imageProcessAlg?: number; /** 当前互联网底图地图类型 WGS84(84坐标,如天地图,osm), GCJ02(火星坐标,如高德,腾讯地图), BD09LL(百度经纬度坐标,如百度地图), BD09MC(百度墨卡托米制坐标,如百度地图)*/ webMapType?: "WGS84" | "GCJ02" | "BD09LL" | "BD09MC"; } ``` ## (3) 实现 我们只要确定CAD图是哪个坐标系即可。因为百度wms的坐标系是EPSG3857,所以上面WMS图层的webMapType参数采用默认的WGS84坐标就可以了。 ```js let cadEpsg = "EPSG:4544";// cad图的espg代号 // 增加cad的wms图层 let wmsUrl = svc.wmsTileUrl({ mapid: mapId, // 地图id layers: layer, // 图层名称 bbox: '', // bbox这里不需要 srs: "EPSG:3857", crs: cadEpsg }) // tileUrlTemplate 包含OGC标准的WMS地图服务的GetMap接口的参数 wmsLayer = BMapGL.XYZLayer({ tileUrlTemplate: wmsUrl + '&BBOX=[b] ', useThumbData: false }); map.addTileLayer(wmsLayer); ``` 效果如下图: ![image-20220924213028012](https://vjmap.com/blogimages/image-20220924213028012.png) 从叠加效果来看,和百度地图能按实际坐标叠加到一起。 # 其他 ## (1) 没有坐标系的CAD图怎么叠加 上面叠加的图是有坐标系的。然而实际中有些图是没有坐标系的。如一个小区或商铺的平面图,那这种没有坐标系的CAD图怎么叠加到百度地图上呢?就这需要人为的指定公共点。步骤为在CAD图找二个及以上的点,同时在百度地图上相对应的位置也找同样的位置点,通过这些公共点计算出坐标转换的关系去进行叠加。而在唯杰地图的WMS图层中是支持四参数计算的。 根据公共点提供的WMS图层的示例代码为: ```js // cad上面的点坐标 let cadPoints = [ vjmap.geoPoint([587464448.8435846, 3104003685.208661,]), vjmap.geoPoint([587761927.7224838, 3104005967.655292]), vjmap.geoPoint([587463688.0280377, 3103796743.3798513]), vjmap.geoPoint([587760406.0913897, 3103793700.1176634]) ]; // 在互联网图上面拾取的与上面的点一一对应的坐标(百度坐标) let webPoints = [ vjmap.geoPoint([116.49738632967344,39.969244215079016]), vjmap.geoPoint([116.50000171663382,39.96753915494187]), vjmap.geoPoint([116.49843968869915,39.96613594214925]), vjmap.geoPoint([116.49581440721418,39.967790723887404]) ] // 通过坐标参数求出四参数 let epsg3857Points = webPoints.map(w => vjmap.geoPoint(vjmap.transform.convert(w, vjmap.transform.CRSTypes.BD09, vjmap.transform.CRSTypes.EPSG3857))); let param = vjmap.coordTransfromGetFourParamter(epsg3857Points, cadPoints , false); // 这里考虑旋转 let fourparam = [param.dx, param.dy, param.scale, param.rotate] // wms图层地址 const getCadWmsUrl = (transparent) => { let wmsUrl = svc.wmsTileUrl({ mapid: mapId, // 地图id layers: layer, // 图层名称 bbox: '', // bbox这里不需要 srs: "EPSG:3857", // fourParameter: fourparam, transparent: transparent, backgroundColor: 'rgba(240, 255, 255)' // 不透明时有效 }) return wmsUrl } ``` 通过上面的示例我们发现,其实只要有公共点,使用方法与有坐标系的WMS是差不多的。只是多了一个公共点计算完后成的得到的`四参数` `fourParameter`这个属性。 我们对一个没有坐标系的CAD图,随便在百度地图上叠加了几个公共点,叠加效果如下 ![image-20220924213906133](https://vjmap.com/blogimages/image-20220924213906133.png) ## (2) 百度地图叠加的WMS图层如果是透明色,则颜色会发生变化 在叠加WMS到百度地图的过程中,发现叠加的CAD图和之前的CAD图颜色不太一致,如下图所示 ![image-20220924214415039](https://vjmap.com/blogimages/image-20220924214415039.png) 而在之前和高德和cesium中通过WMS叠加,没有发现这个问题。真是百思不得其解。翻阅了百度地图的文档也没有发现原因。可能和百度地图的渲染绘制有关,百度地图在线提供的WMS示例都是没有透明度叠加的。没办法的情况下只能采用下策,把WMS图层做为一个不透明的图层叠加到百度地图中。具体代码为: ```js let wmsUrl = svc.wmsTileUrl({ mapid: mapId, // 地图id layers: layer, // 图层名称 bbox: '', // bbox这里不需要 srs: "EPSG:3857", // fourParameter: fourparam, transparent: false, // 这里透明度要改成false,表示不透明背景 backgroundColor: 'rgba(240, 255, 255)' // 不透明时有效,设置不透明背景色 }) ``` 最终叠加的效果如下图所示 ![image-20220924214830000](https://vjmap.com/blogimages/image-20220924214830000.png) 设置成不透明色后,CAD图的颜色确实和原来的颜色一样的了。美中不足的是因为设置了不透明,所以看不到CAD图覆盖的百度地图下面区域的内容了。 ## (3) GIS数据和WMS图层对应问题 因为上面的原因,如果设置了不透明,无法看到CAD图覆盖的百度地图下面区域的内容,所以想通过另外一个办法,就是把CAD的数据做为GIS中的geojson数据格式,通过百度地图SDK来进行前端绘制。 在百度地图SDK中提供了加载GeoJSON图层的方法。 地理数据多使用GeoJSON形式存储表达,百度地图JS API GL提供两种不同的方式支持开发者加载自己的GeoJSON数据: 1.提供GeoJSONParse类,满足用户将geojson数据解析为符合百度地图坐标的Overlay数据,用户可得到Overlay属性、坐标数据,进行覆盖物实例化或者其他处理。 2.提供GeoJSONLayer类,满足用户将geojson数据直接解析为覆盖物的图层,并进行交互操作 [https://lbsyun.baidu.com/index.php?title=jspopularGL/guide/geoJsonLayer](https://lbsyun.baidu.com/index.php?title=jspopularGL/guide/geoJsonLayer) 而在唯杰地图中也可以通过查询的方式,把CAD的数据转为geojson数据,再把geojson转成百度地图所要求的坐标格式即可。 最后代码如下 ```js // 在唯杰地图中通过查询获取CAD图的geojson数据 async function queryData(svc, cadPrj) { let res = await svc.conditionQueryFeature({ condition: ``, // 只需要写sql语句where后面的条件内容,字段内容请参考文档"服务端条件查询和表达式查询" fields: "", includegeom: true, // 是否返回几何数据,为了性能问题,realgeom为false时,如果返回条数大于1.只会返回每个实体的外包矩形,如果条数为1的话,会返回此实体的真实geojson;realgeom为true时每条都会返回实体的geojson realgeom: true, limit: 100000 //设置很大,不传的话,默认只能取100条 }, pt => { // 查询到的每个点进行坐标处理回调 let cadPoint = cadPrj.fromMercator(pt);// 转成cad的坐标 return cadToWebCoordinate(cadPoint); // 把cad的坐标转成当前地图的经纬度坐标 }) if (res && res.result && res.result.length > 0) { let features = []; for (let ent of res.result) { if (ent.geom && ent.geom.geometries) { let clr = vjmap.entColorToHtmlColor(ent.color); for (let g = 0; g < ent.geom.geometries.length; g++) { features.push({ id: ent.id, type: "Feature", properties: { objectid: ent.objectid + "_" + g, color: clr }, geometry: ent.geom.geometries[g] }) } } } let geom = { type: "FeatureCollection", features: features }; return geom; } } // 通过百度地图的sdk加载Geojson图层 function addGeoJsonData(map, data) { var region = data; // https://lbsyun.baidu.com/index.php?title=jspopularGL/guide/geoJsonLayer var cadLayer = new BMapGL.GeoJSONLayer('cad-child', { reference: 'WGS84', dataSource: region, level: -10, polygonStyle: function (properties) { var index = properties.join || 0; return { fillColor: properties.color } }, polylineStyle: function (properties) { return { strokeColor: properties.color } }, markerStyle: function (properties) { return { } } }); map.addGeoJSONLayer(cadLayer, { maxZoom: 24 }); return cadLayer; } ``` 最终效果如下 ![image-20220924215904227](https://vjmap.com/blogimages/image-20220924215904227.png) 通过GeoJSON的方式,不光能实现透明显示,还能在前端进行交互,如悬浮高亮,点击查询等。缺点就是geojson数据量大,加载速度慢,同时前端渲染也费性能。 最后把WMS图层和GeoJson同时显示,看下什么效果呢?按理说应该是完全叠加在一起才对。 ![image-20220924220238525](https://vjmap.com/blogimages/image-20220924220238525.png) ![image-20220924220402978](https://vjmap.com/blogimages/image-20220924220402978.png) 通过WMS图层和GeoJson同时显示,我们发现这两个图层之间存在偏移,当地图移动后级别放大后,又差不多能对应到一起。而在高德(GCJ02火星坐标系)和天地图(WGS84坐标系)的叠加中,WMS图层和GeoJson是能完全叠加对应上的。所以具体原因也百思不得其解,难道是百度地图对WMS图层做了什么特殊处理吗? # 结论 通过上面的测试,我们实现了在百度地图中通过WMS的方式加载CAD图,对于无坐标系的CAD图,也可以通过四参数进行叠加。同时也可以通过GeoJSON图层的方式来加载CAD的GIS数据展示。在线体验地址为 [百度地图SDK中加载CAD图](https://vjmap.com/demo/#/demo/map/web/09baiduCadLayer) https://vjmap.com/demo/#/demo/map/web/09baiduCadLayer [百度地图SDK公共点加载CAD图](https://vjmap.com/demo/#/demo/map/web/09baiduCadLayerFourparam) https://vjmap.com/demo/#/demo/map/web/09baiduCadLayerFourparam 遇到的坑至今没找到原因的有: (1)如果把CAD图层通过WMS做为透明图层叠加的时候,颜色会发生变化,原因未知。 (2)同样的坐标转换,通过WMS叠加CAD图和通过Geojson展示CAD图存在偏移,当地图移动后级别放大后,又差不多能对应到一起。原因未知。 --- # 几何算法 ## 点 |类名/命名空间|方法名|参数|说明| |:---|:---|:---|:---| |GeoPoint|add|p: GeoPoint|两个点相加 示例: let p1 = new vjmap.GeoPoint(1,2); let p2 = new vjmap.GeoPoint(3, 4); p1.add(p2); // 4, 6| |GeoPoint|sub|p: GeoPoint|两个点相减| |GeoPoint|multByPoint|p: GeoPoint|两个点相乘| |GeoPoint|divByPoint|p: GeoPoint|两个点相除| |GeoPoint|dot|p: GeoPoint|向量点积| |GeoPoint|lerp|v: GeoPoint, alpha: number|两点之间根据比例插值出新的点| |GeoPoint|rotate|angle: number|旋转| |GeoPoint|roateAround|angle: number, p: GeoPoint|绕一个点旋转.| |GeoPoint|matMult|m: number[]|乘一个矩阵| |GeoPoint|unit| |单位长度| |GeoPoint|equals|other: GeoPoint, dotErr: number = 8|判断是否相等| |GeoPoint|distanceTo|p: GeoPoint|两点之间距离| |GeoPoint|angle| |角度| |GeoPoint|angleTo|p: GeoPoint|距一个点的角度| |GeoPoint|angleWith|p: GeoPoint|距一个点的interiorAngle| |GeoPoint|angleWithSep|p: GeoPoint|距一个点的interiorAngle| |GeoPoint|transform|basePt: GeoPoint, destPt: GeoPoint, scale?:number, angle?: number|对一个点围绕一个基点进行缩放旋转平移操作.| ## 矩形范围外包盒 |类名/命名空间|方法名|参数|说明| |:---|:---|:---|:---| |GeoBounds|center| |获取中心点 示例: let bounds = new vjmap.GeoBounds([0,10], [10, 50]); let c = bounds.center(); // [5, 30]| |GeoBounds|square|isLatlng?: boolean, isMinValue?: boolean|变成正方形,使宽高相等|| |GeoBounds|width| |获取宽度|| |GeoBounds|height| |获取高度|| |GeoBounds|scale|ratio: number, origin?: GeoPoint | null|按照比例扩大/缩小出一个新的 GeoBounds。|| |GeoBounds|scaleXY|ratioX: number, ratioY: number, origin?: GeoPoint | null|按照XY比例扩大/缩小出一个新的 GeoBounds。|| |GeoBounds|translate|dx: number, dy: number|位移包围盒|| |GeoBounds|update|vertices: GeoPoint[]|更新包围盒|| |GeoBounds|updateByBounds|bounds: GeoBounds[] | GeoBounds|根据子包围盒更新包围盒|| |GeoBounds|intersect|b: GeoBounds|两包围盒求交集|| |GeoBounds|union|bound: GeoBounds|求多个包围盒的并集|| |GeoBounds|isIntersect|bound: GeoBounds|判断与另一个包围盒是否相交|| |GeoBounds|isContains|bound: GeoBounds|求一个包围盒是否包含另一个包围盒|| |GeoBounds|contains|point: GeoPoint|查看点是否在包围盒中|| |GeoBounds|closestPoint|testPoint: GeoPoint, includeInterior: boolean = true|查找边界框上最近的点并返回该点.|| ## 线 |类名/命名空间|方法名|参数|说明| |:---|:---|:---|:---| |vjmap|pointToSegmentDistance|p: GeoPoint, p1: GeoPoint, p2: GeoPoint|点到线段的距离 示例: vjmap.pointToSegmentDistance(vjmap.geoPoint([3, 2]), vjmap.geoPoint([0,0]), vjmap.geoPoint([0, 10])) // 3 | |vjmap|simplify|points: GeoPoint[], tolerance?: number|简化点坐标|| |vjmap|closestPointOnSegment|p: GeoPoint, p1: GeoPoint, p2: GeoPoint|点到线段的最近点|| |vjmap|closestPointOnPolyline|p: GeoPoint, points: GeoPoint[]|点到多段线的最近点|| |vjmap|closestPointOnPolylines|p: GeoPoint, lines: GeoPoint[][]|点到多条线的最近点| |vjmap|segmentIntersect|x1,y1,x2,y2,x3,y3,x4,y4|线段相交|| |vjmap|lineSplit|line: GeoPoint[], splitLine: GeoPoint[], dotErr?: number|线被另外一条线分开多条线段,返回新的线段|| |vjmap|multiLineSplit|lines: GeoPoint[][], dotErr?: number|多条线之间根据交点相互分开,返回相交后的所有线段,请确保每条线段不要自相交|| |vjmap|clipSegment|a: GeoPoint, b: GeoPoint, bounds: GeoBounds|根据范围裁剪线段|| |vjmap|interpolatePointsByRatio|points: GeoPointLike[], ratio: number, isLngLat: boolean = false|通过比例(0-1)的值,得到坐标序列的点坐标的值|| |vjmap|interpolateLineRange|points, number, isLngLat, offsetDist, minGap, includeSrcPoint|要插值的(多段)线的顶点(输入必须为经纬度).|| |vjmap.Math2D|dist|ax,ay, bx, by|计算两点距离| vjmap.Math2D.dist(10,10,10,30); // 20| |vjmap.Math2D|lineDist|pts: GeoPointLike[]|求线的距离| | |vjmap.Math2D|distToSegmentSquared|px, py, l0x, l0y, l1x, l1y|计算点和二维线段之间的平方距离。| | |vjmap.Math2D|intersectLineAndCircle|xLine1, yLine1, xLine2, yLine2, radius, xCenter, yCenter|求线和圆的交点| | |vjmap.Math2D|lineDist|pts: GeoPointLike[]|求线的距离| | ## 多边形 |类名/命名空间|方法名|参数|说明| |:---|:---|:---|:---| |vjmap|calcPolygonArea|points: GeoPoint[]|计算多边形面积 示例: vjmap.calcPolygonArea([vjmap.geoPoint([0, 0]),vjmap.geoPoint([5, 5]),vjmap.geoPoint([10, 0])]) // 25| |vjmap|clipPolygon|points: GeoPoint[], bounds: GeoBounds|根据范围裁剪多边形|| |vjmap|bufferConvexPolygon|ring: GeoPoint[], buffer: number|取一个凸环并通过在其周围应用缓冲区将其向外扩展。 此功能假定环是顺时针缠绕的。|| |vjmap|polygonIsClockwise|points: GeoPoint[]|根据格林公式判断多边形是否为顺时针| | |vjmap|polygonCentroid|points: GeoPoint[]|根据坐标列表计算多边形的质心| | |vjmap|isPointInPolygon|pos: GeoPoint, polygon: GeoPoint[]|判断点是否在多边形内。| | |vjmap|polylineMarginToPolygon|pts: GeoPointLike[],options|把多段线往外扩展成多边形。| | |vjmap|createObjectPolygonUtil| | 创建一个多边形几何工具对象,可对多边形进行差,并,相交等操作。| | ## 圆/椭圆 |类名/命名空间|方法名|参数|说明| |:---|:---|:---|:---| |vjmap|getCirclePolygonCoordinates|center,radius,points,startAngle,endAngle |得到圆或圆弧的离散化的多边形的点|| |vjmap|getCircleFeature|center,radius,points,startAngle,endAngle |得到圆或圆弧的离散化的多边形的geojson的形式|| |vjmap|getEllipsePolygonCoordinates|同上,majorAxisRadius,minorAxisRadius |得到椭圆或椭圆弧的离散化的多边形的点|| |vjmap|getEllipseFeature|同上,majorAxisRadius,minorAxisRadius |得到圆或圆弧的离散化的多边形的geojson的形式|| ## 曲线 |类名/命名空间|方法名|参数|说明| |:---|:---|:---|:---| |vjmap|bezierCurveToPolyline|bezierCurve,precision, recursiveCount |将 bezierCurve 转换为折线|| |vjmap|getBezierCurveLength|bezierCurve,precision, recursiveCount |计算贝塞尔曲线的长度|| |vjmap|convertBezierCurveToPolyline|bezierCurve,precision, recursiveCount |将贝塞尔曲线转换为折线.|| ## 拓扑图 |类名/命名空间|方法名|参数|说明| |:---|:---|:---|:---| |vjmap|Graph| |找最短路径 示例: var graph = Graph().addEdge("s", "t", 10).addEdge("s", "y", 5).addEdge("s", "y", 4).addEdge("t", "y", 2).addEdge("y", "t", 3).addEdge("t", "x", 1).addEdge("y", "x", 9).addEdge("y", "z", 2).addEdge("x", "z", 4).addEdge("z", "x", 6);var res = graph.shortestPath("s", "z")| |vjmap|findShortestPath|startPoint, endPoint,lines,precision,hasDirection | 给定所有路线、起始点、终点生成最短路径| | ## 算法 |类名/命名空间|方法名|参数|说明| |:---|:---|:---|:---| |vjmap|coordTransfromGetFourParamter|srcArr,destArr,isSetRotateZero,isConsiderPointOrder |坐标转换得到四参数| | |vjmap|coordTransfromByFourParamter|pt: GeoPoint, param: {dx: number, dy: number, scale: number, rotate: number} |通过四参数进行坐标转换| | |vjmap|coordTransfromByInvFourParamter|pt: GeoPoint, param: {dx: number, dy: number, scale: number, rotate: number} |通过四参数反算进行坐标转换| | |vjmap|buildTransformerMatrix3d|src: number[], dest: number[] |透视投影转3d css 矩阵| buildTransformerMatrix3d([10, 44, 100, 44, 100, 300, 10, 300],[50, 54, 130, 14, 140, 330, 14, 220]); // matrix3d(0.33941251980451626,-0.45559008266623746,0,-0.003618782566632648,-0.14489797252411882,0.32715165499980803,0,-0.0011983213450810846,0,0,1,0,48.53568735052134,39.35987390477654,0,1) | |vjmap|vectorContour|featureCollection: FeatureCollection,weight: string, breaks: number[],params |生成矢量等值面| | ## 其他 |类名/命名空间|方法名|参数|说明|示例| |:---|:---|:---|:---|:---| |vjmap.MathUtils|equals|num1: number, num2: number, dotErr: number = 8 |判断两个点是否相等| vjmap.MathUtils.equals(0.0041, 0.0042, 3) // true | |vjmap.MathUtils|lerp|v1: number, v2: number, value: number |线性插值|vjmap.MathUtils.lerp(10, 20, 0.3) // 13| |vjmap.MathUtils|map|val: number, inMin: number, inMax: number, outMin: number, outMax: number|将数字从一个范围映射到另一个范围。|vjmap.MathUtils.map(5, 0, 10, 100, 200) // 150| |vjmap.MathUtils|clamp|value: number, min: number, max: number|确保输入值适合给定范围。|vjmap.MathUtils.clamp(-1, 0, 10) // 0| |vjmap.MathUtils|wrap|n: number, min: number, max: number, isMin: boolean = false|通过模运算将n约束在给定范围内,不包括最小值|vjmap.MathUtils.wrap(11, 0, 10) // 1| ## 几何模型 API文档地址: [https://vjmap.com/docs/geomodel/](https://vjmap.com/docs/geomodel/) 示例代码地址: [https://vjmap.com/demo/#/demo/map/models/01geomodels](https://vjmap.com/demo/#/demo/map/models/01geomodels) --- # 几何模型 API文档地址: [https://vjmap.com/docs/geomodel/](https://vjmap.com/docs/geomodel/) 示例代码地址: [https://vjmap.com/demo/#/demo/map/models/01geomodels](https://vjmap.com/demo/#/demo/map/models/01geomodels) ## 快速开始 ### 直接引入JS ```html ``` ### npm ``` npm install vjgeo ``` ``` import vjgeo from 'vjgeo' ``` ## 点 一个点由具有 2 个元素的数组表示。第一个元素是 x,第二个元素是 y。 ```js var p = [0, 0]; ``` ## 坐标 使用与基础数学和传统制图相同的坐标系,其中 x 值从左到右增加,y 值从下到上增加。允许负值。 ## 路径 路径由具有以下强制属性的对象表示: - 类型:字符串 - “线”、“圆”或“弧” - 起点:点 ## 线 一条线是一个类型为“线”的路径和这个附加属性: ```js var line = { type: 'line', origin: [0, 0], end: [1, 1] }; ``` ## 圆 ```js var circle = { type: 'circle', origin: [0, 0], radius: 1 }; ``` ## 圆弧 ```js var arc = { type: 'arc', origin: [0, 0], radius: 1, startAngle: 0, endAngle: 45 }; ``` ## 导出DWG 调用`vjgeo.exporter.toDWG`函数并将您的路径作为参数传递: ```js var line = { type: 'line', origin: [0, 0], end: [50, 50] }; let dwgDoc = vjgeo.exporter.toDWG(line); ``` var line = { type: 'line', origin: [0, 0], end: [50, 50] }; let dwgDoc = vjgeo.exporter.toDWG(line); 完整的html页面代码如下,可复制下面的代码保存为index.html本地调试。修改示例中的相关代码片段即可 ```html vjmap demo
``` 也可以把这个路径数组做为参数传递 ```js var line = { type: 'line', origin: [0, 0], end: [50, 50] }; var circle = { type: 'circle', origin: [0, 0], radius: 50 }; var pathArray = [ line, circle ]; let dwgDoc = vjgeo.exporter.toDWG(pathArray); ``` var line = { type: 'line', origin: [0, 0], end: [50, 50] }; var circle = { type: 'circle', origin: [0, 0], radius: 50 }; var pathArray = [ line, circle ]; let dwgDoc = vjgeo.exporter.toDWG(pathArray); ## 模型 模型是vjgeo的核心对象,有以下几个属性 - origin: 基点 - paths: 模型路径对象 - models: 子模型对象 把路径做为模型参数的示例 ```js var line = { type: 'line', origin: [0, 0], end: [50, 50] }; var circle = { type: 'circle', origin: [0, 0], radius: 50 }; var pathObject = { myLine: line, myCircle: circle }; var model = { paths: pathObject }; let dwgDoc = vjgeo.exporter.toDWG(model); ``` var line = { type: 'line', origin: [0, 0], end: [50, 50] }; var circle = { type: 'circle', origin: [0, 0], radius: 50 }; var pathObject = { myLine: line, myCircle: circle }; var model = { paths: pathObject }; let dwgDoc = vjgeo.exporter.toDWG(model); 如果我们将模型代码包装在一个函数中,我们可以多次调用它。做这件事有很多种方法。首先,我们将保持代码不变,并返回模型变量。 ```js function myModel() { var line = { type: 'line', origin: [0, 0], end: [50, 50] }; var circle = { type: 'circle', origin: [0, 0], radius: 50 }; var pathObject = { myLine: line, myCircle: circle }; var model = { paths: pathObject }; return model; } let dwgDoc = vjgeo.exporter.toDWG(myModel()); ``` 或者,我们可以更改我们的函数以使用new运算符,并且使用this关键字设置我们的模型属性: ```js function myModel() { var line = { type: 'line', origin: [0, 0], end: [50, 50] }; var circle = { type: 'circle', origin: [0, 0], radius: 50 }; var pathObject = { myLine: line, myCircle: circle }; this.paths = pathObject; } let dwgDoc = vjgeo.exporter.toDWG(new myModel()); ``` function myModel() { var line = { type: 'line', origin: [0, 0], end: [50, 50] }; var circle = { type: 'circle', origin: [0, 0], radius: 50 }; var pathObject = { myLine: line, myCircle: circle }; this.paths = pathObject; } let dwgDoc = vjgeo.exporter.toDWG(new myModel()); 我们将创建一个具有 2 个 myModel 实例的新模型 ```js function myModel() { var line = { type: 'line', origin: [0, 0], end: [50, 50] }; var circle = { type: 'circle', origin: [0, 0], radius: 50 }; var pathObject = { myLine: line, myCircle: circle }; this.paths = pathObject; } var model1 = new myModel(); var model2 = new myModel(); model2.origin = [100, 0]; var model = { models: { "myModel1": model1, "myModel2": model2 } }; let dwgDoc = vjgeo.exporter.toDWG(model); ``` function myModel() { var line = { type: 'line', origin: [0, 0], end: [50, 50] }; var circle = { type: 'circle', origin: [0, 0], radius: 50 }; var pathObject = { myLine: line, myCircle: circle }; this.paths = pathObject; } var model1 = new myModel(); var model2 = new myModel(); model2.origin = [100, 0]; var model = { models: { "myModel1": model1, "myModel2": model2 } }; let dwgDoc = vjgeo.exporter.toDWG(model); 给模型设置填充和颜色属性 填充属性用`isFill`属性,只能作用于路径对象如圆、圆弧、闭合的路径中。如果中间有洞的用`isHole`属性 颜色属性可用于模型或路径对象 ```js function myModel() { let points = [ [100, 300], [100, 300], [100, 400], [0, 475], [-100, 400], [-100, 300], [-20, 300], [-20, 380], [20, 380], [20, 300], [20, 300] ]; let polyline = new vjgeo.models.Polyline(true, points); polyline.data = { colorIndex: 2, isFill: true, alpha: 100 } let hole = { paths: { circle: new vjgeo.paths.Circle([1000, 300], 300), rectLine1: new vjgeo.paths.Line([900, 200], [1100, 200]), rectLine2: new vjgeo.paths.Line([1100, 200], [1100, 400]), rectLine3: new vjgeo.paths.Line([1100, 400], [900, 400]), rectLine4: new vjgeo.paths.Line([900, 400], [900, 200]) }, data: { color: 0x00FFFF, isHole: true } } this.models = {polyline, hole} } var model1 = new myModel(); var model2 = new myModel(); model2.origin = [0, -800]; model2.models.polyline.data.colorIndex = 1; //修改颜色 model2.models.polyline.data.alpha = 255; //不透明 var model = { models: { "myModel1": model1, "myModel2": model2 } }; let dwgDoc = vjgeo.exporter.toDWG(model); ``` function myModel() { let points = [ [100, 300], [100, 300], [100, 400], [0, 475], [-100, 400], [-100, 300], [-20, 300], [-20, 380], [20, 380], [20, 300], [20, 300] ]; let polyline = new vjgeo.models.Polyline(true, points); polyline.data = { colorIndex: 2, isFill: true, alpha: 100 } let hole = { paths: { circle: new vjgeo.paths.Circle([1000, 300], 300), rectLine1: new vjgeo.paths.Line([900, 200], [1100, 200]), rectLine2: new vjgeo.paths.Line([1100, 200], [1100, 400]), rectLine3: new vjgeo.paths.Line([1100, 400], [900, 400]), rectLine4: new vjgeo.paths.Line([900, 400], [900, 200]) }, data: { color: 0x00FFFF, isHole: true } } this.models = {polyline, hole} } var model1 = new myModel(); var model2 = new myModel(); model2.origin = [0, -800]; model2.models.polyline.data.colorIndex = 1; model2.models.polyline.data.alpha = 255; var model = { models: { "myModel1": model1, "myModel2": model2 } }; let dwgDoc = vjgeo.exporter.toDWG(model); 也可以结合vjmap中的dwg实体类型进行创建 ```js function myModel() { let points = [ [50, -500], [100, -500] ]; let polyline = new vjgeo.models.Polyline(false, points); polyline.data = { colorIndex: 2 } let dim = new vjgeo.models.DbEntity("DbAlignedDimension", [ { point: [75, -520], attr: "dimLinePoint" }, { point: [50, -500], attr: "xLine1Point" }, { point: [100, -500], attr: "xLine2Point" } ], { dimScale: 10, textColor: 0x00FFFF, arrowColor: 0x00FF00, lineColor: 0xFF0000 }); this.models = {polyline, dim} } var model1 = new myModel(); var model2 = new myModel(); model2.origin = [0, -50]; vjgeo.model.scale(model2, 2, false, [50, -550]); let text = new vjgeo.models.Text("hello vjmap", [100, -540], 10, Math.PI / 8); text.data.colorIndex = 6; var model = { models: {model1,model2,text} }; let dwgDoc = vjgeo.exporter.toDWG(model); ``` function myModel() { let points = [ [50, -500], [100, -500] ]; let polyline = new vjgeo.models.Polyline(false, points); polyline.data = { colorIndex: 2 } let dim = new vjgeo.models.DbEntity("DbAlignedDimension", [ { point: [75, -520], attr: "dimLinePoint" }, { point: [50, -500], attr: "xLine1Point" }, { point: [100, -500], attr: "xLine2Point" } ], { dimScale: 10, textColor: 0x00FFFF, arrowColor: 0x00FF00, lineColor: 0xFF0000 }); this.models = {polyline, dim} } var model1 = new myModel(); var model2 = new myModel(); model2.origin = [0, -50]; vjgeo.model.scale(model2, 2, false, [50, -550]); let text = new vjgeo.models.Text("hello vjmap", [100, -540], 10, Math.PI / 8); text.data.colorIndex = 6; var model = { models: {model1,model2,text} }; let dwgDoc = vjgeo.exporter.toDWG(model); ## 内置模型 内置模型包括 - Belt - Bezier Curve - Bolt Circle - Bolt Rectangle - Connect the dots - DbEntity - Dogbone - Dome - Ellipse - Elliptic Arc - Holes - Oval - OvalArc - Polygon - Rectangle - Ring - RoundRectangle - S curve - Slot - Square - Star - Text ## 移动模型 模型和路径可以移动到绝对位置,或相对于当前位置移动 [x, y] 量。请记住,由于路径包含在模型中,并且模型可能包含在模型中,因此它们的坐标将相对于包含模型。 为了说明这一点,让我们创建一个有几个正方形的模型 ```js function Squares() { this.models = { s1: new vjgeo.models.Square(100), s2: vjgeo.model.move(new vjgeo.models.Square(100), [120, 0]), s3: new vjgeo.models.Square(100) }; //move the third square by setting its origin property. this.models.s3.origin = [240, 0]; } var squares = new Squares(); let dwgDoc = vjgeo.exporter.toDWG(squares); ``` ![image-20230422164828602](https://vjmap.com/blogimages/image-20230422164828602.png) 将模型移动到绝对位置的方法是设置其origin属性。vjgeo.model.move函数就是这样做的,但它还允许您在一行代码上执行更多操作。 要按相对量移动模型,请使用vjgeo.model.moveRelative: ```js function Squares() { this.models = { s1: new vjgeo.models.Square(100), s2: vjgeo.model.move(new vjgeo.models.Square(100), [120, 0]), s3: new vjgeo.models.Square(100) }; this.models.s3.origin = [240, 0]; } var squares = new Squares(); //move some squares by a relative distance vjgeo.model.moveRelative(squares.models.s2, [-10, 10]); vjgeo.model.moveRelative(squares.models.s3, [-20, 20]); let dwgDoc = vjgeo.exporter.toDWG(squares); ``` ![image-20230422165000328](https://vjmap.com/blogimages/image-20230422165000328.png) 同样,可以使用vjgeo.path.move 绝对移动路径或使用vjgeo.path.moveRelative 相对移动路径: ```js function Squares() { this.models = { s1: new vjgeo.models.Square(100), s2: vjgeo.model.move(new vjgeo.models.Square(100), [120, 0]), s3: new vjgeo.models.Square(100) }; this.models.s3.origin = [240, 0]; } var squares = new Squares(); //move a path by a relative distance vjgeo.path.moveRelative(squares.models.s3.paths.ShapeLine3, [0, 20]); //move a path to an absolute point vjgeo.path.move(squares.models.s2.paths.ShapeLine1, [30, 20]); let dwgDoc = vjgeo.exporter.toDWG(squares); ``` ![image-20230422165118400](https://vjmap.com/blogimages/image-20230422165118400.png) 请注意,第二个正方形的原点为 [120, 0],但我们将正方形内的一条线移动到绝对点 [30, 20]。由于直线包含在正方形模型中,因此它的坐标以正方形表示,这就是它看起来位于 [150, 20] 的原因。 ## 自定义模型 鉴于基本模型和移动它们实例的能力,我们现在可以开始建模了。以下是一些示例来说明如何使用它们: 平板电脑支架: ```js var outer = new vjgeo.models.RoundRectangle(200, 280, 8); var inner = new vjgeo.models.RoundRectangle(160, 230, 8); inner.origin = [20, 30]; var buttonhole = new vjgeo.paths.Circle([100, 15], 8); var bolts = new vjgeo.models.BoltRectangle(180, 260, 2); bolts.origin = [10, 10]; var tabletFaceMount = { paths: { buttonhole: buttonhole }, models: { inner: inner, outer: outer, bolts: bolts } }; let dwgDoc = vjgeo.exporter.toDWG(tabletFaceMount); ``` var outer = new vjgeo.models.RoundRectangle(200, 280, 8); var inner = new vjgeo.models.RoundRectangle(160, 230, 8); inner.origin = [20, 30]; var buttonhole = new vjgeo.paths.Circle([100, 15], 8); var bolts = new vjgeo.models.BoltRectangle(180, 260, 2); bolts.origin = [10, 10]; var tabletFaceMount = { paths: { buttonhole: buttonhole }, models: { inner: inner, outer: outer, bolts: bolts } }; let dwgDoc = vjgeo.exporter.toDWG(tabletFaceMount); 圆形转接板: ```js var model = { models: { ring1: new vjgeo.models.Ring(40, 100), bc1: new vjgeo.models.BoltCircle(90, 4, 10), bc2: new vjgeo.models.BoltCircle(55, 7, 6, 30) } }; let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422165505865](https://vjmap.com/blogimages/image-20230422165505865.png) ## 测量 要获取路径或模型的边界矩形,请使用: - vjgeo.measure.pathExtents(路径:对象) - vjgeo.model.modelExtents(模型:对象) 测量路径示例: ```js var arc = new vjgeo.paths.Arc([0, 0], 100, 45, 135); var m = vjgeo.measure.pathExtents(arc); console.log('measurement:'); console.log(m); var totalWidth = m.high[0] - m.low[0]; var totalHeight = m.high[1] - m.low[1]; var measureRect = new vjgeo.models.Rectangle(totalWidth, totalHeight); measureRect.origin = m.low; var model = { paths: { arc: arc }, models: { measureRect: measureRect } }; let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422165715082](https://vjmap.com/blogimages/image-20230422165715082.png) ## 归零和居中 要移动模型,使其下边缘和左边缘位于 x 和 y 轴上,请使用 model.zero。此函数接受 2 个布尔参数:zeroOnXAxis、zeroOnYAxis。如果不传递任何参数,它将在两个轴上归零。 ```js var model = { models: { crosshairs: { paths: { h: new vjgeo.paths.Line([-5, 0], [5, 0]), v: new vjgeo.paths.Line([0, -5], [0, 5]) } }, nut: { models: { polygon: new vjgeo.models.Polygon(6, 40) }, paths: { inner: new vjgeo.paths.Circle(20) } } } }; vjgeo.model.zero(model.models.nut); let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422104015846](https://vjmap.com/blogimages/image-20230422104015846.png) 要移动模型使其在 x 和 y 轴上居中,请使用model.center。此函数接受 2 个布尔参数:centerOnXAxis、centerOnYAxis。如果不传递任何参数,它将以两个轴为中心。 ```js var model = { models: { crosshairs: { paths: { h: new vjgeo.paths.Line([-5, 0], [5, 0]), v: new vjgeo.paths.Line([0, -5], [0, 5]) } }, box: { models: { outer: new vjgeo.models.Rectangle(60, 30), inner: new vjgeo.models.Oval(45, 15) } } } }; var shortcut = model.models.box.models; vjgeo.model.center(shortcut.outer); vjgeo.model.center(shortcut.inner); let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422104256407](https://vjmap.com/blogimages/image-20230422104256407.png) ## 坐标系统一 模型中的路径相对于其父模型进行引用。有时您可能希望所有对象都在同一坐标空间内。让我们创建一个简单的演示模型: ```js function box(origin) { this.models = { outer: new vjgeo.models.RoundRectangle(100, 100, 1) }; this.paths = { inner: new vjgeo.paths.Circle([50, 50], 25) }; this.origin = origin; } var box1 = new box([0, 0]); var box2 = new box([150, 0]); var model = { models: { box1: box1, box2: box2 } }; let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422105914067](https://vjmap.com/blogimages/image-20230422105914067.png) 在此示例中,虽然box1.paths.inner.origin和 不在同一位置,但它们都box2.paths.inner.origin具有原点,因为它们相对于包含它们的模型定位。[50, 50]为了使所有模型和路径都占据一个单一的坐标空间,我们可以使用vjgeo.model.originate: ```js function box(origin) { this.models = { outer: new vjgeo.models.RoundRectangle(100, 100, 1) }; this.paths = { inner: new vjgeo.paths.Circle([50, 50], 25) }; this.origin = origin; } var box1 = new box([0, 0]); var box2 = new box([150, 0]); var model = { models: { box1: box1, box2: box2 } }; //move all path origins into the same space vjgeo.model.originate(model); let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422105914067](https://vjmap.com/blogimages/image-20230422105914067.png) ## 缩放 要按比例缩放一个简单的点,请使用vjgeo.point.scale。 缩放路径示例: ```js var model = { models: { inner: new vjgeo.models.Polygon(6, 40), outer: vjgeo.model.scale(new vjgeo.models.Polygon(6, 40), 1.7) } }; let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422110247868](https://vjmap.com/blogimages/image-20230422110247868.png) ## 扭曲 要不成比例地缩放一个简单的点,请使用vjgeo.point.distort。 要不成比例地缩放路径,请使用 vjgeo.path.distort (path: object, scaleX: number, scaleY: number) 返回一个新对象并且不修改原始对象。返回对象的类型取决于被扭曲的路径类型: - 一条线将返回一条线IPath对象,因为失真可以用另一条线表示。 - 圆弧将返回 BezierCurve IModel对象,因为变形不是圆形的。 - 圆将返回一个 Ellipse IModel对象,因为变形不是圆形的。 ```js var circle = new vjgeo.paths.Circle(50); var line = new vjgeo.paths.Line([-50,-50], [50, 50]); var distortedLine = vjgeo.path.distort(line, 4, 1.5); var ellipse = vjgeo.path.distort(circle, 4, 1.5); var model = { paths: { circle: circle, line: line, distortedLine: distortedLine }, models: { ellipse: ellipse } }; let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422110503196](https://vjmap.com/blogimages/image-20230422110503196.png) ## 旋转 ```js var line1 = new vjgeo.paths.Line([0, 0], [100, 0]); var line2 = new vjgeo.paths.Line([0, 0], [100, 0]); var paths = [line1, vjgeo.path.rotate(line2, -30, [100, 0])]; let dwgDoc = vjgeo.exporter.toDWG(paths); ``` ![image-20230422110640927](https://vjmap.com/blogimages/image-20230422110640927.png) ## 克隆 模型和路径是简单的 JavaScript 对象,因此它们很容易以 JavaScript 标准的方式克隆。vjgeo 提供了一些用于克隆的函数: - vjgeo.cloneObject - 克隆模型或任何其他对象。 - vjgeo.path.clone - 克隆路径(比 cloneObject 更快) - vjgeo.point.clone - 克隆一个点(比 cloneObject 更快) 克隆在许多情况下都很有用。例如,如果您需要一个模型的多个副本进行旋转: ```js function sawtooth(numTeeth, r1, rd, offset) { var a = 360 / numTeeth; var a1 = 90 - a / 2; var r2 = r1 + rd; var p1 = vjgeo.point.fromPolar(vjgeo.angle.toRadians(a1), r1); var p2 = vjgeo.point.rotate(p1, a, [0, 0]); var p3 = [-offset, r2]; this.paths = { outer: new vjgeo.paths.Arc(p1, p3, r2 / 4, false, false), inner: new vjgeo.paths.Arc(p2, p3, r1 / 4, false, false) }; } var wheel = { models: {} }; var numTeeth = 30; var tooth = new sawtooth(numTeeth, 100, 20, 10); for (var i = 0; i < numTeeth; i++ ) { var clone = vjgeo.cloneObject(tooth); var a = 360 / numTeeth; vjgeo.model.rotate(clone, a * i, [0, 0]); wheel.models[i] = clone; } let dwgDoc = vjgeo.exporter.toDWG(wheel); ``` function sawtooth(numTeeth, r1, rd, offset) { var a = 360 / numTeeth; var a1 = 90 - a / 2; var r2 = r1 + rd; var p1 = vjgeo.point.fromPolar(vjgeo.angle.toRadians(a1), r1); var p2 = vjgeo.point.rotate(p1, a, [0, 0]); var p3 = [-offset, r2]; this.paths = { outer: new vjgeo.paths.Arc(p1, p3, r2 / 4, false, false), inner: new vjgeo.paths.Arc(p2, p3, r1 / 4, false, false) }; } var wheel = { models: {} }; var numTeeth = 30; var tooth = new sawtooth(numTeeth, 100, 20, 10); for (var i = 0; i < numTeeth; i++ ) { var clone = vjgeo.cloneObject(tooth); var a = 360 / numTeeth; vjgeo.model.rotate(clone, a * i, [0, 0]); wheel.models[i] = clone; } let dwgDoc = vjgeo.exporter.toDWG(wheel); ## 镜像 使用vjgeo.angle.mirror获取角度的镜像,使用 vjgeo.point.mirror获取单点的镜像。 您可以使用以下函数创建路径和模型的镜像副本。镜像可以发生在 x 轴、y 轴或两者上。 - vjgeo.path.mirror (path: object, mirrorX: boolean, mirrorY: boolean) - vjgeo.model.mirror(模型:对象,mirrorX:布尔值,mirrorY:布尔值) 这些函数中的每一个都返回一个新对象并且不修改原始对象。 镜像路径示例: ```js var line1 = new vjgeo.paths.Line([0, 0], [100, 100]); var line2 = vjgeo.path.mirror(line1, true, false); var paths = [line1, line2]; let dwgDoc = vjgeo.exporter.toDWG(paths); ``` ![image-20230422111031839](https://vjmap.com/blogimages/image-20230422111031839.png) 镜像模型示例: ```js var ovalArc1 = new vjgeo.models.OvalArc(45, 135, 50, 10); var model = { models: { ovalArc1: ovalArc1, ovalArc2: vjgeo.model.mirror(ovalArc1, false, true) } }; let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422111141032](https://vjmap.com/blogimages/image-20230422111141032.png) ## 重复布局 vjgeo 提供了几个函数,可以克隆您的路径或模型并在各种布局中重复它们。 ### 列 调用vjgeo.layout.cloneToColumn(path or model, count, [optional] margin)在列中重复克隆和布局。间隔将是路径或模型边界框的高度。额外的垂直边距是可选的。 ```js var dogbone = new vjgeo.models.Dogbone(50, 20, 2, -1, false); var grooves = vjgeo.layout.cloneToColumn(dogbone, 5, 20); let dwgDoc = vjgeo.exporter.toDWG(grooves); ``` ![image-20230422111411278](https://vjmap.com/blogimages/image-20230422111411278.png) ### 行 连续 调用vjgeo.layout.cloneToRow(path or model, count, [optional] margin)重复克隆布局。间隔将是路径或模型边界框的宽度。额外的水平边距是可选的 ```js var oval = new vjgeo.models.Oval(20, 150); var grill = vjgeo.layout.cloneToRow(oval, 12, 20); let dwgDoc = vjgeo.exporter.toDWG(grill); ``` ![image-20230422111602637](https://vjmap.com/blogimages/image-20230422111602637.png) ### 网格 ```js var roundSquare = new vjgeo.models.RoundRectangle(20, 20, 4); var grid = vjgeo.layout.cloneToGrid(roundSquare, 11, 5, 5); let dwgDoc = vjgeo.exporter.toDWG(grid); ``` ![image-20230422111831510](https://vjmap.com/blogimages/image-20230422111831510.png) ### 砖块 ```js var brick = new vjgeo.models.Rectangle(20, 8); var wall = vjgeo.layout.cloneToBrick(brick, 8, 7, 2); let dwgDoc = vjgeo.exporter.toDWG(wall); ``` ![image-20230422112051919](https://vjmap.com/blogimages/image-20230422112051919.png) ### 蜂窝 ```js var star = vjgeo.model.rotate(new vjgeo.models.Star(6, 50, 0, 2), 30); var pattern = vjgeo.layout.cloneToHoneycomb(star, 8, 5, 30); let dwgDoc = vjgeo.exporter.toDWG(pattern); ``` var star = vjgeo.model.rotate(new vjgeo.models.Star(6, 50, 0, 2), 30); var pattern = vjgeo.layout.cloneToHoneycomb(star, 8, 5, 30); let dwgDoc = vjgeo.exporter.toDWG(pattern); ### 径向 ```js var rect = vjgeo.model.move(new vjgeo.models.Rectangle(30, 10), [40, -5]); var spinner = vjgeo.layout.cloneToRadial(rect, 16, 22.5); let dwgDoc = vjgeo.exporter.toDWG(spinner); ``` ![image-20230422112144513](https://vjmap.com/blogimages/image-20230422112144513.png) ### 路径布局 ```js var square = new vjgeo.models.Square(10); var row = vjgeo.layout.cloneToRow(square, 10, 10); var arc = new vjgeo.paths.Arc([0, 0], 150, 45, 135); vjgeo.layout.childrenOnPath(row, arc); let dwgDoc = vjgeo.exporter.toDWG(row); ``` ![image-20230422124214205](https://vjmap.com/blogimages/image-20230422124214205.png) ```js var square = new vjgeo.models.Square(10); var row = vjgeo.layout.cloneToRow(square, 10, 10); row.models[0].models = { triangle: new vjgeo.models.Polyline(true, [ [5, 8], [2, 2], [8, 2] ]) }; var arc = new vjgeo.paths.Arc([0, 0], 150, 45, 135); //layout on the y-center vjgeo.layout.childrenOnPath(row, arc, 0.5); row.paths = { arc: arc }; let dwgDoc = vjgeo.exporter.toDWG(row); ``` ![image-20230422124344022](https://vjmap.com/blogimages/image-20230422124344022.png) 如果您希望每个子项都放置在路径上但**不**旋转,请将**旋转**设置为 false ```js var square = new vjgeo.models.Square(10); var row = vjgeo.layout.cloneToRow(square, 10, 10); row.models[0].models = { triangle: new vjgeo.models.Polyline(true, [ [5, 8], [2, 2], [8, 2] ]) }; var arc = new vjgeo.paths.Arc([0, 0], 150, 45, 135); //layout on the y-center, reversed, not contained, not rotated vjgeo.layout.childrenOnPath(row, arc, 0.5, true, false, false); arc.layer = "red"; row.paths = { arc: arc }; let dwgDoc = vjgeo.exporter.toDWG(row); ``` ![image-20230422124531817](https://vjmap.com/blogimages/image-20230422124531817.png) ## 相交点 您可以使用vjgeo.path.intersection 找到两条路径之间的交点。如果路径不相交,此函数将返回 null。否则,它将返回一个对象,该对象具有名为 intersectionPoints 的属性,该属性是一个点数组。此外,如果任一路径是圆弧或圆,则此对象将包含发生交叉点的角度。 相交点示例: ```js var model = { paths: { line1: new vjgeo.paths.Line([0, 0], [20, 10]), line2: new vjgeo.paths.Line([2, 10], [50, 2]) } }; var int = vjgeo.path.intersection(model.paths.line1, model.paths.line2); if (int) { var p = int.intersectionPoints[0]; var id = JSON.stringify(vjgeo.point.rounded(p, 0.01)); model.paths[id] = new vjgeo.paths.Circle(p, 1); } let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422112346415](https://vjmap.com/blogimages/image-20230422112346415.png) ```js var model = { paths: { circle1: new vjgeo.paths.Circle([0, 10], 20), circle2: new vjgeo.paths.Circle([20, 0], 20) } }; var int = vjgeo.path.intersection(model.paths.circle1, model.paths.circle2); if (int) { int.intersectionPoints.forEach( function(p, i) { var id = JSON.stringify(vjgeo.point.rounded(p, 0.01)) + ' intersects circle1 at ' + vjgeo.round(int.path1Angles[i], .01) + ' circle2 at ' + vjgeo.round(int.path2Angles[i], .01); model.paths[id] = new vjgeo.paths.Circle(p, 1); } ); } let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422112451151](https://vjmap.com/blogimages/image-20230422112451151.png) ## 相交线 要使线在斜坡交点处相交,请使用vjgeo.path.converge。此功能仅适用于直线,不适用于弧线。 converge 函数将尝试使用距离收敛点最近的线的末端。如果您需要指定应收敛线条的哪一端,请传递两个额外的布尔值。布尔值 true 表示使用线的起点,false 表示使用线的终点。 ```js var model = { origin: [0, 0], paths: { line1: new vjgeo.paths.Line([0, 0], [10, 5]), line2: new vjgeo.paths.Line([0, 10], [10, 4]), line3: new vjgeo.paths.Line([1, 0], [5, -2]) } }; var clone1 = vjgeo.cloneObject(model); clone1.origin = [10, 0]; var clone2 = vjgeo.cloneObject(model); clone2.origin = [20, 0]; vjgeo.path.converge(clone1.paths.line1, clone1.paths.line2); vjgeo.path.converge(clone1.paths.line1, clone1.paths.line3); vjgeo.path.converge(clone2.paths.line1, clone2.paths.line2, false, true); vjgeo.path.converge(clone2.paths.line1, clone2.paths.line3, true, false); let dwgDoc = vjgeo.exporter.toDWG({ models: { before: clone1, after: model, x: clone2 } }); ``` var model = { origin: [0, 0], paths: { line1: new vjgeo.paths.Line([0, 0], [10, 5]), line2: new vjgeo.paths.Line([0, 10], [10, 4]), line3: new vjgeo.paths.Line([1, 0], [5, -2]) } }; var clone1 = vjgeo.cloneObject(model); clone1.origin = [10, 0]; var clone2 = vjgeo.cloneObject(model); clone2.origin = [20, 0]; vjgeo.path.converge(clone1.paths.line1, clone1.paths.line2); vjgeo.path.converge(clone1.paths.line1, clone1.paths.line3); vjgeo.path.converge(clone2.paths.line1, clone2.paths.line2, false, true); vjgeo.path.converge(clone2.paths.line1, clone2.paths.line3, true, false); let dwgDoc = vjgeo.exporter.toDWG({ models: { before: clone1, after: model, x: clone2 } }); ## 修改模型 我们知道模型是相对简单的对象,具有众所周知的递归结构。这允许我们为不同的目的修改它们。让我们在一张图中修改和组合两个不同的模型。 对于这个例子,我们将使用椭圆来制作一个椭圆形的 L 形。我们首先创建一个具有两个椭圆的模型函数。 通过调试模型树,我们知道 Oval 是 RoundRectangle 并且圆弧的 id 是*BottomLeft*、*BottomRight*、*TopLeft*和*TopRight*。边的 id 是*Left*,*Right*,*Top*和*Bottom*。此外,我们需要注意这些线的方向,以便我们知道哪些是起点和终点。 要删除路径,我们使用 JavaScript **delete**关键字 ```js function ovalL(width, height, thickness) { var ovalH = new vjgeo.models.Oval(width, thickness); var ovalV = new vjgeo.models.Oval(thickness, height); delete ovalV.paths.BottomLeft; delete ovalV.paths.BottomRight; delete ovalH.paths.TopLeft; ovalH.paths.Top.end = ovalV.paths.Right.origin = [thickness, thickness]; this.models = { h: ovalH, v: ovalV }; } function ovalC(width, height, thickness) { var m = this.models = new ovalL(width, height, thickness).models; m.h2 = new vjgeo.models.Oval(width, thickness); m.h2.origin = [0, height - thickness]; delete m.h2.paths.TopLeft; delete m.h2.paths.BottomLeft; delete m.v.paths.TopRight; //h2 has paths relative to h2 origin, //we need to originate to share the point vjgeo.model.originate(this); //share the point m.h2.paths.Bottom.origin = m.v.paths.Right.end = [thickness, height - thickness]; } let dwgDoc = vjgeo.exporter.toDWG(new ovalC(100, 100, 37)); ``` ![image-20230422113019881](https://vjmap.com/blogimages/image-20230422113019881.png) ## 打断 如果路径上有一个点(例如,从十字路口),您可以使用vjgeo.path.breakAtPoint将路径分成两部分。这个函数会改变你传递给它的路径,所以它在那一点被打破,它会返回一个新的路径对象,这是另一个破碎的部分. ```js var model = { paths: { arc: new vjgeo.paths.Arc([0, 0], 50, 0, 180) } }; var arc2 = vjgeo.path.breakAtPoint(model.paths.arc, [0, 50]); vjgeo.model.moveRelative(arc2, [-10, 0]); model.paths.arc2 = arc2; let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422113208169](https://vjmap.com/blogimages/image-20230422113208169.png) ## 圆角 圆角是两条路径相交的圆角。vjgeo 提供了两种类型的圆角:传统圆角和狗骨圆角 ### 传统圆角 ```js var model = { paths: { line1: new vjgeo.paths.Line([0, 20], [30, 10]), line2: new vjgeo.paths.Line([10, 0], [30, 10]) } }; //create a fillet var arc = vjgeo.path.fillet(model.paths.line1, model.paths.line2, 2); //add the fillet to the model model.paths.arc = arc; let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422113740243](https://vjmap.com/blogimages/image-20230422113740243.png) ### 狗骨圆角 ```js var dogbones = { models: { round: new vjgeo.models.Dogbone(100,50, 5, 0), horizontal: new vjgeo.models.Dogbone(100,50, 5, -1), vertical: new vjgeo.models.Dogbone(100,50, 5, 1) } }; dogbones.models.horizontal.origin = [115, 0]; dogbones.models.vertical.origin = [230, 0]; let dwgDoc = vjgeo.exporter.toDWG(dogbones); ``` var dogbones = { models: { round: new vjgeo.models.Dogbone(100,50, 5, 0), horizontal: new vjgeo.models.Dogbone(100,50, 5, -1), vertical: new vjgeo.models.Dogbone(100,50, 5, 1) } }; dogbones.models.horizontal.origin = [115, 0]; dogbones.models.vertical.origin = [230, 0]; let dwgDoc = vjgeo.exporter.toDWG(dogbones); ## 级联函数 vjgeo 提供了一种级联以面向对象的样式修改绘图的简便方法,类似jQuery库。 调用vjgeo.$(x)获取返回的级联容器对象。然后,您可以在 x 上调用一系列级联函数。每个函数的输出成为下一个函数的输入。级联容器仅适用于输出与作为第一个参数输入的对象类型相同的对象的函数,该对象必须是以下类型之一: - Model - Path - Point 级联容器将具有操作容器本身的特殊属性(与操作 x 相对)。这些以 $ 为前缀: - $initial : object获取你传入的原始 x 对象。 - $result : object获取所有级联函数调用的最终结果。 - $reset() : function() - 将容器重置为 $initial。 ```js var starburst = {}; vjgeo.$(new vjgeo.paths.Arc([5, 5], 5, 180, 270)) .addTo(starburst, 'arc1') .mirror(true, false) .addTo(starburst, 'arc2') .mirror(false, true) .addTo(starburst, 'arc3') .mirror(true, false) .addTo(starburst, 'arc4'); let dwgDoc = vjgeo.exporter.toDWG(starburst); ``` ![image-20230422114256089](https://vjmap.com/blogimages/image-20230422114256089.png) ## 布尔运算 您可以使用vjgeo.model.combine函数组合模型,传递这些参数: - 第一个要组合的模型,我们称之为“modelA” - 第二个要组合的模型,我们称之为“modelB” - 布尔值以包含模型 B 中模型 A 的路径 - 布尔值以包括模型 A 的路径,这些路径在模型 B 之外 - 布尔值以包含模型 A 中模型 B 的路径 - 布尔值以包括模型 B 在模型 A 之外的路径 - 每个模型都必须是封闭的几何体,并且不应自相交。这些示例显示了 4 个布尔参数的效果 ```js function example(origin) { this.models = { rect: new vjgeo.models.Rectangle(100, 50), oval: vjgeo.model.move(new vjgeo.models.Oval(100, 50), [50, 25]) }; this.origin = origin; } var examples = { models: { x1: new example([0, 0]), x2: new example([200, 0]), x3: new example([400, 0]), x4: new example([500, 0]) } }; //save us some typing :) var x = examples.models; vjgeo.model.combine(x.x2.models.rect, x.x2.models.oval, false, true, false, true); vjgeo.model.combine(x.x3.models.rect, x.x3.models.oval, false, true, true, false); vjgeo.model.combine(x.x4.models.rect, x.x4.models.oval, true, false, true, false); let dwgDoc = vjgeo.exporter.toDWG(examples); ``` ![image-20230422121618114](https://vjmap.com/blogimages/image-20230422121618114.png) 布尔标志快捷方式: vjgeo.model.combineIntersection vjgeo.model.combineSubtraction vjgeo.model.combineUnion ```js function example(origin) { this.models = { rect: new vjgeo.models.Rectangle(100, 50), oval: vjgeo.model.move(new vjgeo.models.Oval(100, 50), [50, 25]) }; this.origin = origin; } var examples = { models: { x1: new example([0, 0]), x2: new example([200, 0]), x3: new example([400, 0]), x4: new example([500, 0]) } }; //save us some typing :) var x = examples.models; vjgeo.model.combineUnion(x.x2.models.rect, x.x2.models.oval); vjgeo.model.combineSubtraction(x.x3.models.rect, x.x3.models.oval); vjgeo.model.combineIntersection(x.x4.models.rect, x.x4.models.oval); let dwgDoc = vjgeo.exporter.toDWG(examples); ``` ### 布尔运算顺序 将模型与布尔运算相结合是一项强大的功能,但在某些情况下可能具有挑战性。可能需要重新建模您的绘图才能获得某些结果。我们将通过示例项目探索操作顺序概念。 ```js var star = new vjgeo.models.Star(28, 25, 20); var plus = vjgeo.model.rotate({ models: { v: vjgeo.model.center(new vjgeo.models.Rectangle(3, 90)), h: vjgeo.model.center(new vjgeo.models.Rectangle(110, 3)) } }, -12.5); vjgeo.model.combineUnion(plus.models.v, plus.models.h); vjgeo.model.combineUnion(star, plus); var starplus = { models: { star: star, plus: plus } }; var frame = { models: { outer: vjgeo.model.center(new vjgeo.models.RoundRectangle(100, 80, 4)), inner: vjgeo.model.center(new vjgeo.models.Rectangle(90, 70)) } }; vjgeo.model.combineSubtraction(frame.models.inner, starplus); //subtract from the inner frame only var model = { models: { starplus: starplus, frame: frame } }; let dwgDoc = vjgeo.exporter.toDWG(model); ``` var star = new vjgeo.models.Star(28, 25, 20); var plus = vjgeo.model.rotate({ models: { v: vjgeo.model.center(new vjgeo.models.Rectangle(3, 90)), h: vjgeo.model.center(new vjgeo.models.Rectangle(110, 3)) } }, -12.5); vjgeo.model.combineUnion(plus.models.v, plus.models.h); vjgeo.model.combineUnion(star, plus); var starplus = { models: { star: star, plus: plus } }; var frame = { models: { outer: vjgeo.model.center(new vjgeo.models.RoundRectangle(100, 80, 4)), inner: vjgeo.model.center(new vjgeo.models.Rectangle(90, 70)) } }; vjgeo.model.combineSubtraction(frame.models.inner, starplus); //subtract from the inner frame only var model = { models: { starplus: starplus, frame: frame } }; let dwgDoc = vjgeo.exporter.toDWG(model); ## 扩展路径 可以扩展路径以生成完美包围它们的封闭几何模型。 ```js var model = { paths: { p1: new vjgeo.paths.Line([0, 2], [10, 2]), p2: new vjgeo.paths.Circle([35, 2], 5) } }; model.models = { x1: vjgeo.path.expand(model.paths.p1, 2), x2: vjgeo.path.expand(model.paths.p2, 2) }; let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422122453673](https://vjmap.com/blogimages/image-20230422122453673.png) ### 扩展模型中的所有路径: ```js var star = vjgeo.model.rotate(new vjgeo.models.Star(5, 100), 18); var expanded = vjgeo.model.expandPaths(star, 10); var model = { models: { star: star, outline: expanded } }; let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422122619375](https://vjmap.com/blogimages/image-20230422122619375.png) ```js this.models = { frame: new vjgeo.models.Polyline(true, [ [0, h], [w, 0], [0, 0] ]) }; var angled = this.models.frame.paths.ShapeLine1; var bracepoints = [ [0, 0], vjgeo.point.middle(angled, 1/3), [w/2 , 0], vjgeo.point.middle(angled, 2/3) ]; this.models.brace = new vjgeo.models.Polyline(false, bracepoints); } var truss = new trussWireframe(200, 50); var expansion = vjgeo.model.expandPaths(truss, 3, 1); let dwgDoc = vjgeo.exporter.toDWG(expansion); ``` ![image-20230422122917496](https://vjmap.com/blogimages/image-20230422122917496.png) ## 路径简化 可以使用vjgeo.model.simplify来简化路径 - 但是需注意: 您的模型必须在调用 simplify 函数之前创建。 这是为了确保所有分段路径共享相同的坐标空间。 ```js function trussWireframe(w, h) { this.models = { frame: new vjgeo.models.Polyline(true, [ [0, h], [w, 0], [0, 0] ]) }; var angled = this.models.frame.paths.ShapeLine1; var bracepoints = [ [0, 0], vjgeo.point.middle(angled, 1/3), [w/2 , 0], vjgeo.point.middle(angled, 2/3) ]; this.models.brace = new vjgeo.models.Polyline(false, bracepoints); } var truss = new trussWireframe(200, 50); var expansion = vjgeo.model.expandPaths(truss, 3, 1); //call originate before calling simplify: vjgeo.model.originate(expansion); vjgeo.model.simplify(expansion); let dwgDoc = vjgeo.exporter.toDWG(expansion); ``` ![image-20230422123236101](https://vjmap.com/blogimages/image-20230422123236101.png) ## 贝塞尔曲线 ```js var points = [[0, 0], [50, 50], [50, -50], [100, 0]]; var curve1 = new vjgeo.models.BezierCurve(points); curve1.origin = [0, 20]; //more accurate var curve2 = new vjgeo.models.BezierCurve(points, 0.1); var model = { models: { c1: curve1, c2: curve2 } }; let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422123355527](https://vjmap.com/blogimages/image-20230422123355527.png) ## 导入SVG ```js var pathData = "M942.08 696.32H81.92V81.92h860.16zM122.88 655.36h778.24V122.88H122.88z M491.52 675.84h40.96v163.84h-40.96zM204.8 860.16h614.4v40.96H204.8z" var model = vjgeo.importer.fromSVGPathData(pathData); let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422171404339](https://vjmap.com/blogimages/image-20230422171404339.png) ### 导入GeoJSON ```js var geojson = { "type": "FeatureCollection", "features": [ { "id": "1", "type": "Feature", "properties": { "color": "#00FFFF" }, "geometry": { "coordinates": [ [ 587680772.4127743, 3103807303.19205 ], [ 587681877.0860132, 3103809140.85324 ], [ 587670560.3140597, 3103815943.7041655 ], [ 587668980.1131245, 3103813314.986614 ], [ 587665615.6670034, 3103815337.456052 ], [ 587666093.402162, 3103816132.184601 ], [ 587651779.2146056, 3103824736.8719153 ], [ 587651338.2282269, 3103824003.2762012 ], [ 587648218.4697058, 3103825878.656586 ] ], "type": "LineString" } } ] } let model = vjgeo.importer.fromGeoJsonData(geojson) let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422172523617](https://vjmap.com/blogimages/image-20230422172523617.png) ## 导出DWG ``` var star = new vjgeo.models.Star(8, 50, 15, 2) let dwgDoc = vjgeo.exporter.toDWG(star); ``` ![image-20230422172907802](https://vjmap.com/blogimages/image-20230422172907802.png) ## 导出GeoJSON 导出DWG时加入{isGeoJson: true}选项即可 ```js var star = new vjgeo.models.Star(8, 50, 15, 2) let geojson = vjgeo.exporter.toDWG(star, {isGeoJson: true}); ``` ![image-20230422173219313](https://vjmap.com/blogimages/image-20230422173219313.png) ## 导出JSON ```js var star = new vjgeo.models.Star(8, 50, 15, 2) let json = JSON.stringify(star);; console.log(json) ``` ![image-20230422173355722](https://vjmap.com/blogimages/image-20230422173355722.png) ## 模型树结构 模型是一个树状结构,可能包含路径,也可能包含继承关系中的其他模型。 示例 ```js var plate = { models: { outer: vjgeo.model.center(new vjgeo.models.RoundRectangle(120, 100, 10)), bolts: vjgeo.model.center(new vjgeo.models.BoltRectangle(100, 80, 5)) }, paths: { hole: new vjgeo.paths.Circle(25) } }; let dwgDoc = vjgeo.exporter.toDWG(plate); ``` ![image-20230422124741482](https://vjmap.com/blogimages/image-20230422124741482.png) ``` plate +-models | +-outer | | +-paths | | +-TopLeft | | +-Top | | +-TopRight | | +-Left | | +-Right | | +-BottomLeft | | +-Bottom | | +-BottomRight | +-bolts | +-paths | +-TopLeft_bolt | +-TopRight_bolt | +-BottomLeft_bolt | +-BottomRight_bolt +paths +-hole ``` 从根开始,我们可以轻松地遍历树。例如,让我们更改 BottomRight_bolt 孔的半径: ```js var plate = { models: { outer: vjgeo.model.center(new vjgeo.models.RoundRectangle(120, 100, 10)), bolts: vjgeo.model.center(new vjgeo.models.BoltRectangle(100, 80, 5)) }, paths: { hole: new vjgeo.paths.Circle(25) } }; //change radius of BottomRight bolt hole plate.models.bolts.paths.BottomRight_bolt.radius = 2; let dwgDoc = vjgeo.exporter.toDWG(plate); ``` ![image-20230422124936380](https://vjmap.com/blogimages/image-20230422124936380.png) ## 模型路由 我们知道我们可以使用点符号来引用深层对象: ``` var bottomRight_bolt = plate.models.bolts.paths.BottomRight_bolt; ``` `plate`从到的 引用`BottomRight_bolt`是硬编码的。假设我们有一个`plate2`重复的模型`plate`——我们需要硬编码对它的引用`BottomRight_bolt`: ``` var bottomRight_bolt2 = plate2.models.bolts.paths.BottomRight_bolt; ``` 代替硬编码的点符号,我们可以有一种抽象的方式来使用**路由**来引用深层对象。它只是一个字符串数组,表示点之间的段名称。我们不将根对象放在路由中。`plate`我们可以同时应用的路线`plate2`是: ``` var route = ["models", "bolts", "paths", "BottomRight_bolt"]; ``` 通过路由数组键值来定位模型: ```js var plate = { models: { outer: vjgeo.model.center(new vjgeo.models.RoundRectangle(120, 100, 10)), bolts: vjgeo.model.center(new vjgeo.models.BoltRectangle(100, 80, 5)) }, paths: { hole: new vjgeo.paths.Circle(25) } }; var plate2 = vjgeo.cloneObject(plate); plate2.origin = [130, 0]; //route to the BottomRight_bolt circle var route = ["models", "bolts", "paths", "BottomRight_bolt"]; //create a local variables for BottomRight_bolt holes var bottomRight_bolt = vjgeo.travel(plate, route).result; bottomRight_bolt.radius = 2; var bottomRight_bolt2 = vjgeo.travel(plate2, route).result; bottomRight_bolt2.radius = 3; var plates = { models: { plate: plate, plate2: plate2 } }; let dwgDoc = vjgeo.exporter.toDWG(plates); ``` ![image-20230422125531861](https://vjmap.com/blogimages/image-20230422125531861.png) ### 遍历模型树 您可以通过使用您的模型和具有以下可选属性的对象 调用vjgeo.model.walk来遍历模型树: #### walkPath 对象 walkPath 对象具有以下属性: - **layer**:包含此路径的图层名称(如果有)。 - **modelContext**:包含此路径的模型。 - **offset**:此路径所在的 [0, 0] 的绝对坐标。 - **pathContext**:路径本身。 - **pathId**:此路径在其父 model.paths 容器中的 ID。 - **route**:属性名称数组,用于从树的根部定位此路径。 - **routeKey**:路由的字符串表示,可以安全地用作此路径的唯一密钥标识符。 #### walkModel 对象 walkModel 对象具有以下属性: - **childId**:此模型在其父 model.models 容器中的 ID。 - **childModel**:模型本身 - **layer**:包含此路径的图层名称(如果有)。 - **offset**:此模型所在的 [0, 0] 的绝对坐标。 - **parentModel**:包含此模型的模型。 - **route**:属性名称数组,用于从树的根部定位此模型。 - **routeKey**:路由的字符串表示,可以安全地用作此模型的唯一密钥标识符。 我们将创建一个 RoundRectangle 并遍历它的树。我们有一个`onPath`函数,它将为模型中的每条路径调用。如果路径是弧形,我们将其反转: ```js function invertArc(arc) { var chord = new vjgeo.paths.Chord(arc); var midPoint = vjgeo.point.middle(chord); vjgeo.path.rotate(arc, 180, midPoint); } var shape = new vjgeo.models.RoundRectangle(100, 50, 10); var walkOptions = { onPath: function (wp) { if (wp.pathContext.type === 'arc') { invertArc(wp.pathContext); } } }; vjgeo.model.walk(shape, walkOptions); let dwgDoc = vjgeo.exporter.toDWG(shape); ``` ![image-20230422125919721](https://vjmap.com/blogimages/image-20230422125919721.png) ## 链条Chain 当 2 条或更多条路径首尾相接时,我们称其为**链**。这里有 3 条线首尾相连,形成一个有 3 个链接的链条;每条线路径都被视为链中的一个链接: ![image-20230422161520619](https://vjmap.com/blogimages/image-20230422161520619.png) 当链接没有任何松散的末端并相互连接时,我们称之为**循环**链条。通常,循环链被用来表示一个*封闭的几何体*。这是一条由 2 条线和一条弧线组成的循环链: ![image-20230422161542212](https://vjmap.com/blogimages/image-20230422161542212.png) 圆本质上是一个封闭的几何体。在 vjgeo 中,一个圆圈包含一条只有一个链接的循环链条。 ![image-20230422161559929](https://vjmap.com/blogimages/image-20230422161559929.png) 一条链可以递归地包含其他链。如果一条链本身是一条循环链,那么它只能包含其他链。以下是一条链包含另一条链的一些示例: ![image-20230422161615707](https://vjmap.com/blogimages/image-20230422161615707.png) **这是一个没有**任何链条的 模型。尽管线路重叠,但它们并不首尾相接。 ![image-20230422161632608](https://vjmap.com/blogimages/image-20230422161632608.png) ### 查找单链 让我们从绘制矩形开始。矩形是一个模型,但我们也隐含地知道矩形由 4 条首尾相连的路径组成。让我们现在使用vjgeo.model.findSingleChain(model)找到这个链: ```js var model = new vjgeo.models.Rectangle(100, 50); let dwgDoc = vjgeo.exporter.toDWG(model); //now find the chain var chain = vjgeo.model.findSingleChain(model); document.write('found a chain with ' + chain.links.length + ' links and endless=' + chain.endless); ``` ``` found a chain with 4 links and endless=true ``` 现在,让我们将两个矩形模型组合成一个并集。请注意,由于两个矩形模型是独立的,链将继续不受约束 ```js var drawing = { models: { r1: new vjgeo.models.Rectangle(100, 50), r2: vjgeo.model.move(new vjgeo.models.Rectangle(100, 50), [50, 25]) } }; vjgeo.model.combineUnion(drawing.models.r1, drawing.models.r2); let dwgDoc = vjgeo.exporter.toDWG(drawing); //now find the chain var chain = vjgeo.model.findSingleChain(drawing); document.write('found a chain with ' + chain.links.length + ' links and endless=' + chain.endless); ``` ``` found a chain with 8 links and endless=true ``` ### 链条关键点 调用 vjgeo.model.toKeyPoints(chain, [optional] maxArcFacet) 传递你的链,以及圆弧和圆上的最大面长度: ```js var rect = new vjgeo.models.RoundRectangle(100, 50, 10); var chain = vjgeo.model.findSingleChain(rect); var keyPoints = vjgeo.chain.toKeyPoints(chain, 5); var model = { models: { rect: rect, dots: new vjgeo.models.Holes(1, keyPoints) } }; let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422162704231](https://vjmap.com/blogimages/image-20230422162704231.png) 要使点沿链条间隔一致,请调用 vjgeo.model.toPoints(chain, distance) 传递您的链条以及点之间的距离: ```js var rect = new vjgeo.models.RoundRectangle(100, 50, 10); var chain = vjgeo.model.findSingleChain(rect); var spacing = 10; var keyPoints = vjgeo.chain.toPoints(chain, spacing); var model = { models: { rect: rect, dots: new vjgeo.models.Holes(1, keyPoints) } }; let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422162811461](https://vjmap.com/blogimages/image-20230422162811461.png) 您可以使用链的**pathLength**属性来确保您的距离在整个链上平均分配: ```js var rect = new vjgeo.models.RoundRectangle(100, 50, 10); var chain = vjgeo.model.findSingleChain(rect); var minimumSpacing = 10; var divisions = Math.floor(chain.pathLength / minimumSpacing); var spacing = chain.pathLength / divisions; console.log(spacing); var keyPoints = vjgeo.chain.toPoints(chain, spacing); var model = { models: { rect: rect, dots: new vjgeo.models.Holes(1, keyPoints) } }; let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422163014215](https://vjmap.com/blogimages/image-20230422163014215.png) ### 链条圆角 通过使用以下参数调用vjgeo.chain.fillet可以在链中的所有路径之间 添加圆角: - chainToFillet:包含将被修改为在其关节处具有圆角的路径的链。 - filletRadius:圆角的半径。 这将修改链条的所有路径以适应彼此之间的弧线,并且它将返回一个包含所有适合的圆角的新模型。这个新模型应该添加到您的树中 ```js var model = { paths: { "0": new vjgeo.paths.Line([0, 0], [100, 0]), "1": new vjgeo.paths.Line([100, 0], [100, 100]), "2": new vjgeo.paths.Line([100, 100], [200, 100]) }, //create a placeholder in the tree for more models models: {} }; //find the chain var chain = vjgeo.model.findSingleChain(model); //add fillets to the chain var filletsModel = vjgeo.chain.fillet(chain, 10); //put the fillets in the tree model.models.fillets = filletsModel; let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422163215981](https://vjmap.com/blogimages/image-20230422163215981.png) ### 链条布局 类似于路径布局,您可以使用链作为模型中一行子模型的布局指南。调用vjgeo.layout.childrenOnChain(parentModel: Model, onChain: chain),x轴将投影到你的onChain上: ```js var square = new vjgeo.models.Square(5); var row = vjgeo.layout.cloneToRow(square, 10, 10); var curve = new vjgeo.models.BezierCurve([0, 0], [33, 25], [66, -25], [100, 0]); var chain = vjgeo.model.findSingleChain(curve); vjgeo.layout.childrenOnChain(row, chain, 0.5, false, true); var model = { models: { curve: curve, row: row } }; curve.data.colorIndex = vjgeo.exporter.colors.red; row.data = { color: 0x00FFFF } let dwgDoc = vjgeo.exporter.toDWG(model); ``` ![image-20230422163946062](https://vjmap.com/blogimages/image-20230422163946062.png) --- # 动画 ::: tip 提示 以下函数的命名空间为`vjmap`,执行前记得加上`vjmap`,如 vjmap.`createAnimation` ::: ## 动画 `animate` 执行关键帧或弹簧动画。 ```js createAnimation({ from: 0, to: 100, onUpdate: latest => console.log(latest) }) ``` 它可以为数字设置动画: ```js createAnimation({ from: 0, to: 100 }) ``` 或相同类型的字符串: ```js createAnimation({ from: "0px", to: "100px" }) createAnimation({ from: "#fff", to: "#000" }) ``` 字符串可能非常复杂,例如框阴影或 SVG 路径定义。唯一的限制是其中包含的数字和颜色必须按相同顺序排列: ```js createAnimation({ from: "0px 0px 0px rgba(0, 0, 0, 0)", to: "10px 10px 0px rgba(0, 0, 0, 0.2)" }) ``` 执行的动画类型将从提供的选项中自动检测,或者可以通过定义`type`为`"keyframes"`、`"spring"`或手动选择`"decay"`。 ### 选项 可以为**所有动画**设置这些选项: #### from 开始动画的初始值。 默认为 `0` ```js createAnimation({ from: "linear-gradient(#e66465, #9198e5)", to: "linear-gradient(#9198e5, #e66465)" }) ``` #### elapsed 设置初始经过时间,以毫秒为单位。为延迟设置为负值。 ```js createAnimation({ to: 100, elapsed: -300 }) ``` #### repeat 重复动画的次数。设置为`Infinity`永远重复。 ```js createAnimation({ to: 100, repeat: 2 }) ``` #### repeatDelay 在重复动画之前等待的持续时间(以毫秒为单位)。 ```js createAnimation({ to: 100, repeat: 2, repeatDelay: 200 }) ``` #### repeatType 要么`"loop"`,`"mirror"`要么`"reverse"`。默认为`"loop"`. - `"loop"`:从 重复动画`0`。 - `"mirror":`交替交换`from`/`to`值。 - `"reverse":` 交替反转动画。 ```js createAnimation({ to: 100, repeat: 2, repeatType: "reverse" }) ``` #### type animate将根据提供的选项自动检测要使用的动画类型。但是可以通过定义type为"keyframes","spring"或手动选择特定类型"decay"。 ```js createAnimation({ to: 100, type: "spring" }) ``` ### 生命周期事件 以下生命周期事件可用于所有动画: #### onUpdate 这被称为动画以最新计算值触发的每一帧。 ```js createAnimation({ to: 100, onUpdate: latest => console.log(latest) }) ``` #### onPlay 这在动画开始时调用。当前,这会在animate被调用时自动执行。 ```js createAnimation({ to: 100, onPlay: () => {} }) ``` #### onComplete 当动画成功完成时调用它。 ```js createAnimation({ to: 100, onComplete:() => {} }) ``` #### onRepeat 这在动画重复时调用。 ```js createAnimation({ to: 100, repeat: 2, onRepeat: () => {} }) ``` #### onStop 当stop控件停止动画时调用此方法。 ```js const animation = createAnimation({ to: 100, onStop: () => {} }) animation.stop() ``` 关键帧选项 关键帧动画是默认的动画类型,可以使用from和to选项定义: ```js createAnimation({ from: 0, to: 100 }) ``` 或者作为提供给to选项的一系列关键帧: ```js createAnimation({ to: [0, 100, 200] }) ``` #### to 要设置动画的单个值,或要设置动画的值数组。 ```js createAnimation({ to: ["#0ff", "#f00", "#0f0"] }) ``` 如果to是一个数组,任何定义的from都将被忽略。 #### duration 这定义了动画的持续时间,以毫秒为单位。 ```js createAnimation({ to: 100, duration: 300 }) ``` #### ease 这是一个缓动函数或函数数组,用于在每个关键帧之间进行缓动。 ```js nimate({ to: 100, ease: linear }) createAnimation({ to: ["#fff", "#000", "#f00"], ease: [linear, easeInOut] }) ``` 如果设置为任何数组,则此数组的长度必须比在其间设置动画的值的数量短一。 #### offset 这是一个介于0和之间的值数组,1它定义了在整个动画中应该到达每个关键帧的哪个点。 该数组的长度应与定义的关键帧数相同。 ```js createAnimation({ to: ["#fff", "#000", "#f00"], offset: [0, 0.2, 1] }) ``` ### 弹簧选项 Springs 非常适合创建自然感觉的界面和动态的可中断动画。 如果检测到stiffness,damping或mass选项中的任何一个,则将使用弹簧动画。 注意:弹簧模拟本质上是数值型的,因此如果给它一个颜色、数组或对象,它会从0to运行动画100并将其插入到给定的值。 #### to 要动画化的单个值。 ```js createAnimation({ to: 100, type: "spring" }) ``` 如果to是一个数组,任何定义的from都将被忽略。 #### stiffness 这定义了弹簧的刚度。更高的刚度将产生更快速的动画。 默认为 100 ```js createAnimation({ to: 100, stiffness: 1000 }) ``` #### damping 这是 的反作用力stiffness。当您相对于 减小此值时,stiffness弹簧将变得更有弹性并且动画将持续更长时间。同样,较高的相对值将具有较小的弹性并导致动画较短。 默认为 10 ```js createAnimation({ to: 100, damping: 50 }) ``` #### mass 这是动画对象的质量。较重的物体需要更长的时间来加速和减速。 默认为1. ```js createAnimation({ to: 100, mass: 2 }) ``` #### velocity 动画的初始速度,以每秒为单位。 ```js createAnimation({ to: 100, velocity: 1000 }) ``` #### duration 弹簧的持续时间,以毫秒为单位。 将被stiffness,mass或覆盖damping。 ```js createAnimation({ to: 100, duration: 1000 }) ``` #### bounce 弹簧的弹力,作为0和之间的值1,其中0没有弹跳。 将被stiffness,mass或damping覆盖。 ```js createAnimation({ to: 100, bounce: 0.2 }) ``` #### restDelta 距动画目标的距离,在该距离处动画可以被视为完成。当restDelta和restSpeed都满足时,动画完成。 ```js createAnimation({ to: 100, restDelta: 0.5 }) ``` #### restSpeed 绝对速度,以每秒单位数为单位,低于该速度可以认为动画完成。当restDelta和restSpeed都满足时,动画完成。默认为10. ``js createAnimation({ to: 100, restSpeed: 5 }) `` ### 播放控制 `createAnimation` 返回 `PlaybackControls`,可用于控制动画的播放。 目前这只包括一个stop方法,但可能会扩展更多。 #### stop 停止动画。 ```js const playback = createAnimation({ from: 0, to: 100 }) playback.stop() ``` ### inertia 该inertia动画用于逐渐减速一个数字。 #### 选项 除了`from`,`onUpdate`和`onComplete`之外,`inertia`还支持以下内容: #### velocity 动画的初始速度,以每秒为单位。 ```js inertia({ from: 0, velocity: 100 }) ``` #### power 用于计算目标值的常数。更高的功率 = 进一步的目标。 默认为0.8. ```js inertia({ from: 0, power: 0.3 }) ``` #### timeConstant 调整时间常数会改变减速的持续时间。 默认为350. ```js inertia({ from: 0, velocity: 100, timeConstant: 400 }) ``` #### modifyTarget 一个接收计算目标并返回一个新目标的函数。用于将目标捕捉到网格。 ```js const roundToNearest = target => v => Math.ceil(v / target) * target inertia({ from: 0, velocity: 100, modifyTarget: roundToNearest(100) }) ``` #### min 动画将从逐渐减速切换并使用弹簧动画捕捉到该点的最小值。 ```js inertia({ from: 50, velocity: -100, min: 0 }) ``` #### max 动画将从逐渐减速切换并使用弹簧动画捕捉到该点的最大值。 ```js inertia({ from: 50, velocity: 100, max: 100 }) ``` #### bounceStiffness 这定义了动画命中min或时弹簧的刚度max。更高的刚度将产生更快速的动画。 默认为 500 ```js inertia({ from: 0, velocity: 100, max: 50, bounceStiffness: 1000 }) ``` #### bounceDamping 这是 的反作用力bounceStiffness。当您相对于 减小此值时,bounceStiffness弹簧将变得更有弹性并且动画将持续更长时间。同样,较高的相对值将具有较小的弹性并导致动画较短。 默认为 10 ```js inertia({ from: 0, velocity: 100, max: 50, bounceDamping: 300 }) ``` #### restDelta 距动画目标的距离,在该距离处动画可以被视为完成。 ```js inertia({ from: 0, velocity: 100, restDelta: 0.5 }) ``` ### 迭代器 迭代器使您能够以高度控制的方式运行动画。 每个都可以使用上面的匹配选项进行初始化(decay使用inertia's 选项的子集,不包括bounce-选项): ```js const animation = spring({ from: 0, to: 100, stiffness: 200 }) ``` 使用返回的迭代器,您可以使用其next方法在特定时间戳处解析动画。 ```js // 在 200 毫秒时解析动画 const { value, done } = animation.next(200) ``` ## Easing 内置的缓动功能 ### 函数 每个缓动函数都可以像这样导入: 每个函数都接受0和之间的进度值1,并返回一个新值: ```js const progress = 0.5 const easedProgress = easeInOut(progress) ``` - linear - easeIn - easeInOut - easeOut - circIn - circInOut - circOut - backIn - backInOut - backOut - anticipate - bounceIn - bounceInOut - bounceOut #### cubicBezier ```js const easing = cubicBezier(0, .42, 0, 1) ``` #### steps steps 返回一个缓动函数,它将动画转换为一系列离散的步骤。 ```js const easing = steps(5) ``` 它可以选择接受第二个参数,"start"或者"end"(默认)决定步骤是与动画的开始还是结束对齐。 ```js steps(5, "start") ``` #### mirrorEasing 镜像现有的缓动函数。 #### reverseEasing 反转现有的缓动函数。例如,提供它easeIn会返回一个easeOut. ```js const reversed = reverseEasing(linear) reversed(1) // 0 reversed(0.5) // 0.5 reversed(0) // 1 ``` #### createExpoIn 根据所提供的指数创建缓动函数power。越高power,宽松力度越大。 ```js const expoIn = createExpoIn(4) ``` 返回的缓动函数是缓动函数,这意味着它开始缓慢,结束迅速。mirrorEasing并且reverseEasing可用于创建缓入和缓出变体: ```js const expoIn = createExpoIn(4) const expoOut = mirrorEasing(easeIn) const expoInOut = reverseEasing(easeIn) ``` #### createBackIn 创建一个带有过冲的缓动函数。它接受一个power值,越高power越强过冲。 ```js const backIn = createBackIn(4) ``` 返回的缓动函数是缓入,这意味着过冲发生在动画开始时。mirrorEasing并且reverseEasing可用于创建缓入和缓出变体: ```js const backIn = createBackIn(4) const backOut = mirrorEasing(easeIn) const backInOut = reverseEasing(easeIn) ``` #### createAnticipate 创建一个缓动,在以过冲进行动画处理之前稍微向后拉。越强power,过冲越大。 ```js const anticipate = createAnticipate(4) ``` ### 通用 #### angle 返回两点之间的角度,以度为单位。 ```js angle( { x: 0, y: 0 }, { x: 45, y: 100 } ) ``` #### attract ```js attract(5, 10, 12) ``` #### attractExpo ```js attractExpo(5, 10, 12) ``` #### clamp 将值限制在给定范围内。 ```js const min = 50 const max = 100 clamp(min, max, 150) // 100 ``` #### degreesToRadians 将度数转换为弧度。 ```js degreesToRadians(45) // 0.785... ``` #### distance 返回两个数字、两个 2D 点或两个 3D 点之间的距离。 ```js distance(10, 50) distance({ x: 0, y: 0 }, { x: 45, y: 100 }) distance({ x: 0, y: 0, z: 100 }, { x: 45, y: 100, z: 0 }) ``` #### interpolate 创建一个函数,该函数将从线性数字序列插入到非线性数字序列、相同数字格式的字符串、颜色或它们的数组/对象。 ```js const mapXToOpacity = interpolate( [-100, 0, 100], [0, 1, 0] ) mapXToOpacity(-50) // 0.5 const mapProgressToValues = interpolate( [0, 1], [ { x: 0, color: "#fff" }, { x: 100, color: "#000" } ] ) mapProgressToValues(0.5) // { x: 50, color: "#888" } const rescale = interpolate( [0, 1], [100, 200], { clamp: false } ) rescale(2) // 300 ``` ##### 选项 interpolate 接受可选的第三个参数,一个选项对象。 - clamp:将值限制在给定范围内。默认为true. - ease:一个Easing函数或缓动函数数组,用于缓和每个段的插值。 - mixer:一个函数,当提供 afrom和to值时,将返回一个新函数,该函数接受这两个值之间的进度值0并1在这两个值之间进行混合。 #### isPoint 如果提供的参数是 2D 点,则返回true。 ```js isPoint({ x: 0 }) // false isPoint({ x: 0, y: 0 }) // true ``` #### isPoint3D 如果提供的参数是 3D 点,则返回true。 ```js isPoint3D({ x: 0 }) // false isPoint3D({ x: 0, y: 0 }) // false isPoint3D({ x: 0, y: 0, z: 0 }) // true ``` #### mix 将在两个值之间混合,progress作为第三个参数给出。 ```js mix(0, 100, 0.5) // 50 mix(0, 100, 2) // 200 ``` #### mixColor 返回一个函数,当提供一个progress值时,它将在两种颜色之间混合。接受十六进制、rgba 和 hsla 颜色。 ```js mixColor("#000", "#fff")(0.5) // "rgba(125, 125, 125, 1)" ``` #### mixComplex 返回一个函数,当提供一个progress值时,它将在具有相同数字和颜色顺序的两个字符串之间混合。 ```js mixComplex("100px #fff", "0px #000")(0.5) // "50px rgba(125, 125, 125, 1)" ``` #### pointFromVector 给定一个点,以度为单位的角度和距离,将返回一个新点。 ```js const point = { x: 0, y: 0 } const angle = 45 const distance = 100 pointFromVector(point, angle, distance) ``` #### progress 给定一个最小和最大范围以及一个值,将返回progress范围内的值归一化为0-1范围。 ```js const min = 100 const max = 200 progress(min, max, 150) // 0.5 ``` #### radiansToDegrees 将弧度转换为度数。 ```js radiansToDegrees(0.785) // 45 ``` #### snap 创建一个函数,它将数字捕捉到提供的数组中最近的位置或固定间隔。 ```js // Snap to regular intervals const snapTo = snap(45); snapTo(1); // 0 snapTo(40); // 45 snapTo(50); // 45 snapTo(80); // 90 // Snap to values in an array const snapTo = snap([-100, -50, 100, 200]); snapTo(-200); // -100 snapTo(-76); // -100 snapTo(-74); // -50 ``` #### toDecimal 将数字四舍五入到特定的小数位。 ```js toDecimal(3.3333); // 3.33 toDecimal(6.6666, 1); // 6.67 ``` #### velocityPerFrame ```js velocityPerFrame(50, 16.7); // 0.835 ``` #### velocityPerSecond ```js velocityPerSecond(1, 16.7); // 59.880... ``` #### wrap ```js wrap(0, 1, 0.5); // 0.5 wrap(0, 1, 1.5); // 0.5 ``` --- # 前端开源几何库 # JSTS ​ JSTS 是一个 ECMAScript 空间谓词和函数库,用于处理符合开放地理空间联盟发布的 SQL 简单特征规范的几何。JSTS 也是成熟的 Java 库JTS的一个端口。 ​ 该项目的主要目标是为网络地图应用程序提供一个完整的库来处理和分析简单的几何图形,但 JSTS 也可以用作独立的几何图形库。 JSTS 是通过 AST 到 AST 转换保留JTS API使用原始 [JTS](http://locationtech.github.io/jts/javadoc/) Java 源的自动翻译制作的,除了 I/O 相关类,这些类已被选择性地手动移植并支持 WKT、GeoJSON 和 OpenLayers 3+。 github地址: [https://github.com/bjornharrtell/jsts](https://github.com/bjornharrtell/jsts) 核心特征: - 几何模型(点、线、面积) - 几何操作(例如,相交、联合、内部等) - 几何构造 - 度量函数(例如笛卡尔二维距离、豪斯多夫距离) - 空间算法(例如缓冲区创建、线偏移、线简化) - 几何数学函数(如角度、距离等) - 空间结构(如四叉树和R-树、Delauney三角测量) - 输入/输出(例如WKT、GML) - 高精度算术 用法: [http://bjornharrtell.github.io/jsts/](http://bjornharrtell.github.io/jsts/) ```js var reader = new jsts.io.WKTReader() var a = reader.read('POINT (-20 0)') var b = reader.read('POINT (20 0)') // Buffer A and B by 40 units a = a.buffer(40) b = b.buffer(40) // Intersection of A and B var intersection = a.intersection(b) // Difference of A and B var difference = a.difference(b) // Union of A and B var union = a.union(b) // Symmetric difference of A and B var symDifference = a.symDifference(b) ``` ![image-20221115211610404](https://vjmap.com/blogimages/image-20221115211610404.png) 其中JSTS的基本概念与JTS相同。JTS基本概念和使用: [https://blog.csdn.net/runing9/article/details/51890350](https://blog.csdn.net/runing9/article/details/51890350) 优点: - 功能强大的开源库 - 支持平面坐标系,不限制于地理坐标系 缺点: - 文档缺少,示例缺少 # Turf Turf.js是JavaScript 空间分析库,Turf 实现了常用的空间分析操作,例如生成缓冲区、计算等高线,建立 TIN 等等。以往只属于桌面 GIS 的分析功能,已经可以在浏览器中使用。 Turf 使用 JavaScript 编写,通过 npm 进行包管理。良好的模块化设计使得 Turf 不仅可用于浏览器端,还可以通过 Node.js 在服务器端使用。Turf 原生支持 GeoJSON 矢量数据。GeoJSON 的优点是结构简单,并且得到了所有网页地图API的支持; github地址: [https://github.com/Turfjs/turf](https://github.com/Turfjs/turf) 主要功能: - Measurement 测量计算 - Coordinate Mutation 坐标变换 - Transformation 数据处理 - Feature Conversion 要素转换 - Misc - Helper 数据工具 - Random 随机生成数据 - Interpolation 插值 - Joins 空间关联 - Grids 网格计算 - Classification - Aggregation 聚合 - Meta 元数据使用方法 - Assertions 类型校验 - Booleans 布尔类方法 - Unit Conversion 单位换算 用法: [http://turfjs.org/](http://turfjs.org/) 前端编译工具或者node 环境中使用: ```js var collect = require('@turf/collect'); // or in ES6 import collect from '@turf/collect'; // 使用 collect(points, polys, 'population', 'populationValues'); // 全量引用 import * as turf from '@turf/turf' ``` 浏览器中直接使用 ```html ``` turf缓冲区分析 ![image-20221115213106611](https://vjmap.com/blogimages/image-20221115213106611.png) turf 绘制等值线 ![image-20221115213158776](https://vjmap.com/blogimages/image-20221115213158776.png) 优点: - 功能强大的GIS开源库,持续更新 - 文档全,示例多 缺点: - turf.js只支持3857和4326的坐标系。其他坐标系都不支持 # geometric geometric是一个用于处理几何的 JavaScript 库。非常的小巧。 github地址:[https://github.com/HarryStevens/geometric]https://github.com/HarryStevens/geometric 功能:提供了点,线,面的及相交判断的相交算法 . ### 网页浏览器 在原版中,`geometric`全局导出。您可以使用 unpkg 中的最新版本。 ```js