---
title: 实体对象智能识别
source: https://vjmap.com/guide/maprecognizer.html
---

# 实体对象智能识别

## 概述

实体对象智能识别能够在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 `<h3>块名称: ${f.properties.blockName}</h3>图层名称: ${f.properties.layerName}<br>对象ID: ${f.properties.objectId}${f.properties.attributeDef ? '<br>属性数据: ' + 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. 

