【ENVI二次开发学习整理 02】IDL语言基础与程序设计
📝内容范围:本文重点介绍IDL工作台、工程与源码文件、程序运行方式、变量、数组、字符串、结构体、指针、链表、哈希表、运算符、程序控制、参数传递和关键字继承。
整理方式:延续前一篇的知识笔记风格,减少零散分点,更多使用表格和连续解释,并将语法知识与ENVI二次开发中的实际数据处理场景联系起来。
文章目录
- 【ENVI二次开发学习整理 02】IDL语言基础与程序设计
- @[toc]
- 第一章 从IDL工作台开始认识程序开发
- 1.1 IDL工作台不仅是代码编辑器
- 1.2 工程化开发与命令行试验的区别
- 1.3 工作台中的常用视图
- 第二章 IDL程序的创建、编译和发布
- 2.1 第一个程序的作用不只是输出文字
- 2.2 源码文件名与过程名
- 2.3 编译、运行与发布的区别
- 第三章 变量、数据类型与动态类型
- 3.1 IDL变量的命名
- 3.2 变量的动态性
- 3.3 常见数据类型与使用场景
- 3.4 系统变量
- 第四章 数组:IDL程序设计的核心
- 4.1 数组为什么重要
- 4.2 数组的创建方式
- 直接赋值创建
- 函数创建
- 4.3 数组访问方式
- 4.4 数组计算与序列生成
- 4.5 数组存储与图像方向
- 第五章 字符串与文件名处理
- 5.1 字符串的创建
- 5.2 常用字符串函数
- 5.3 数值转字符串后的空格
- 5.4 从文件名提取日期
- 5.5 扩展名判断
- 第六章 结构体:组织一组相关数据
- 6.1 结构体的用途
- 6.2 匿名结构体与命名结构体
- 6.3 结构体访问
- 6.4 结构体继承与动态扩展
- 第七章 指针与堆变量
- 7.1 指针解决什么问题
- 7.2 指针的共享特点
- 7.3 `/NO_COPY`与大数据
- 7.4 指针的适用场景
- 第八章 链表与哈希表
- 8.1 链表
- 8.2 哈希表
- 8.3 结构体、链表和哈希表如何选择
- 第九章 运算符
- 9.1 数学运算符
- 9.2 逻辑与位运算
- 9.3 关系运算符
- 9.4 数组乘与矩阵乘
- 9.5 其他符号
- 第十章 程序控制
- 10.1 条件、循环与跳转
- 10.2 `IF`结构
- 10.3 `CASE`与`SWITCH`
- 10.4 遥感批处理中的循环
- 第十一章 参数传递
- 11.1 位置参数与关键字参数
- 11.2 地址传递与值传递
- 11.3 参数设计建议
- 第十二章 关键字继承
- 12.1 `_EXTRA`与`_REF_EXTRA`
- 12.2 关键字继承的使用场景
- 第十三章 将语言基础应用到ENVI二次开发
- 13.1 语言知识与开发任务的对应关系
- 13.2 一个典型文件批处理框架
- 13.3 代码组织建议
- 第十四章 常见错误与排查思路
- 第十五章 本篇学习重点总结
- 第十六章 术语表
文章目录
- 【ENVI二次开发学习整理 02】IDL语言基础与程序设计
- @[toc]
- 第一章 从IDL工作台开始认识程序开发
- 1.1 IDL工作台不仅是代码编辑器
- 1.2 工程化开发与命令行试验的区别
- 1.3 工作台中的常用视图
- 第二章 IDL程序的创建、编译和发布
- 2.1 第一个程序的作用不只是输出文字
- 2.2 源码文件名与过程名
- 2.3 编译、运行与发布的区别
- 第三章 变量、数据类型与动态类型
- 3.1 IDL变量的命名
- 3.2 变量的动态性
- 3.3 常见数据类型与使用场景
- 3.4 系统变量
- 第四章 数组:IDL程序设计的核心
- 4.1 数组为什么重要
- 4.2 数组的创建方式
- 直接赋值创建
- 函数创建
- 4.3 数组访问方式
- 4.4 数组计算与序列生成
- 4.5 数组存储与图像方向
- 第五章 字符串与文件名处理
- 5.1 字符串的创建
- 5.2 常用字符串函数
- 5.3 数值转字符串后的空格
- 5.4 从文件名提取日期
- 5.5 扩展名判断
- 第六章 结构体:组织一组相关数据
- 6.1 结构体的用途
- 6.2 匿名结构体与命名结构体
- 6.3 结构体访问
- 6.4 结构体继承与动态扩展
- 第七章 指针与堆变量
- 7.1 指针解决什么问题
- 7.2 指针的共享特点
- 7.3 `/NO_COPY`与大数据
- 7.4 指针的适用场景
- 第八章 链表与哈希表
- 8.1 链表
- 8.2 哈希表
- 8.3 结构体、链表和哈希表如何选择
- 第九章 运算符
- 9.1 数学运算符
- 9.2 逻辑与位运算
- 9.3 关系运算符
- 9.4 数组乘与矩阵乘
- 9.5 其他符号
- 第十章 程序控制
- 10.1 条件、循环与跳转
- 10.2 `IF`结构
- 10.3 `CASE`与`SWITCH`
- 10.4 遥感批处理中的循环
- 第十一章 参数传递
- 11.1 位置参数与关键字参数
- 11.2 地址传递与值传递
- 11.3 参数设计建议
- 第十二章 关键字继承
- 12.1 `_EXTRA`与`_REF_EXTRA`
- 12.2 关键字继承的使用场景
- 第十三章 将语言基础应用到ENVI二次开发
- 13.1 语言知识与开发任务的对应关系
- 13.2 一个典型文件批处理框架
- 13.3 代码组织建议
- 第十四章 常见错误与排查思路
- 第十五章 本篇学习重点总结
- 第十六章 术语表
第一章 从IDL工作台开始认识程序开发
1.1 IDL工作台不仅是代码编辑器
IDL工作台同时承担工程管理、源码编辑、命令执行、变量查看、帮助查询和程序调试等任务。对于刚开始学习IDL的人来说,工作台中最容易混淆的是“工程”“源码文件”“工作空间”和“搜索路径”之间的关系。
| 名称 | 作用 | 实际理解 |
|---|---|---|
| 工作空间 | 保存当前开发环境中的工程和配置 | 类似一个总的开发目录 |
| 工程 | 组织同一程序相关的源码和资源 | 一个完整IDL程序的管理单元 |
| 源码文件 | 保存IDL命令、过程或函数 | 通常使用.pro扩展名 |
| 搜索路径 | IDL查找过程、函数和数据的位置 | 文件不在路径中时可能无法调用 |
| 命令行 | 直接输入并执行IDL语句 | 适合试验语法和查看结果 |
| 变量查看器 | 查看当前变量的类型、维数和值 | 适合检查数组和结构体 |
| 命令历史 | 保存之前执行过的命令 | 便于重复运行和修改 |
工作空间负责管理开发环境,工程负责组织程序,源码文件保存代码,而搜索路径决定IDL能否找到相应的过程、函数或数据文件。后续进行ENVI二次开发时,一个工程通常会包含主程序、公共函数、配置文件、图标和测试数据。
1.2 工程化开发与命令行试验的区别
在IDL中,同一段代码可以先在命令行中试验,再整理到.pro文件中。命令行适合验证函数返回值、数组维度和语法写法,而工程和源码文件适合保存可重复运行的程序。
| 使用方式 | 优点 | 局限 | 更适合的阶段 |
|---|---|---|---|
| 命令行模式 | 立即执行、反馈快 | 不便保存复杂流程 | 语法试验和问题排查 |
| 批处理模式 | 文件中直接写命令并顺序执行 | 结构和复用能力有限 | 简单一次性任务 |
| 编译运行模式 | 支持过程、函数和工程组织 | 需要理解编译规则 | 正式程序和二次开发 |
| 工程运行 | 统一管理多个文件和资源 | 前期配置稍多 | 中大型程序 |
在ENVI二次开发中,较可靠的做法是先在命令行中验证某个ENVI API调用,再将其封装成过程或函数,最后加入工程和批处理流程。
1.3 工作台中的常用视图
| 视图或功能 | 主要用途 | 使用建议 |
|---|---|---|
| 编辑器 | 编写.pro源码 | 保持缩进和注释统一 |
| 变量查看器 | 查看变量类型和内容 | 重点检查数组维数与数据类型 |
| 命令行 | 交互执行命令 | 用于快速验证 |
| 命令历史 | 回看执行记录 | 可复制到源码文件中 |
| 工程浏览器 | 管理源码和资源 | 按功能划分文件 |
| 帮助系统 | 查询函数和关键字 | 不确定参数时优先查看 |
专题材料还提到源码格式、颜色和首选项设置。统一的源码格式虽然不改变程序结果,却会显著影响代码可读性。IDL程序中条件语句、循环和过程体较多,缩进不清会使逻辑错误难以及时发现。
第二章 IDL程序的创建、编译和发布
2.1 第一个程序的作用不只是输出文字
专题以HelloWorld.pro作为第一个IDL程序。它的主要目的不是学习输出字符串,而是完整经历以下开发过程:
一个典型IDL过程可以写成:
PRO HelloWorld PRINT, 'Hello World' END这里的PRO表示定义过程,HelloWorld是过程名,END表示过程结束。编译完成后,可在命令行中输入:
HelloWorld即可调用该过程。
2.2 源码文件名与过程名
在IDL开发中,源码文件名、主过程名和调用名通常保持一致。例如:
| 项目 | 示例 |
|---|---|
| 文件名 | helloworld.pro |
| 过程名 | HelloWorld |
| 调用方式 | HelloWorld |
保持一致有助于IDL通过搜索路径找到源码,也便于工程维护。后续编写ENVI工具时,建议每个文件只放置一个主要公开过程或函数,其余辅助过程可以放在同一文件前部或拆分到独立文件。
2.3 编译、运行与发布的区别
| 阶段 | 作用 | 结果 |
|---|---|---|
| 编写 | 保存IDL源码 | .pro文件 |
| 编译 | 检查语法并生成可运行定义 | 当前会话中可调用 |
| 运行 | 执行过程或函数 | 输出结果或生成文件 |
| 保存 | 将编译结果保存 | .sav文件 |
| 发布 | 将程序放入Runtime或虚拟机环境 | 供其他用户运行 |
专题材料给出的发布示例为:
make_rt, 'helloWorld', 'c:\temp\out', $ savefile='D:\trainning\helloWorld\helloworld.sav', /vm该示例体现了IDL程序可以生成可发布成果。实际发布时,还需要考虑数据文件、搜索路径、第三方库和用户运行环境。
第三章 变量、数据类型与动态类型
3.1 IDL变量的命名
专题材料给出的命名规则为:变量最大长度不超过255个字符,首位只能是字母或下划线,中后部可使用字母、数字、下划线和连接符号$。
| 合法示例 | 不合法示例 | 原因 |
|---|---|---|
abc_3$d | 4_line | 首位不能是数字 |
ok_24_bit | abc.cha | 不能使用点号 |
IDL_type | ||
_day_month_year |
IDL提供IDL_VALIDNAME检查或转换变量名:
result = IDL_VALIDNAME(string)也可以使用/CONVERT_ALL或/CONVERT_SPACES关键字处理不合法字符。该函数在根据文件名、字段名或外部配置动态生成变量或标签时具有实际价值。
3.2 变量的动态性
IDL变量具有动态类型。一个变量可以先保存整数,再保存浮点数、字符串或数组。
a = 4 a = 4.5 a = 'text' a = [1, 2, 3]动态类型让交互试验更加方便,但也可能隐藏类型错误。尤其在遥感计算中,整型除法会造成精度丢失。
专题材料给出的示例:
result = 8 / 5 aspect = !D.X_Size / !D.Y_Size若两个参与运算的值都是整数,结果可能按整型计算。为了获得浮点结果,可以显式写成:
result = 8.0 / 5.0 aspect = FLOAT(!D.X_Size) / FLOAT(!D.Y_Size)3.3 常见数据类型与使用场景
| 数据类型 | 典型写法 | 遥感开发中的用途 |
|---|---|---|
| Byte | 3B | 8位分类图、颜色值 |
| Integer | 3 | 一般整数和部分DN |
| Long | 4L | 较大整数和索引 |
| Float | 3.0 | 反射率、指数和一般计算 |
| Double | 3D或3.0D | 高精度计算 |
| String | 'text' | 文件路径、名称和配置 |
| Complex | COMPLEX(1,2) | 复数科学数据 |
| Pointer | PTR_NEW() | 动态数据和共享堆变量 |
| Object | LIST()、HASH() | 动态容器和对象 |
3.4 系统变量
IDL系统变量通常以感叹号开头,例如!D。专题还介绍了自定义系统变量:
DEFSYSV, '!变量名', 值使用时与普通变量类似:
PRINT, !变量名系统变量在整个IDL会话中可访问,适合保存全局配置,但若大量使用,会使程序依赖隐式状态。正式ENVI工程中,更建议将配置放入结构体、哈希表或参数文件中。
第四章 数组:IDL程序设计的核心
4.1 数组为什么重要
遥感影像本身就是数组。单波段影像是二维数组,多光谱和高光谱影像通常可以组织为三维数组。IDL支持0至8维数组,因此数组操作是后续图像计算、波段运算和ENVI栅格处理的基础。
专题材料强调,IDL数组下标按照“先列后行”的方式理解。对于二维影像数组:
value = image[column, row]第一维对应列,第二维对应行。若把顺序理解反了,读取位置、裁剪范围和邻域运算都会出现错误。
4.2 数组的创建方式
直接赋值创建
arr = [1, 2, 3] HELP, arr类型会根据元素自动确定。
| 表达式 | 推断类型 |
|---|---|
[1,2,3] | Integer数组 |
[1,2,3B] | 材料示例仍显示为Integer数组 |
[1,2,3D] | Double数组 |
数组元素类型通常需要保持一致。不同类型混合时,IDL会进行类型提升,因此应使用HELP检查实际类型。
函数创建
IDL可使用专门函数创建数组,例如:
a = INTARR(100, 100) b = FLTARR(100, 100) c = INDGEN(10) d = MAKE_ARRAY(10, 20, TYPE=4)| 函数 | 作用 |
|---|---|
BYTARR | 创建Byte全零数组 |
INTARR | 创建Integer全零数组 |
LONARR | 创建Long全零数组 |
FLTARR | 创建Float全零数组 |
DBLARR | 创建Double全零数组 |
STRARR | 创建字符串数组 |
INDGEN | 创建Integer索引数组 |
FINDGEN | 创建Float索引数组 |
MAKE_ARRAY | 按参数创建通用数组 |
4.3 数组访问方式
| 方式 | 示例 | 说明 |
|---|---|---|
| 单元素下标 | a[3] | 获取某个元素 |
| 二维下标 | img[10,20] | 获取第10列、第20行 |
| 向量下标 | a[[1,3,5]] | 获取多个指定元素 |
| 连续子数组 | a[2:6] | 获取索引2至6 |
| 二维裁剪 | img[x0:x1,y0:y1] | 获取矩形区域 |
| 全维选择 | cube[*,*,3] | 获取第4个波段 |
星号*表示该维度的全部元素。对于多波段数据,理解各维度顺序十分重要。不同文件读取函数可能返回不同维度组织方式,应通过HELP或SIZE确认。
4.4 数组计算与序列生成
专题给出了等差数列和等比数列示例:
PRINT, INDGEN(6) * 5 + 1结果为:
1 6 11 16 21 26等比数列:
PRINT, 3 ^ INDGEN(6) PRINT, 3 ^ INDGEN(6) * 2这种数组表达式说明IDL可以一次性完成整组数据计算。在遥感处理中,类似写法可用于:
- 构建波长序列;
- 生成坐标索引;
- 批量计算阈值;
- 创建查找表;
- 计算多波段系数。
4.5 数组存储与图像方向
专题材料说明数组按行存储,并展示了一维和二维数组的存储关系。实际使用时,最重要的是区分:
- 数组在内存中的顺序;
- 数组下标的维度顺序;
- 影像显示时的行列方向;
- 文件中的存储顺序。
在读取二进制影像时,若维度、字节顺序或行列理解错误,影像可能出现旋转、转置或条带错位。应使用少量已知数据验证数组方向,再处理完整影像。
第五章 字符串与文件名处理
5.1 字符串的创建
字符串可直接赋值:
str = 'string'字符串数组可使用:
names = STRARR(10)字符串在ENVI二次开发中非常重要,因为文件路径、栅格名称、波段名称、任务参数和输出目录通常都以字符串表示。
5.2 常用字符串函数
| 函数 | 作用 | 示例用途 |
|---|---|---|
STRTRIM | 删除字符串两侧空格 | 清理数值转字符串结果 |
STRMID | 截取子字符串 | 从文件名提取日期 |
STRPOS | 查找子字符串位置 | 查找扩展名分隔点 |
STRUPCASE | 转为大写 | 不区分大小写比较扩展名 |
STRLOWCASE | 转为小写 | 统一文件名 |
STRING | 数值转字符串 | 生成输出名称 |
STRSPLIT | 分割字符串 | 解析路径或配置 |
STRJOIN | 合并字符串 | 拼接名称列表 |
5.3 数值转字符串后的空格
专题示例:
var = 4L str = STRING(var) HELP, str转换结果前可能包含格式空格,因此常继续使用:
str = STRTRIM(str, 1)这在生成文件名时很重要。若不去除前导空格,输出文件名中可能出现难以发现的空白字符。
5.4 从文件名提取日期
专题使用:
file = 'AMOD0320040707140331.hdf' PRINT, STRMID(file, 6, 8)输出:
20040707这种固定位置截取适用于命名结构稳定的卫星产品。更通用的程序还应检查文件名长度和命名规则,避免对不符合规范的文件直接截取。
5.5 扩展名判断
专题示例展示了如何查找扩展名并跳过头文件:
fName = FILE_BASENAME(imageNames[k]) pPos = STRPOS(fName, '.', /REVERSE_SEARCH) IF pPos[0] NE -1 THEN BEGIN IF STRUPCASE(STRMID(fName, pPos[0]+1, 3)) EQ 'HDR' THEN CONTINUE ENDIF| 代码步骤 | 作用 |
|---|---|
FILE_BASENAME | 去除目录,仅保留文件名 |
STRPOS(...,/REVERSE_SEARCH) | 从后向前查找最后一个点号 |
STRMID | 提取扩展名 |
STRUPCASE | 转为大写后比较 |
CONTINUE | 跳过当前循环 |
该模式非常适合批量处理遥感数据时过滤.hdr、临时文件和非目标格式。
第六章 结构体:组织一组相关数据
6.1 结构体的用途
结构体可以把标量、数组、字符串甚至其他结构体组合为一个复合变量。与多个独立变量相比,结构体更适合表示一个完整对象的属性。
例如,一个遥感影像信息结构可以包含:
- 文件路径;
- 行列数;
- 波段数;
- 数据类型;
- 投影;
- NoData;
- 成像日期。
6.2 匿名结构体与命名结构体
p = CREATE_STRUCT('A', 1, 'B', 'xxx')创建匿名结构体。
p = CREATE_STRUCT(NAME='struct1', 'A', 1, 'B', 'xxx')创建命名结构体。
也可使用花括号语法:
info = {file:'image.dat', ncols:1000L, nrows:800L}6.3 结构体访问
| 访问方式 | 示例 | 说明 |
|---|---|---|
| 标签名访问 | info.file | 按字段名获取 |
| 动态索引访问 | info.(idx) | 按字段顺序获取 |
| 标签查询 | TAG_NAMES(info) | 获取字段名称 |
| 字段数量 | N_TAGS(info) | 获取标签数 |
按字段名访问更清晰,动态索引适用于遍历结构体字段。
6.4 结构体继承与动态扩展
专题示例:
str1 = {str1name, a:1, b:2} str2 = {str2name, INHERITS str1name, c:3}表示命名结构体可以继承已有结构。
匿名结构体可使用CREATE_STRUCT动态扩展:
a = {a:1, b:2} a = CREATE_STRUCT(a, 'c', 3)结构体适合固定字段的数据组织。若字段需要频繁增加或删除,哈希表通常更加灵活。
第七章 指针与堆变量
7.1 指针解决什么问题
专题材料指出,指针指向存储在堆中的变量。堆变量在程序运行期间可动态分配,并通过指针访问。
c = PTR_NEW(2.0D) PRINT, *cPTR_NEW创建指针,*c表示解引用并获取指向的数据。
7.2 指针的共享特点
若两个指针指向同一堆变量,对其中一个进行修改,会影响另一个访问到的结果。这是因为修改的是堆中的同一份数据,而不是复制后的独立变量。
| 操作 | 含义 |
|---|---|
PTR_NEW(data) | 创建堆变量并复制数据 |
PTR_NEW(data,/NO_COPY) | 将数据直接移入堆,减少复制 |
*point | 访问指针指向的数据 |
PTR_VALID(point) | 检查指针是否有效 |
PTR_FREE, point | 释放堆变量 |
7.3/NO_COPY与大数据
pData = PTR_NEW(data, /NO_COPY)/NO_COPY用于避免复制大数组,可以减少内存消耗。执行后,原变量的数据所有权发生转移,程序不应继续假设原变量仍保存完整数据。
在遥感影像处理中,一个数组可能占用数百MB甚至更多。指针和/NO_COPY可以提高内存效率,但也增加了内存管理复杂度。使用完毕后应释放指针:
PTR_FREE, pData7.4 指针的适用场景
| 场景 | 使用指针的原因 |
|---|---|
| 可选大数组 | 没有数据时可使用空指针 |
| 多模块共享数据 | 避免重复复制 |
| 动态结构 | 运行期间创建和释放 |
| 复杂界面状态 | 在事件处理中保存共享变量 |
| 大影像缓存 | 减少内存复制 |
如果普通变量已经能够清晰完成任务,就不必为了“高级”而使用指针。
第八章 链表与哈希表
8.1 链表
IDL链表可保存不同类型的成员,适合长度动态变化的有序集合。
list = LIST('one', 2.0, 3, 4L, PTR_NEW(5), {n:6}, COMPLEX(7,0))成员可通过索引访问:
PRINT, list[0]| 链表方法 | 作用 |
|---|---|
Add | 添加成员 |
Count | 返回成员数量 |
IsEmpty | 判断是否为空 |
Remove | 移除成员 |
Reverse | 逆转顺序 |
ToArray | 转换为数组 |
Where | 按条件查询 |
链表适合保存文件列表、任务列表、动态结果集合和不同类型对象。
8.2 哈希表
哈希表通过键访问值,适合保存配置项和名称—对象映射。
hash = HASH('black', 0, 'gray', 128, 'grey', 128, 'white', 255)访问:
PRINT, hash['black']新增成员:
hash['green'] = 50也可写为:
hash += HASH('green', 50)| 哈希表方法 | 作用 |
|---|---|
Count | 返回成员数量 |
HasKey | 判断是否包含键 |
IsEmpty | 判断是否为空 |
Keys | 返回所有键 |
Values | 返回所有值 |
Remove | 删除成员 |
ToStruct | 转换为结构体 |
Where | 查询成员 |
8.3 结构体、链表和哈希表如何选择
| 数据结构 | 主要特点 | 更适合的情况 |
|---|---|---|
| 结构体 | 字段固定、访问清晰 | 影像元数据、固定配置 |
| 链表 | 有序、长度动态、成员可异构 | 文件队列、任务序列 |
| 哈希表 | 键值访问、字段动态 | 参数表、名称映射和配置 |
在ENVI批处理中,可使用链表保存待处理文件,使用哈希表保存任务参数,使用结构体保存每个结果的固定统计信息。
第九章 运算符
9.1 数学运算符
| 运算符 | 含义 |
|---|---|
+ | 加法 |
++ | 自增 |
- | 减法 |
-- | 自减 |
* | 乘法 |
/ | 除法 |
^ | 幂 |
MOD | 取余 |
< | 取较小值 |
> | 取较大值 |
IDL中的<和>除了出现在关系判断中,还可以作为取小和取大运算。使用时应结合上下文区分。
9.2 逻辑与位运算
| 类型 | 运算符 |
|---|---|
| 逻辑与 | && |
| 逻辑或 | ` |
| 逻辑非 | ~ |
| 位与 | AND |
| 位非 | NOT |
| 位或 | OR |
| 位异或 | XOR |
逻辑运算用于条件真假,位运算直接处理整数的二进制位。遥感质量控制数据经常使用位编码,因此位与、位移和掩码操作具有实际用途。
9.3 关系运算符
| 运算符 | 含义 |
|---|---|
EQ | 等于 |
NE | 不等于 |
GE | 大于等于 |
GT | 大于 |
LE | 小于等于 |
LT | 小于 |
IDL关系运算通常写成关键字形式,而不是常见的==、!=。例如:
IF value GT 0 THEN PRINT, value对数组使用关系运算时,会返回布尔数组,可配合WHERE筛选像元。
9.4 数组乘与矩阵乘
专题材料区分了:
| 运算符 | 作用 |
|---|---|
# | 数组乘 |
## | 矩阵乘 |
矩阵维度和乘法方向需要通过小型示例验证。对于遥感算法中的线性变换、回归和主成分计算,矩阵形状错误是常见问题,应在计算前使用HELP检查维度。
9.5 其他符号
| 符号 | 用途 |
|---|---|
() | 控制运算顺序、函数参数 |
[] | 数组和下标 |
?: | 条件表达式 |
-> | 调用对象方法 |
* | 指针解引用 |
$ | 续行符 |
$还可出现在变量名中,但在代码行末通常表示下一行继续。为了避免阅读混乱,应保持续行缩进统一。
第十章 程序控制
10.1 条件、循环与跳转
| 类型 | 语句 | 适用场景 |
|---|---|---|
| 条件 | IF | 单一条件判断 |
| 多分支 | CASE | 多个互斥值 |
| 多分支 | SWITCH | 允许继续执行后续分支 |
| 固定循环 | FOR | 已知次数 |
| 条件循环 | WHILE | 条件成立时继续 |
| 后判断循环 | REPEAT | 至少执行一次 |
| 容器遍历 | FOREACH | 遍历链表、哈希表等 |
| 跳出 | BREAK | 退出循环 |
| 跳过 | CONTINUE | 跳到下一轮 |
| 跳转 | GOTO | 跳到标签,不建议大量使用 |
10.2IF结构
IF condition THEN BEGIN ; 条件成立 ENDIF ELSE BEGIN ; 条件不成立 ENDELSE当分支只有一条语句时,可以省略BEGIN和ENDIF,但复杂程序中保持块结构通常更清晰。
10.3CASE与SWITCH
CASE适合根据一个表达式选择一个分支;SWITCH允许从匹配位置继续向后执行。若不了解两者差异,容易造成多执行分支的问题。
10.4 遥感批处理中的循环
FOR i = 0, N_ELEMENTS(files)-1 DO BEGIN file = files[i] ; 打开并处理文件 ENDFOR批处理程序还应考虑:
- 文件列表为空;
- 某个文件读取失败;
- 输出已存在;
- 单个文件失败后是否继续;
- 是否记录日志。
程序控制不仅用于实现流程,还决定程序在异常条件下是否稳定。
第十一章 参数传递
11.1 位置参数与关键字参数
IDL过程和函数可以同时使用位置参数和关键字参数。
PRO Process_Image, input_file, output_file, METHOD=method, OVERWRITE=overwrite调用:
Process_Image, in_file, out_file, METHOD='nearest', /OVERWRITE| 参数类型 | 特点 | 建议用途 |
|---|---|---|
| 位置参数 | 按顺序传入 | 必需且数量较少的参数 |
| 关键字参数 | 按名称传入 | 可选参数和配置项 |
| 开关关键字 | 使用/KEYWORD | 布尔选项 |
位置参数过多会降低可读性,因此复杂ENVI工具通常把可选配置设计为关键字。
11.2 地址传递与值传递
专题材料强调IDL参数传递涉及地址和值。IDL过程参数通常可以修改调用者变量,因此编写过程时应明确哪些参数是输入、哪些是输出。
例如:
PRO Add_One, value value = value + 1 END调用后,原变量可能发生改变。为了避免意外修改,可以在过程内部创建副本,或在文档中明确参数方向。
11.3 参数设计建议
| 参数类别 | 命名建议 | 示例 |
|---|---|---|
| 输入 | 名称直接表示数据 | input_raster |
| 输出 | 可使用out_前缀 | out_result |
| 可选参数 | 使用关键字 | METHOD=method |
| 布尔开关 | 使用开关关键字 | /OVERWRITE |
| 错误信息 | 独立输出或异常机制 | ERROR=error |
第十二章 关键字继承
12.1_EXTRA与_REF_EXTRA
专题材料给出两种关键字继承机制:
| 关键字 | 传递方式 |
|---|---|
_EXTRA | 值传递 |
_REF_EXTRA | 引用传递 |
关键字继承允许一个过程接收自身未明确声明的关键字,并继续传递给内部调用的过程或函数。这样可以减少包装函数重复声明大量参数。
12.2 关键字继承的使用场景
假设自定义过程封装了绘图函数:
PRO Show_Result, data, _EXTRA=extra p = PLOT(data, _EXTRA=extra) END调用者可以把颜色、标题和线型等关键字传给内部PLOT函数,而Show_Result不需要逐一声明所有绘图参数。
在ENVI二次开发中,关键字继承可用于:
- 包装ENVI API;
- 传递任务参数;
- 扩展绘图和输出函数;
- 保持接口兼容。
但关键字传递过于隐式,也会降低代码可读性。对于核心业务参数,仍应明确声明。
第十三章 将语言基础应用到ENVI二次开发
13.1 语言知识与开发任务的对应关系
| IDL知识 | ENVI二次开发中的用途 |
|---|---|
| 变量与类型 | 保存路径、阈值、数据类型和任务参数 |
| 数组 | 处理波段、掩膜和像元计算 |
| 字符串 | 文件命名、目录遍历和产品日期解析 |
| 结构体 | 保存影像元数据和统计结果 |
| 指针 | 管理大数组和界面共享状态 |
| 链表 | 保存动态文件和任务列表 |
| 哈希表 | 保存配置和名称—对象映射 |
| 运算符 | 实现指数、阈值和逻辑筛选 |
| 控制语句 | 批处理、条件分支和异常流程 |
| 参数传递 | 设计可复用过程和函数 |
| 关键字继承 | 包装ENVI与绘图接口 |
13.2 一个典型文件批处理框架
PRO Batch_Process, input_dir, output_dir, OVERWRITE=overwrite files = FILE_SEARCH(input_dir, '*.dat', COUNT=count) IF count EQ 0 THEN BEGIN PRINT, 'No input files.' RETURN ENDIF FOR i = 0, count-1 DO BEGIN input_file = files[i] base_name = FILE_BASENAME(input_file, '.dat') output_file = FILEPATH(base_name + '_result.dat', ROOT_DIR=output_dir) IF FILE_TEST(output_file) AND ~KEYWORD_SET(overwrite) THEN CONTINUE ; 在此调用ENVI任务或自定义算法 ENDFOR END这个框架同时使用了字符串、数组、循环、条件、关键字参数和文件函数。后续加入ENVI API后,就可以发展为实际遥感批处理程序。
13.3 代码组织建议
| 文件类型 | 建议内容 |
|---|---|
| 主程序 | 程序入口和总体流程 |
| 数据读取模块 | 文件搜索、元数据和数据打开 |
| 算法模块 | 指数、分类和统计计算 |
| 输出模块 | 文件保存、命名和日志 |
| 公共工具 | 字符串、路径和参数检查 |
| 测试文件 | 小数据和单元测试 |
把所有代码写在一个.pro文件中,短期看起来方便,但后期难以维护。随着ENVI开发内容增加,应逐步按功能拆分。
第十四章 常见错误与排查思路
| 问题表现 | 常见原因 | 排查方式 |
|---|---|---|
| 除法结果为整数 | 输入变量是整型 | 使用HELP检查类型并显式转浮点 |
| 数组裁剪位置错误 | 行列下标顺序理解错误 | 用小数组测试索引 |
| 读取影像方向异常 | 维度或存储顺序错误 | 检查SIZE和原始文件说明 |
| 文件找不到 | 文件不在搜索路径 | 检查工作目录和IDL路径 |
| 字符串比较失败 | 大小写或空格不同 | 使用STRUPCASE和STRTRIM |
| 结构体无法新增字段 | 结构体字段固定 | 使用CREATE_STRUCT扩展或改用哈希表 |
| 内存持续增长 | 指针或对象未释放 | 使用PTR_FREE和OBJ_DESTROY |
| 循环跳过错误文件 | 扩展名解析不正确 | 检查STRPOS和STRMID结果 |
| 参数被意外修改 | 地址传递 | 明确输入输出并创建副本 |
| 关键字未传入内部函数 | _EXTRA使用错误 | 检查关键字继承写法 |
排查IDL程序时,应优先检查数据类型、数组维数和文件路径。许多看似算法错误的问题,实际来自整型计算、维度顺序或字符串空格。
第十五章 本篇学习重点总结
IDL语言基础可以概括为三条主线。
第一条是数据线:变量、数组、字符串、结构体、指针、链表和哈希表决定程序如何保存和组织数据。其中数组是遥感影像处理的核心,字符串决定文件和产品管理,结构体与哈希表适合保存元数据和配置。
第二条是计算线:运算符、条件、循环和参数传递决定算法如何执行。只有理解整型和浮点型差异、数组关系运算和矩阵维度,才能正确实现遥感公式和批处理。
第三条是工程线:工作空间、工程、源码文件、编译、运行和发布决定程序如何从临时代码变成可维护工具。ENVI二次开发最终需要同时具备这三类能力。
第十六章 术语表
| 术语 | 说明 |
|---|---|
| 工作空间 | 用于管理IDL工程和环境配置的目录 |
| 工程 | 组织同一程序源码与资源的开发单元 |
.pro | IDL源码文件扩展名 |
.sav | IDL编译保存文件 |
| 动态类型 | 变量在运行中可改变数据类型 |
| 系统变量 | 以!开头的全局系统状态变量 |
| 数组 | IDL科学计算和图像处理的核心数据结构 |
| 结构体 | 由多个命名字段组成的复合变量 |
| 指针 | 指向堆变量的引用 |
| 堆变量 | 在运行期间动态分配的全局内存变量 |
| 链表 | 有序且长度可动态变化的对象容器 |
| 哈希表 | 按键访问值的动态容器 |
WHERE | 根据条件查找数组索引 |
# | IDL数组乘运算符 |
## | IDL矩阵乘运算符 |
| 位置参数 | 按调用顺序传递的参数 |
| 关键字参数 | 按名称传递的可选参数 |
_EXTRA | 以值方式继承关键字 |
_REF_EXTRA | 以引用方式继承关键字 |
/NO_COPY | 在部分操作中避免复制大数据的关键字 |