# 查询示例(自动分割子图)
一键在Web端把CAD图自动分割成多张图纸并导出子图或图片
# 介绍
在实际中,一个CAD文件中往往存放多张图纸,有时需要这些图纸分开,单独保存或显示。以往的做法是在cad中人工进行处理。今天小编教您在web端一键把CAD图自动分割成多张图纸并能把每个子图导出成单独的dwg文件或保存成图片。
例如要处理的CAD原图为:
自动识别所有子图的结果为(所有子图的范围都被紫色颜色所高亮显示了):
# 实现
# 先上效果图
# 原理介绍
# 自动分割图纸算法
# 算法原理:
子图的特征为,外面有一个图框,如果能找出所有图中的图框,就能根据图框位置自动拆分出所有子图了。
而图框的最外面为矩形,同时这个图框矩形外面没有被其他矩形所包含了。
(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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 获取所有子图范围矩形代码
// 得到一个图里面所有的矩形
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]); // 没有包含的,才以为是一个新的图的图框
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# 把子图保存为单独的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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
其实现代码为
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);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 把子图导出为图片
根据范围导出为图片可以利用唯杰地图提供的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";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
其实现代码为
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);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 最后
我们通过短短的一二百行代码即实现了把cad图自动分割成多张子图,并成单独保存为新的dwg文件和导出图片,大大减轻了人工手动分图的工作量。可访问 https://vjmap.com/demo/#/demo/map/service/22findsubmapsplit (opens new window) 在线体验。