翼度科技»论坛 编程开发 html5 查看内容

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

5

主题

5

帖子

15

积分

新手上路

Rank: 1

积分
15
引言

在实际中,当多专业设计协助时,遇到图纸更新后,要对比图纸找出图纸的不同处,一直是一个比较耗时费力的事情,也是业内的一大痛点。一般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~default~CTRLIST~Rate-1-81000437-blog-83271885.pc_relevant_aa&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-1-81000437-blog-83271885.pc_relevant_aa&utm_relevant_index=2

实现

我们基于BS模式对图片进行对比分析找出不同处。在服务端实现解析CAD图纸,生成像素图片;利用pixelmatch算法找出不同处。在浏览器端加载CAD图并显示出不同的地方。
(1)  Web端在线打开CAD图
如何在Web网页端展示CAD图形(唯杰地图云端图纸管理平台  https://vjmap.com/app/cloud),这个在前面的博文中已讲过,这里不再重复,有需要的朋友可下载工程源代码研究下。

(2)  把CAD图转成图片
因为唯杰地图采用的把CAD图转成GIS数据渲染的思路,所以可以通过提供的WMS服务,渲染成指定像素大小的图片。这里为了对比结果准确,可以把渲染的级别设置大点,得到的图片像素大小也变大,更加清晰,对比结果更准确。
接口如下:
  1. /**
  2. * wms服务url地址接口
  3. */
  4. export  interface IWmsTileUrl {
  5.     /** 地图ID(为空时采用当前打开的mapid), 为数组时表时同时请求多个. */
  6.     mapid?: string | string[];
  7.     /** 地图版本(为空时采用当前打开的地图版本). */
  8.     version?: string | string[];
  9.     /** 图层名称(为空时采用当前打开的地图图层名称). */
  10.     layers?: string | string[];
  11.     /** 范围,缺省{bbox-epsg-3857}. (如果要获取地图cad一个范围的wms数据无需任何坐标转换,将此范围填cad范围,srs,crs,mapbounds填为空).*/
  12.     bbox?: string;
  13.     /** 当前坐标系,缺省(EPSG:3857). */
  14.     srs?: string;
  15.     /** cad图的坐标系,为空的时候由元数据坐标系决定. */
  16.     crs?: string | string[];
  17.     /** 地理真实范围,如有值时,srs将不起作用 */
  18.     mapbounds?: string;
  19.     /** 宽. */
  20.     width?: number;
  21.     /** 高. */
  22.     height?: number;
  23.     /** 是否透明. */
  24.     transparent?: boolean;
  25.     /** 四参数(x偏移,y偏移,缩放,旋转弧度),可选,对坐标最后进行修正*/
  26.     fourParameter?: string | string[];
  27.     /** 是否是矢量瓦片. */
  28.     mvt?: boolean;
  29.     /** 是否考虑旋转,在不同坐标系中转换是需要考虑。默认自动考虑是否需要旋转. */
  30.     useImageRotate?: boolean;
  31. }
复制代码
(3) 像素对比分析算法
其反锯齿像素对比核心算法代码如下
  1. uint8_t blend(uint8_t c, double a) {
  2.     return 255 + (c - 255) * a;
  3. }
  4. double rgb2y(uint8_t r, uint8_t g, uint8_t b) { return r * 0.29889531 + g * 0.58662247 + b * 0.11448223; }
  5. double rgb2i(uint8_t r, uint8_t g, uint8_t b) { return r * 0.59597799 - g * 0.27417610 - b * 0.32180189; }
  6. double rgb2q(uint8_t r, uint8_t g, uint8_t b) { return r * 0.21147017 - g * 0.52261711 + b * 0.31114694; }
  7. // 使用YIQ NTSC传输颜色空间测量感知色差”计算色差
  8. double colorDelta(const uint8_t* img1, const uint8_t* img2, std::size_t k, std::size_t m, bool yOnly = false) {
  9.     double a1 = double(img1[k + 3]) / 255;
  10.     double a2 = double(img2[m + 3]) / 255;
  11.     uint8_t r1 = blend(img1[k + 0], a1);
  12.     uint8_t g1 = blend(img1[k + 1], a1);
  13.     uint8_t b1 = blend(img1[k + 2], a1);
  14.     uint8_t r2 = blend(img2[m + 0], a2);
  15.     uint8_t g2 = blend(img2[m + 1], a2);
  16.     uint8_t b2 = blend(img2[m + 2], a2);
  17.     double y = rgb2y(r1, g1, b1) - rgb2y(r2, g2, b2);
  18.     if (yOnly) return y; // 仅亮度差
  19.     double i = rgb2i(r1, g1, b1) - rgb2i(r2, g2, b2);
  20.     double q = rgb2q(r1, g1, b1) - rgb2q(r2, g2, b2);
  21.     return 0.5053 * y * y + 0.299 * i * i + 0.1957 * q * q;
  22. }
  23. void drawPixel(uint8_t* output, std::size_t pos, uint8_t r, uint8_t g, uint8_t b) {
  24.     output[pos + 0] = r;
  25.     output[pos + 1] = g;
  26.     output[pos + 2] = b;
  27.     output[pos + 3] = 255;
  28. }
  29. double grayPixel(const uint8_t* img, std::size_t i) {
  30.     double a = double(img[i + 3]) / 255;
  31.     uint8_t r = blend(img[i + 0], a);
  32.     uint8_t g = blend(img[i + 1], a);
  33.     uint8_t b = blend(img[i + 2], a);
  34.     return rgb2y(r, g, b);
  35. }
  36. // 检查像素是否可能是抗锯齿的一部分
  37. 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) {
  38.     std::size_t x0 = x1 > 0 ? x1 - 1 : 0;
  39.     std::size_t y0 = y1 > 0 ? y1 - 1 : 0;
  40.     std::size_t x2 = std::min(x1 + 1, width - 1);
  41.     std::size_t y2 = std::min(y1 + 1, height - 1);
  42.     std::size_t pos = (y1 * width + x1) * 4;
  43.     uint64_t zeroes = 0;
  44.     uint64_t positives = 0;
  45.     uint64_t negatives = 0;
  46.     double min = 0;
  47.     double max = 0;
  48.     std::size_t minX = 0, minY = 0, maxX = 0, maxY = 0;
  49.     // 穿过8个相邻像素
  50.     for (std::size_t x = x0; x <= x2; x++) {
  51.         for (std::size_t y = y0; y <= y2; y++) {
  52.             if (x == x1 && y == y1) continue;
  53.             // 中心像素和相邻像素之间的亮度增量
  54.             double delta = colorDelta(img, img, pos, (y * width + x) * 4, true);
  55.             // 计算相等、较暗和较亮相邻像素的数量
  56.             if (delta == 0) zeroes++;
  57.             else if (delta < 0) negatives++;
  58.             else if (delta > 0) positives++;
  59.             // 如果找到两个以上相同的同级,则绝对不是抗锯齿
  60.             if (zeroes > 2) return false;
  61.             if (!img2) continue;
  62.             // 记得最暗的像素
  63.             if (delta < min) {
  64.                 min = delta;
  65.                 minX = x;
  66.                 minY = y;
  67.             }
  68.             // 记住最亮的像素
  69.             if (delta > max) {
  70.                 max = delta;
  71.                 maxX = x;
  72.                 maxY = y;
  73.             }
  74.         }
  75.     }
  76.     if (!img2) return true;
  77.     // 如果同级之间没有较暗和较亮的像素,则不是抗锯齿
  78.     if (negatives == 0 || positives == 0) return false;
  79.     // 如果最暗或最亮的像素在两幅图像中都有两个以上相同的同级
  80.         //(绝对不是反走样),该像素是反走样的
  81.     return (!antialiased(img, minX, minY, width, height) && !antialiased(img2, minX, minY, width, height)) ||
  82.            (!antialiased(img, maxX, maxY, width, height) && !antialiased(img2, maxX, maxY, width, height));
  83. }
  84. }
