引言

在实际中,当多专业设计协助时,遇到图纸更新后,要对比图纸找出图纸的不同处,一直是一个比较耗时费力的事情,也是业内的一大痛点。一般CAD新旧图纸的内容对比,包括增加新的图形元素、减少原有的图形元素以及对原有的图形进行修改。传统的方式一般是在PC端CAD环境中实现对图纸比较的功能,然后随着互联网移动端技术的不断发展,如何摆脱CAD环境,在Web端轻松实现图纸对比功能呢?

实现思路

通常对比图纸不同有两种思路:

数据比较法

此方法是对图纸的原始数据进行比较分析。思路是通过遍历图纸中的所有实体元素,根据属性数据逐一比较差异性比较,找出不同处。

优点:算法准确。能定位出不同的实体对象。

缺点:图纸大时运算量大;同时,如果同一个实体删除了重新绘制会导致ObjectID发生变化,导致不好判断是否是同一个实体,算法实现难度大。

像素比较法

此方法是根据渲染后的图片进行比较。对图片的像素进行分析对比,找出不同的区域。

优点:速度快,算法实现相对容易。

缺点:只能定位出不同的区域,不能定位出具体是哪些实体。

在实际需求中,要求快速定位不同处,而无需定位到是哪些具体的实体对象。所以我们选用像素比较法来进行对比分析实现。

先上最终效果图如下:

同步对比分析效果:

地图卷帘效果效果:

算法分析

大家看到图片像素对比分析,肯定第一反应是这算法太简单了。一个个像素判断是否相等,然后就知道差异性了。如果这么想,那就是把问题想的太简单了。实际中,由于渲染时反锯齿的功能,会导致相同的绘制内容也会导致像素值细微的区别。而算法的核心就是把这些干扰因素给排除,找到真正差异的部分。

图片相似度计算方法总结

  • 余弦相似度

​ 把图片表示成一个向量,通过计算向量之间的余弦距离来表征两张图片的相似度

​ 具体算法可参考 https://zhuanlan.zhihu.com/p/93893211

  • 直方图

​ 按照某种距离度量的标准对两幅图像的直方图进行相似度的测量

​ 具体算法可参考 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

实现

我们基于BS模式对图片进行对比分析找出不同处。在服务端实现解析CAD图纸,生成像素图片;利用pixelmatch算法找出不同处。在浏览器端加载CAD图并显示出不同的地方。

(1) Web端在线打开CAD图

