Daily Build

AutoCAD 影像自动地理配准工具:INSGEO

INSGEO.lsp 在 AutoCAD 中按真实地理坐标插入栅格影像的 AutoLISP 脚本——开源、精准、批量。

简介

在 AutoCAD 里贴航拍图、正射影像或扫描地形图,常见做法有几种:手动量距离做缩放对齐(慢、不准);用 91 卫图助手、水经注等软件附带的 INSG 插件(INSG 最初是个人开发者共享出来的 vlx 插件,但它一次只能导入一张,几十张影像就要重复几十遍);或者上付费软件,如南方 CASS + CASS 3D 加载大体积影像。

INSGEO.lsp 是一个免费的 AutoLISP 脚本,一条命令 INSGEO 批量插入多张影像,根据每张的配准文件自动落在正确的世界坐标上

三个特点:

支持的影像格式:TIF / JPG / PNG / ECW 支持的配准文件:.tfw / .jgw / .pgw(ESRI World File)与 .ers(ER Mapper 头文件)

下载

脚本已上传至百度网盘,分享链接:https://pan.baidu.com/s/147_ljYpuInSk4ebW_lFNRg?pwd=ualr

加载与使用

INSGEO.lsp 拖入 AutoCAD,或在命令行执行 (load "C:/path/to/INSGEO.lsp")。加载成功后命令行会提示:

加载成功后命令行提示

输入 INSGEO 即可启动。脚本会弹出多文件选择对话框,左侧显示当前目录下符合扩展名的文件和子文件夹,可以双击进入子目录,选中文件后点 Add Files 添加到右侧"已选"列表,最终点确定:

多文件选择对话框

确定后脚本会逐张插入,每张插入完成都会在命令行打印进度,例如:

Inserting image 1 of 3 ...
Inserting image 2 of 3 ...
Inserting image 3 of 3 ...
Done. 3 image(s) inserted.

整个流程只需两步:输入命令、勾选文件。如果当某张影像没有任何配套配准文件,脚本会弹出文件对话框让用户手动指定;若仍然取消,会留在原点并打印提示。

工作流与团队协作

INSGEO 解决的是"插入"这一步。但插入完成的 dwg 怎么和团队同事共享、怎么交付给客户、影像加载怎么不卡——这些是实际工作里马上会遇到的问题。这一节给出三种典型场景的标准做法。

dwg 的影像引用机制

需要先讲清楚一个前提:AutoCAD 的影像引用只在 dwg 里存了一条路径,影像本身不嵌入 dwg 文件。打开 dwg 时,AutoCAD 按这条路径去硬盘上找影像文件:

所以"对方能不能看到影像"等价于"对方那台电脑按 dwg 里存的路径能不能找到影像文件"。下面三种场景对应"路径"的三种处理思路。

场景 A:个人使用 —— 本地路径就够了

dwg 始终在自己电脑上打开,影像放本地任何位置都行,无需特殊处理。

场景 B:团队协作 —— SMB 共享路径

把所有影像文件 + 配准文件放到团队 SMB 共享(局域网文件服务器、NAS,或者直接是某台主力机器开的共享),团队成员在自己电脑上 INSGEO 时直接引用共享路径。dwg 里存下来的引用是 UNC 路径,同事打开 dwg 时只要也能访问同一个共享,影像就直接显示,零配置

推荐用计算机名形式的 UNC

也就是 \\HOSTNAME\share\... 而不是 \\192.168.x.x\share\...。原因是 DHCP 环境下服务器 IP 可能变化,IP 一变 dwg 里所有引用就全失效;而计算机名通常是稳定的。在不方便给共享主机分配静态 IP 的场景下,计算机名形式比 IP 形式更省心。

在 INSGEO 对话框的 Folder 框里直接粘计算机名 UNC 即可:

基于计算机名的 SMB 共享路径

历史小注:LM:getfiles 原版用 AutoLISP 内置的 vl-directory-files 列目录,这个函数在某些 AutoCAD 版本下对计算机名形式的 UNC 解析失败(返回空列表,对话框只显示 ..),导致以前必须用 IP 形式才能正常工作。INSGEO.lsp 已经把这部分换成了 Scripting.FileSystemObject计算机名 UNC 现在完全可用。如果你拿到其它项目的 LSP 在 UNC 路径下出现"列不出文件"的现象,可以参考本脚本第 5 节的修复做法。