复制代码
(4) 前端调用算法并展示
相关代码如下
  1. // 地图比较不同
  2. let diff = await service.cmdMapDiff({
  3.     // 要比较图1的图名称
  4.     mapid1: mapId1,
  5.     // 要比较图1的图版本,如为空,表示是最新版本
  6.     version1: "",
  7.     // 要比较图1的图层样式名称,可为空。为空的用默认的
  8.     layer1: map1.getService().currentMapParam().layer,
  9.     // 要比较图2的图名称,图名称可以和mapid1不一样
  10.     mapid2: mapId2,
  11.     // 要比较图2的图版本,如为空,表示是最新版本
  12.     version2: "",
  13.     // 要比较图2的图层样式名称,可为空。为空的用默认的
  14.     layer2: map2.getService().currentMapParam().layer
  15. })
  16. if (diff.error) {
  17.     message.error(diff.error);
  18.     return;
  19. }
  20. const drawPolygons = (map, points, color) => {
  21.     if (points.length === 0) return;
  22.     points.forEach(p => p.push(p[0])) ;// 闭合
  23.     let polygons = points.map(p => {
  24.         return {
  25.             points: map.toLngLat(p),
  26.             properties: {
  27.                 color: color
  28.             }
  29.         }
  30.     })
  31.     vjmap.createAntPathAnimateLineLayer(map, polygons, {
  32.         fillColor1: color,
  33.         fillColor2: "#0ffb",
  34.         canvasWidth: 128,
  35.         canvasHeight: 32,
  36.         frameCount: 4,
  37.         lineWidth: 4,
  38.         lineOpacity: 0.8
  39.     });
  40. }
  41. if (diff.modify.length === 0) {
  42.     message.info("完全相同,没有找到不同处");
  43.     return;
  44. }
  45. // 修改的部分
  46. drawPolygons(map2, diff.modify, "#f00");
  47. // 新增部分
  48. drawPolygons(map2, diff.new, "#0f0");
  49. // 删除部分
  50. 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

来源:https://www.cnblogs.com/vjmap/p/16644550.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

上一篇: ​探秘 Web 水印技术

下一篇: html笔记2

举报 回复 使用道具