一鍵在Web端把CAD圖自動分割成多張圖紙並導出子圖或圖片

来源:https://www.cnblogs.com/vjmap/archive/2022/10/19/16807655.html
-Advertisement-
Play Games

前言 ​ 在實際中,一個CAD文件中往往存放多張圖紙,有時需要這些圖紙分開,單獨保存或顯示。以往的做法是在cad中人工進行處理。今天小編教您在web端一鍵把CAD圖自動分割成多張圖紙並能把每個子圖導出成單獨的dwg文件或保存成圖片。 例如要處理的CAD原圖為: 自動識別所有子圖的結果為(所有子圖的範 ...


前言

​ 在實際中,一個CAD文件中往往存放多張圖紙,有時需要這些圖紙分開,單獨保存或顯示。以往的做法是在cad中人工進行處理。今天小編教您在web端一鍵把CAD圖自動分割成多張圖紙並能把每個子圖導出成單獨的dwg文件或保存成圖片。

例如要處理的CAD原圖為:

image-20221019173929903

自動識別所有子圖的結果為(所有子圖的範圍都被紫色顏色所高亮顯示了):

image-20221019174054674

實現

先上效果圖

autosplitmap.gif

原理介紹

自動分割圖紙演算法

演算法原理:

子圖的特征為,外面有一個圖框,如果能找出所有圖中的圖框,就能根據圖框位置自動拆分出所有子圖了。

而圖框的最外面為矩形,同時這個圖框矩形外面沒有被其他矩形所包含了。

  • (1)遍歷圖中所有的矩形,獲取所有的矩形範圍

  • (2) 因為有時候矩形是由四條線所組成的,所以需要獲取圖中所有的水平線和垂直線,然後判斷能否組成矩形

  • (3)對所有獲取的矩形進行判斷,如果這個矩形沒有被其他矩形所包含了,則以為是子圖的圖框。

在圖中查找所有線段坐標代碼

// 在圖中查找所有的直線段
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;
}

獲取所有子圖範圍矩形代碼


// 得到一個圖裡面所有的矩形
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圖可以利用唯傑地圖提供的組合圖形的服務介面,其文檔為

/**
 * 組合新地圖參數
 */
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;
}

其實現代碼為

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圖形的服務介面,其文檔為

/**
 * 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";
}

其實現代碼為

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 線上體驗下。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 有些Windows客戶端因主板電池沒電或其他原因,每次啟動系統後,讀取到BIOS的時間是初始時間(1970年)或錯誤的時間,這時需要系統啟動後立即向時間伺服器同步一次時間。 該方法是添加一個Windows任務計劃,讓系統啟動後立即向時間伺服器同步時間,所以不需要藉助任何插件。 環境 Windo ...
  • nginx配置文件,rewrite,if rewrit 語法:rewrite regex replacement flag; 比如 rewrite ^/images/(.*\.jpg)$ /imgs/$1 break; 又如 rewrite ^/bbs/(.*)$ http://www.idfsof ...
  • 定義:select語句中嵌套select語句,被嵌套的select語句是子查詢。 子查詢可以出現在: select ....(select).. from ....(select).. where ....(select).. 1.where後面嵌套子查詢 select * from emp whe ...
  • 摘要:本文將通過一句口訣,教你如何辨別索引失效。 本文分享自華為雲社區《虛竹哥教你一句口訣辨別索引失效七大場景》,作者:小虛竹 。 一、口訣 教你一句功法口訣:模 型 數 或 運 最 快 二、初始化數據 創建存儲引擎為InnoDB的學生表 drop table if exists student; ...
  • 說起大數據中的應用,很多同學可能馬上會想起用戶畫像。 用戶畫像,英文稱之為User Profile,通過用戶畫像可以完美地抽象出一個用戶的信息全貌,通過用戶畫像數據可以精準地分析用戶的各種行為習慣,如消費習慣、興趣愛好、能力情況等等重要用戶信息。 通常用戶畫像是通過給用戶建標簽系統來實現的,本文介紹... ...
  • GreatSQL社區原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。 GreatSQL是MySQL的國產分支版本,使用上與MySQL一致。 本文來源:社區原創投稿;作者:王慶勛。 客戶的一些應用系統使用的JDK1.7版本,在將資料庫遷移到MySQL8.0的過程中,發現有些MySQL conn ...
  • 作為游戲類App的用戶運營,難免會遇到以下問題: 競品競爭激烈,新用戶獲取成本很高,該如何有效輓回卸載用戶和沉默用戶? 預算有限,該對哪些人群做活動才能獲得較高的ROI呢? 如何根據用戶屬性和用戶行為劃分用戶價值層級,並精準觸達呢? 如今,高昂的買量成本讓游戲的吸量越發艱難,存量用戶的精細化運營成為 ...
  • 本文主要記錄學習 Vue.js 的核心技術,涵蓋常用指令、計算屬性、監聽屬性、數據監測原理、生命周期鉤子等。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...