几个常见踩坑点

场景 C:对外交付 —— ETRANSMIT 打包

如果要把项目发给没有 SMB 访问权的外部客户,用 AutoCAD 自带的 ETRANSMIT 命令打包。它会自动收集 dwg 引用的所有外部文件(影像、字体、外参、打印样式),改写好相对路径,生成一个独立的 zip,对方解压后双击 dwg 即可正常显示所有影像。

操作步骤

打开 dwg,命令行输入 ETRANSMIT,弹出"创建传递"对话框,文件清单里能看到 dwg 加上它引用的影像 tif 等所有依赖:

ETRANSMIT 主对话框

点"传递设置"进入详细配置,几个值得留意的选项:

ETRANSMIT 设置

点确定生成 zip,发给客户即可。客户解压后打开 dwg,影像、配准、缩放全部正常。

加载性能:别忘了金字塔

大尺寸 tif 在 AutoCAD 里加载和缩放都慢。带内部金字塔或外部 .ovr 文件的 tif 在缩放时只读对应分辨率层,十倍以上加速

iTwin Capture Modeler 等主流摄影测量软件默认会同时生成 .tif.tif.ovr——前者是原图,后者是金字塔。两者必须一起拷贝:

如果发现某张 tif 没有配套 ovr(用 gdalinfo 检查输出里有没有 Overviews: 一行),可以用 GDAL 一行命令补:

gdaladdo -r average <file.tif> 2 4 8 16 32

这会在 tif 同目录下生成 <file.tif>.ovr。OSGeo4W、QGIS 都自带 GDAL,装一个就有命令行可用。

核心实现解析

如果你只是用脚本,本节可以跳过;以下面向想读懂或修改脚本的开发者。

脚本按职责分成 5 段:工具函数、命令入口、核心插入流程、两种配准文件解析器、Lee Mac 多文件选择对话框。下面按数据流顺序讲关键的几段。

1. 命令入口:批量循环

(defun c:insgeo ( / image-list total index path )
  (setq image-list (LM:getfiles "Select Geo-Referenced Image(s)"
                                ""
                                *insgeo-image-exts*))
  (setq total (length image-list)
        index 0)
  (foreach path image-list
    (setq index (1+ index))
    (princ (strcat "\nInserting image " (itoa index) " of " (itoa total) " ..."))
    (insgeo:place-image path)
  )
  (if (> total 0)
    (princ (strcat "\nDone. " (itoa total) " image(s) inserted."))
  )
  (princ)
)

入口很薄:调用 LM:getfiles 拿到一个文件路径列表,然后 foreach 逐个交给 insgeo:place-image 处理。这个写法天然兼顾"单张"和"批量"——选 1 个文件时列表只有 1 个元素,循环一次就结束,所以不需要专门的单张命令

2. 核心插入流程:insgeo:place-image

(defun insgeo:place-image ( image-path / image base sidecar ext minpt maxpt )

  ;; 把影像先插到原点,稍后再根据配准文件挪到正确位置
  (setq image (insgeo:add-raster image-path))

  ;; 去掉影像扩展名,得到查找配准文件的"基名"
  (setq base (vl-string-subst
               ""
               (vl-filename-extension (vla-get-imagefile image))
               (vla-get-imagefile image)))

  ;; 按优先级探测配套配准文件,都找不到则弹框让用户选
  (setq sidecar
    (cond
      ((findfile (strcat base ".ers")))
      ((findfile (strcat base ".tfw")))
      ((findfile (strcat base ".jgw")))
      ((findfile (strcat base ".pgw")))
      (t (getfiled "Select World File"
                   base
                   "tfw;jgw;pgw;ers"
                   0))
    )
  )

  (if sidecar
    (progn
      (setq ext (strcase (vl-filename-extension sidecar)))
      (cond
        ((= ext ".ERS") (insgeo:apply-ers       image sidecar))
        (t              (insgeo:apply-worldfile image sidecar))
      )
      ;; 缩放到刚插入的影像范围
      (vla-getboundingbox image 'minpt 'maxpt)
      (vla-zoomwindow (vlax-get-acad-object) minpt maxpt)
    )
    (princ (strcat "\nNo sidecar found for "
                   (vl-filename-base image-path)
                   "; image left at origin."))
  )
  (princ)
)