如何在Web网页端展示CAD图形(唯杰地图云端图纸管理平台 https://vjmap.com/app/cloud),这个在前面的博文中已讲过,这里不再重复,有需要的朋友可下载工程源代码研究下。

(2) 把CAD图转成图片

因为唯杰地图采用的把CAD图转成GIS数据渲染的思路,所以可以通过提供的WMS服务,渲染成指定像素大小的图片。这里为了对比结果准确,可以把渲染的级别设置大点,得到的图片像素大小也变大,更加清晰,对比结果更准确。

接口如下:

/*** 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) 像素对比分析算法

其反锯齿像素对比核心算法代码如下

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) 前端调用算法并展示

相关代码如下


// 地图比较不同
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://vjmap.com/demo/#/demo/map/service/17zmapDiff

Web端CAD图形找不同?一键在Web端找出CAD图不同并对比分析相关推荐

  1. MATLAB如何输出图形到CAD或者UG,ug里面怎么导出cad图形

    我们知道在ug中经常用到cad图形,其实ug也可以导出cad图形.但是很多同学还不太清楚.那么接下来下面是学习啦小编整理的一些关于ug里面导出cad图形,供您参阅. ug里面导出cad图形的方法 1. ...

  2. OLE对象是什么?为什么要在CAD图形中插入OLE对象?

    OLE对象是什么?OLE对象的意思是指对象连接与嵌入.那为什么要在CAD图形中插入OLE对象?一般情况下,在CAD图形中插入OLE对象,是为了将不同应用程序的数据合并到一个文档中.本节内容小编就来给大 ...

  3. 一键在Web端把CAD图自动分割成多张图纸并导出子图或图片

    前言 ​ 在实际中,一个CAD文件中往往存放多张图纸,有时需要这些图纸分开,单独保存或显示.以往的做法是在cad中人工进行处理.今天小编教您在web端一键把CAD图自动分割成多张图纸并能把每个子图导出 ...

  4. cad图形如何导入到奥维地图_将设计线路CAD图导入到奥维互动地图的方法

    将设计线路CAD图导入到奥维互动地图的方法 概要:奥维互动地图是第三方地图浏览器做的非常不错的软件,同时支持多种知名地图,三维地图,地理规划,位置与轨迹分享等,可以同时在电脑端和手机上浏览,在电脑端将 ...

  5. cad转dxf格式文件太大_想知道DWG、DWT、DWS和DXF是什么吗?从了解4种CAD图形格式开始吧...

    原创:就说我在开发区 常用图形文件格式盘点 CAD中的图形文件格式共9种,其扩展名分别为: ❶DWG – 图形或块文件 ❷DWT – 图形样板文件 ❸DWS – 图形标准文件 ❹DXF – 图形交换文 ...

  6. 使用JavaReport制作Web报表与图形 入门示例

    工作中非常需要根据客户录入的数据在页面上生成一些报表,苦于那个AnyChart是收费的,不敢用,所以就找了个免费的基于Java的第三方报表组件:      JavaReport是一款国产的第三方组件, ...

  7. cad调了比例因子没反应_CAD制图初学入门技巧:将CAD图形缩放为指定尺寸

    在使用浩辰CAD软件绘制图纸的过程中,很多CAD制图初学入门者由于对CAD软件的各种复杂功能不够了解,所以会出现比较多的问题,很多初学者会在网上找一下图纸联系,其中就会遇到CAD图形缩放的问题,如何将 ...

  8. 如何在前端通过JavaScript创建修改CAD图形

    背景 在之前的博文CAD图DWG解析WebGIS可视化技术分析总结.CAD_DWG图Web可视化一站式解决方案-唯杰地图-vjmap中讲解了如何把CAD的DWG格式的图纸Web可视化的方案,那在Web ...

  9. 轻松玩转移动AI,一键集成的端智能框架Pitaya

    一.什么是端智能? AI 技术现在已经覆盖到了互联网的方方面面,在云端的应用已经非常广泛和成熟.为了追随人工智能的浪潮,各大厂商也在不断加强移动设备的 AI 能力,主要体现在以下方面: 通过专门为 A ...

最新文章

  1. 用Java模拟multipart形式的Http Post请求
  2. CSS_伪元素_伪类
  3. Sublime Text 3 安装Package Control及配置Python环境
  4. vue嵌套数据多层级里面的数据不自动更新渲染dom
  5. 《人类简史》作者:巨变将至!数据决定你未来是什么物种
  6. 【python】numpy数据load报错
  7. AsyncDisplayKit官方文档个人翻译
  8. 【OJ8756】砝码称重V2,可达性多重背包
  9. 报道称奈雪的茶通过港交所聆讯 回应:以公司经监管机构批准的公告为准
  10. [转]C#利用委托跨线程更新UI数据
  11. 【C/C++】基本数据类型的隐式类型转换
  12. php刷数据库数据库,php数据库操作种
  13. virtualenv 指定 python 解释器的版本
  14. eclipse运行代码后变为红色和绿色
  15. 上海满五非唯一,非普通性住房计算
  16. HomeKit 设备接入协议
  17. 什么是即时通讯?即时通讯的发展
  18. RAID的介绍与配置
  19. 大长今人物系列:宿命中的孤独灵魂——崔今英(转载)
  20. Prometheus(3)Prometheus界面概述、监控服务功能实现

热门文章

  1. 那些百万年薪的算法工程师,都是经历了哪些九死一生?
  2. paddlenlp调用ERNIE、使用ERNIEKIT
  3. MATLAB 区分大小写问题
  4. 生成目录路径树结构方法
  5. c语言输入字母输出数字,输入一字符串 把其中的字母和数字分开输出 用c语言编写...
  6. Linux中TTY是什么意思
  7. cursor属性(cursor属性hand)
  8. 在Xcode下执行C/C++程序
  9. 《码农修行 编写优雅代码的32条法则》林文著 笔记
  10. 数字图像处理——拉普拉斯算子【像素级别处理】(python)