整个函数是"插入 → 找配准 → 应用配准 → 自动缩放"四步:

  1. 先插入再配准——insgeo:add-raster 把影像加到当前布局的 (0,0),返回 VLA 对象,后续只需要修改它的 OriginRotationImageWidthImageHeight 四个属性
  2. 配准文件探测优先级 ERS → TFW → JGW → PGW——cond 利用了 LISP 的特性:每个分支只有一个表达式,如果该表达式返回非空(findfile 返回路径)就当作整个 cond 的值。这样写比 if-else 嵌套清爽很多
  3. 格式分发——.ERS 走 ER Mapper 解析器,其它三种走标准 World File 解析器(它们的格式完全一样,只是扩展名不同)
  4. vla-zoomwindow——插入完直接缩放到该影像的包围盒,用户立刻能看到结果,不需要再 zoom extents

3. 标准 World File 解析:insgeo:apply-worldfile

ESRI World File 是固定的 6 行文本格式。脚本严格按行读取并应用变换:

(defun insgeo:apply-worldfile ( image path / fp x-size x-rot y-rot y-size origin )
  (setq fp     (open path "r")
        x-size (atof (read-line fp))     ; 第 1 行:X 方向像素大小
        x-rot  (atof (read-line fp))     ; 第 2 行:X 旋转
        y-rot  (atof (read-line fp))     ; 第 3 行:Y 旋转
        y-size (atof (read-line fp))     ; 第 4 行:Y 方向像素大小(通常为负)
        origin (list (atof (read-line fp))   ; 第 5 行:左上像素中心 X
                     (atof (read-line fp))   ; 第 6 行:左上像素中心 Y
                     0.0))
  (close fp)

  ;; World File 给的是"左上像素中心",但 AutoCAD 的影像原点是"左下角"。
  ;; 沿旋转后的左边沿往下走 (像素行数 × |y-size|) 就到左下角。
  (setq origin (polar origin
                      (degrad (- x-rot 90.0))
                      (* (vla-get-height image) (abs y-size))))

  (vla-put-rotation    image (degrad x-rot))
  (vla-put-origin      image (insgeo:point->variant origin))
  (vla-put-imageheight image (* (vla-get-height image) (abs y-size)))
  (vla-put-imagewidth  image (* (vla-get-width  image) (abs x-size)))
)

最关键的是中间那行 polar 的用法。坑点在于坐标基准点不同:

所以即使读到了 X/Y,也不能直接 vla-put-origin,必须沿着旋转后的影像左边沿往下平移一整张影像的高度,才是真正的左下角。polar 接收"起点+方向角+距离",一次完成这个平移。

4. ERS 解析:按关键字搜索而不是按行号

老版本脚本对 ERS 文件用 (repeat 19 (read-line fp)) 跳过前 19 行,然后假定第 20、21 行就是坐标——只要 ERS 头多一个字段或一个波段就会读错位置。新实现按关键字逐行扫描:

(defun insgeo:apply-ers ( image path / fp line
                                       east north
                                       x-size y-size
                                       reg-col reg-row
                                       top-left-x top-left-y )
  (setq fp      (open path "r")
        x-size  1.0   ; pixel width fallback
        y-size  1.0   ; pixel height fallback
        reg-col 0.0   ; registration column (0 = leftmost)
        reg-row 0.0)  ; registration row    (0 = topmost)

  ;; 通读全文,按关键字摘出关心的字段
  (while (setq line (read-line fp))
    (cond
      ((insgeo:ers-key? line "Eastings")          (setq east    (insgeo:ers-value line)))
      ((insgeo:ers-key? line "Northings")         (setq north   (insgeo:ers-value line)))
      ((insgeo:ers-key? line "Xdimension")        (setq x-size  (insgeo:ers-value line)))
      ((insgeo:ers-key? line "Ydimension")        (setq y-size  (insgeo:ers-value line)))
      ((insgeo:ers-key? line "RegistrationCellX") (setq reg-col (insgeo:ers-value line)))
      ((insgeo:ers-key? line "RegistrationCellY") (setq reg-row (insgeo:ers-value line)))
    )
  )
  (close fp)

  (if (and east north)
    (progn
      ;; 把"配准点"移回左上像素角...
      (setq top-left-x (- east  (* reg-col x-size))
            top-left-y (+ north (* reg-row y-size)))
      ;; ...再下移一整张图高,得到 AutoCAD 要的左下角原点
      (vla-put-origin image
        (insgeo:point->variant
          (list top-left-x
                (- top-left-y (* (vla-get-height image) y-size))
                0.0)))
      (vla-put-imagewidth  image (* (vla-get-width  image) x-size))
      (vla-put-imageheight image (* (vla-get-height image) y-size))
    )
    (princ (strcat "\nWarning: Eastings/Northings not found in " path))
  )
)

两个细节值得说明:

辅助函数 insgeo:ers-key?insgeo:ers-value 配合工作:前者用 vl-string-search 做大小写不敏感的子串匹配,后者从 = 右侧切出数值并 vl-string-trim 掉空白。这样写对 ERS 文件中各种空白格式(Eastings = 12345.0Eastings=12345.0、带制表符等)都能正确解析。

5. 多文件对话框

LM:getfiles 是 Lee Mac 写的多文件选择库(v1.6,2016 年版),原生 AutoCAD 的 getfiled 只能选一个文件,这个库填补了这个空缺。它运行时动态生成 DCL 临时文件来定义对话框界面,使用完自动删除——INSGEO.lsp 把它直接内嵌进来,无需另外加载。

文档前面那两张截图中的"两栏布局 + 文件夹路径栏 + Browse 按钮 + 列标题"都对应 DCL 里的几行声明(在脚本 5. LM:getfiles 一节里),如果想调整列表框大小、按钮宽度、列标题文字,只需要改那段 DCL 字符串即可。

针对 UNC 路径的小修改:LM:getfiles v1.6 原版用 vl-directory-files 列目录,这个 AutoLISP 内置函数对 NetBIOS 计算机名形式的 UNC 路径支持不完整——\\HOSTNAME\share 会返回空列表,必须改用 \\IP\share 才能工作。脚本里把这部分换成了 Scripting.FileSystemObject(FSO),增加了一个 LM:getfiles:fso 函数作为替代,带回退路径(FSO 失败则退回原函数,行为不会比原版更差)。修复后计算机名 UNC 完全可用,团队协作时可以直接用 \\HOSTNAME\share,省去对静态 IP 的依赖:

;;; ADDED 2026 by isweibin (not part of original Lee Mac code):
;;; List directory contents via Scripting.FileSystemObject. `kind' is
;;; 'subfolders or 'files. Falls back to vl-directory-files if FSO fails
;;; (so behaviour never gets worse than the original).
(defun LM:getfiles:fso ( dir kind / fso folder result )
  (setq fso (vlax-create-object "Scripting.FileSystemObject"))
  (setq result
    (vl-catch-all-apply
      (function
        (lambda ()
          (setq folder (vlax-invoke fso 'GetFolder dir)
                result (if (eq kind 'subfolders) '(".." ".") nil))
          (vlax-for item (vlax-invoke folder
                          (if (eq kind 'subfolders) 'SubFolders 'Files))
            (setq result (cons (vla-get-name item) result)))
          (reverse result)))))
  (vlax-release-object fso)
  (if (vl-catch-all-error-p result)
    (vl-directory-files dir nil (if (eq kind 'subfolders) -1 1))
    result)
)

这处修改在源码里对应 SPDX-Snippet 块的注释中明确披露。Lee Mac 原版的版权声明、freeware 非商用条款都完整保留;基于这部分代码做的修改,版权归 isweibin,但许可证仍然继承 Lee Mac 原条款(整个文件因此不能商用,除非未来彻底替换 LM:getfiles)。

项目结构速查

区段 内容 主要函数
1 角度工具 degrad / raddeg
2 命令入口 c:insgeo
3 核心插入流程 insgeo:place-image / insgeo:add-raster
4a World File 解析 insgeo:apply-worldfile
4b ERS 解析 insgeo:apply-ers / insgeo:ers-key? / insgeo:ers-value
4c COM 辅助 insgeo:point->variant
5 多文件对话框 LM:getfiles 及一系列 LM:getfiles:* 辅助(含 LM:getfiles:fso

致谢


本文采用 CC BY-NC-SA 4.0 协议发布,可自由转载、修改,但需保留作者署名、不可用于商业用途、衍生作品需以相同协议发布。