
1. 项目概述为什么从Arcade的Text和逻辑函数开始如果你正在接触ArcGIS平台尤其是ArcGIS Pro、ArcGIS Online或者ArcGIS Enterprise中的地图样式、标注、弹出窗口或者属性规则那你大概率已经听说过Arcade。它不是街机游戏而是Esri专门为地理空间数据处理设计的一种轻量级脚本语言。很多朋友刚接触时会觉得它有点像JavaScript但又有很多独特的函数和语法专门用来处理地图上的要素Feature和它们的属性。那么为什么这个系列的第一篇要从Text函数和逻辑函数讲起呢从我十多年的GIS项目经验来看这恰恰是新手最容易上手、也最能立刻看到效果的两个领域。几乎你做的第一件事就是想把数据“说人话”——把一堆数字代码转换成可读的文本显示在地图上比如把“1”显示为“住宅区”或者把“20240101”显示为“2024年1月1日”。这就是Text函数的用武之地。而当你需要根据条件来决定显示什么、怎么显示或者计算什么值时逻辑函数就登场了。比如你想在地图上高亮显示所有面积大于1000平方米且状态为“在建”的地块这个“且”和“大于”的判断就是逻辑函数的范畴。最近在社区里看到不少朋友遇到一个报错“逻辑 and ()和 or (||)运算符的操作数必须可转换为标量逻辑值。请使用 any 或 all 函数将操作数简化为标量逻辑值。” 这个错误信息听起来有点拗口但其实它直指Arcade逻辑运算的一个核心特点也是新手最容易踩的坑。我们会在后面的逻辑函数部分结合这个热词把它彻底讲透。所以这篇内容的目标很明确让你能独立使用Arcade的Text函数和逻辑函数解决地图表达中最常见的“文本格式化”和“条件判断”问题并避开那些典型的错误。无论你是GIS分析师、地图制图师还是开发者这些基础都是构建更复杂表达式的基石。2. Arcade表达式基础与环境准备在深入函数之前我们得先搞清楚Arcade表达式写在哪里、怎么运行。这就像你要做饭得先找到厨房和灶具。2.1 Arcade的应用场景它在哪里生效Arcade不是一个独立的编程环境它的脚本我们称之为“表达式”是嵌入在ArcGIS平台的各个功能模块中执行的。主要应用场景有四个标注Labeling这是最常用的场景。你可以写一个Arcade表达式动态决定每个要素上显示什么文本。比如一个地块的标注可能是“[地块编号] - [面积]平方米”。弹出窗口Pop-ups当用户点击地图上的要素时弹出的信息窗口内容可以用Arcade来丰富和格式化比直接显示原始字段美观得多。可视化Symbology你可以用Arcade表达式来驱动地图的显示样式。例如用一个返回数字的表达式来定义颜色渐变或者用一个返回文本的表达式来匹配不同的符号。属性规则Attribute Rules在数据编辑时进行自动计算或约束验证。比如自动计算多边形要素的周长或者在用户输入非法值时弹出警告。对于初学者我强烈建议从标注开始练习。因为标注的反馈是最直观、最即时的——写完表达式地图上立刻就能看到变化非常有利于学习和调试。2.2 编写与调试你的第一个表达式我们以在ArcGIS Pro中为图层设置标注为例。打开标注设置在内容列表Contents中右键点击你的图层选择“标注Labeling”然后点击“标注Label”按钮下拉菜单中的“表达式Expression...”。进入表达式构建器这会打开表达式构建器窗口。窗口上方是表达式编辑区下方是预览窗口会显示当前选中要素的表达式计算结果。理解上下文在编辑区的右侧你会看到“字段Fields”和“函数Functions”两个选项卡。“字段”里列出了当前图层的所有属性字段你可以直接双击将其插入表达式。“函数”则按类别列出了所有可用的Arcade函数包括我们今天要讲的Text和逻辑函数。这里有个非常重要的实操心得一定要善用“预览Preview”功能。在编辑表达式时下方预览区会实时或在你点击“验证”后显示结果。你可以通过点击地图选择不同的要素来查看你的表达式在不同要素上的计算结果是否符合预期。这是调试Arcade表达式最有效的手段没有之一。注意Arcade表达式是大小写敏感的。$feature.OBJECTID和$feature.objectid指向的是不同的东西通常字段名是大写的写错了就会报“未找到变量”的错误。刚开始时最稳妥的方法是从右侧的字段列表里双击插入而不是自己手动输入。3. Text函数详解让数据“说人话”Text函数顾名思义就是处理文本字符串的函数。在地图表达中我们很少会直接把原始的、冰冷的字段值扔给用户看。Text函数能帮你进行格式化、拼接、提取和转换。3.1 字符串拼接与格式化Concatenate与Text函数这是最基础也是最常用的操作把多个字段或文本组合成一句通顺的话。Concatenate(value1, value2, ...): 将多个值连接成一个字符串。它接受数字、文本、日期等各种类型在连接时会自动将它们转换为文本。// 示例将姓名和年龄拼接 Concatenate($feature.NAME, 的年龄是 , $feature.AGE, 岁。) // 如果NAME字段值为“张三”AGE为30则结果为“张三 的年龄是 30 岁。”不过在实际使用中我更喜欢用更简洁的加号来进行拼接这在Arcade里是允许的而且更符合编程习惯$feature.NAME 的年龄是 $feature.AGE 岁。效果完全一样但写起来更顺手。Text(value, format): 这是一个功能强大的格式化函数尤其适用于数字和日期。格式化数字比如控制小数位数、添加千位分隔符、显示为百分比。// 假设 $feature.AREA 字段值为 1234567.8912 Text($feature.AREA, “#,###.##”) // 结果“1,234,567.89” Text($feature.AREA, “#.##%”) // 先将数字乘以100然后加百分号结果“123456789.12%”通常需要先除以100 Text($feature.AREA / 10000, “#.## 公顷”) // 结果“123.46 公顷”#代表数字占位符,是千位分隔符.是小数点。“#,###.##”这个格式符的意思是整数部分使用千位分隔符保留两位小数。格式化日期这是Text函数的杀手级应用。GIS数据里的日期字段常常是“20240101”这样的数字或者时间戳直接显示非常不友好。// 假设 $feature.SURVEY_DATE 是一个日期型字段 Text($feature.SURVEY_DATE, “YYYY年MM月DD日”) // 结果“2024年01月15日” Text($feature.SURVEY_DATE, “MMM D, YYYY”) // 结果“Jan 15, 2024”英文月份缩写常用的日期格式符YYYY-四位年份MM-两位月份DD-两位日期HH-24小时制小时mm-分钟。避坑技巧当你用Text函数格式化一个可能为null空值的字段时整个表达式可能会返回null导致标注不显示。一个稳健的做法是先用IsEmpty()函数判断var myDate $feature.SURVEY_DATE; if (IsEmpty(myDate)) { return “日期未记录”; } else { return Text(myDate, “YYYY-MM-DD”); }3.2 字符串操作截取、查找与替换有时你需要加工文本比如只显示地址的前一部分或者替换掉某些字符。Left(text, count)/Right(text, count)/Mid(text, start, count): 截取子字符串。Left(“Arcade函数”, 3)返回 “Arc”。Mid(“北京市海淀区中关村”, 3, 2)返回 “海淀”。这里注意Arcade中字符串的起始位置是0所以“海”是第3个字符取2个字符。Find(text, search_text): 查找子串的位置返回索引从0开始如果没找到则返回-1。Find(“北京市海淀区”, “海淀”) // 返回 3 Find(“北京市海淀区”, “上海”) // 返回 -1Replace(text, old_text, new_text): 替换文本。Replace(“面积100㎡”, “㎡”, “平方米”) // 返回 “面积100平方米”Lower(text)/Upper(text): 转换为小写/大写。这在做不区分大小写的比较时很有用。Lower($feature.TYPE) “residential” // 无论TYPE字段是“Residential”还是“RESIDENTIAL”都会转为小写后比较。一个综合示例我们有一个“地址”字段格式是“省-市-区-详细地址”我们只想显示“市-区”部分。var fullAddress $feature.ADDRESS; // 假设值为 “广东省-深圳市-南山区-科技园科苑路” var firstDash Find(fullAddress, “-”); // 找到第一个“-”的位置是2 var secondDash Find(fullAddress, “-”, firstDash 1); // 从第一个“-”之后开始找第二个“-”是5 var thirdDash Find(fullAddress, “-”, secondDash 1); // 找第三个“-”是8 if (firstDash 0 secondDash firstDash) { // 截取第一个“-”和第三个“-”之间的部分不包含“-”本身 return Mid(fullAddress, firstDash 1, thirdDash - firstDash - 1); } else { return fullAddress; // 如果格式不对返回原地址 } // 返回结果“深圳市-南山区”这个例子结合了Find和Mid函数并考虑了边界情况。在实际工作中数据往往不那么规整所以增加if判断是个好习惯。4. 逻辑函数与条件判断让地图“智能”起来逻辑函数是表达式的“大脑”它让地图能够根据数据的不同情况做出不同的响应。核心就是if-else语句和逻辑运算符。4.1 逻辑运算符,||,!与常见陷阱这三个运算符和大多数编程语言一样(逻辑与)两边条件都为真时结果才为真。||(逻辑或)两边条件有一个为真时结果就为真。!(逻辑非)对条件取反。看起来很简单但陷阱就藏在细节里。让我们回到开篇提到的那个热词错误“逻辑 and ()和 or (||)运算符的操作数必须可转换为标量逻辑值。请使用 any 或 all 函数将操作数简化为标量逻辑值。”这个错误究竟是什么意思在Arcade中当你对数组Array或集合进行逻辑比较时不能直接使用或||。比如你有一个字段Categories它的值可能是一个数组[“A”, “B”, “C”]。你想判断这个数组里是否包含“A”你可能会错误地写成// 错误写法这将引发上述错误。 $feature.Categories “A” $feature.Status “Active”这里的问题在于$feature.Categories “A”这个比较如果Categories是数组它返回的不是一个简单的true或false而可能是一个布尔值数组[true, false, false]。运算符不知道如何处理这个数组。正确的解决方案使用Any或All函数。Any(iterable, value)检查数组或字典等可迭代对象中是否至少有一个元素满足条件等于某个值或符合某个判断。返回true或false。All(iterable, value)检查数组或字典等可迭代对象中是否所有元素都满足条件。返回true或false。所以上面的例子应该改写为// 正确写法判断数组Categories中是否包含“A” Any($feature.Categories, “A”) $feature.Status “Active”Any($feature.Categories, “A”)会遍历数组只要发现有一个元素等于“A”就立即返回true否则返回false。这样它就变成了一个可以处理的“标量逻辑值”。另一个常见场景判断数值是否在某个范围内。这需要组合使用。// 判断面积在100到500之间包含边界 var area $feature.AREA; if (area 100 area 500) { return “中等面积”; } // 判断状态为“已完成”或“已审核” if ($feature.Status “Completed” || $feature.Status “Reviewed”) { return “可归档”; }4.2 条件判断函数IIf,Decode,When除了标准的if-else语句Arcade提供了几个更简洁的内置函数来处理条件逻辑。IIf(condition, true_value, false_value): 如果条件为真返回第二个参数否则返回第三个参数。它是“Inline If”的缩写非常适合简单的二选一。// 用IIf替代if-else var label IIf($feature.POPULATION 1000000, “大城市”, “一般城市”); // 等价于 var label; if ($feature.POPULATION 1000000) { label “大城市”; } else { label “一般城市”; }Decode(value, case1, result1, case2, result2, ..., default_result): 类似于编程中的switch-case语句根据一个值匹配多种情况。它比一连串的else if更清晰。// 根据用地类型代码返回中文名称 Decode($feature.LANDUSE_CODE, “R”, “居住用地”, “C”, “商业用地”, “M”, “工业用地”, “G”, “绿地与广场用地”, “未知用地类型” // 默认值 )When(expr1, result1, expr2, result2, ..., default_result): 这是功能最强大的条件函数。它依次判断多个表达式expr1,expr2...返回第一个为真的表达式所对应的结果。它允许每个条件都是独立的逻辑表达式而Decode只是比较值是否相等。// 根据人口和面积综合判断城市等级 When( $feature.POPULATION 5000000 $feature.AREA 1000, “特大城市”, $feature.POPULATION 1000000, “大城市”, $feature.POPULATION 500000, “中等城市”, “小城市” // 默认值 )When函数会按顺序判断先看是否满足“人口500万且面积1000”不满足再看是否“人口100万”以此类推。这里有个关键点条件的顺序很重要如果把“人口100万”放在第一行那么所有人口超过100万的要素包括那些超过500万的都会被匹配为“大城市”后面的条件就不再判断了。选择建议对于简单的真假判断用IIf对于基于一个值的多路精确匹配用Decode对于需要复杂逻辑判断的多分支情况用When。When是最通用、最强大的。5. 实战演练构建一个完整的智能标注表达式现在我们把Text函数和逻辑函数组合起来解决一个真实场景的问题。场景我们有一个“建设项目”图层包含以下字段PROJ_NAME(文本)项目名称STATUS(文本)状态值可能是 “Planned”计划中、“InProgress”进行中、“Completed”已完成、“OnHold”已暂停BUDGET(数字)预算万元START_DATE(日期)开始日期AREA(数字)占地面积平方米需求我们需要一个标注能直观反映项目状态和关键信息。具体要求项目名称始终显示。状态用中文和颜色符号表示例如进行中。如果预算超过1000万元在名称后加一个“”符号。显示格式化的开始日期和占地面积转换为公顷保留两位小数。表达式设计与实现// 1. 定义变量避免重复计算和访问字段提高可读性和性能 var projName $feature.PROJ_NAME; var status $feature.STATUS; var budget $feature.BUDGET; var startDate $feature.START_DATE; var area $feature.AREA; // 2. 处理状态显示使用Decode函数进行映射并添加Emoji注意标注表达式本身支持Unicode Emoji var statusDisplay Decode( Upper(status), // 将状态转为大写避免大小写不一致问题 “PLANNED”, “计划中”, “INPROGRESS”, “进行中”, “COMPLETED”, “已完成”, “ONHOLD”, “⚪️已暂停”, “❓状态未知” // 默认值处理异常数据 ); // 3. 判断是否高预算项目 var budgetFlag IIf(budget 1000, “ ”, “”); // 如果预算1000则budgetFlag为“ ”否则为空字符串 // 4. 格式化日期和面积 var dateDisplay “”; var areaDisplay “”; if (!IsEmpty(startDate)) { // 检查日期是否为空 dateDisplay “ | 始于” Text(startDate, “YYYY-MM-DD”); } if (!IsEmpty(area) area 0) { // 检查面积是否为空且大于0 var areaInHectares Round(area / 10000, 2); // 转换为公顷四舍五入保留两位小数 areaDisplay “ | 占地” Text(areaInHectares, “#.##”) “公顷”; } // 5. 拼接最终标注字符串 var finalLabel projName budgetFlag “\n” statusDisplay dateDisplay areaDisplay; // 6. 返回结果 return finalLabel;表达式解析与技巧变量定义开头将字段值赋给局部变量是很好的习惯。它让后面的代码更简洁不用每次都写$feature.XXX更重要的是在某些复杂的表达式或循环中这能轻微提升性能因为字段值只需查找一次。状态映射使用Decode函数将英文状态码映射为带Emoji的中文清晰直观。用Upper()函数包裹status确保了比较时不区分大小写增强了代码的健壮性。条件标志budgetFlag使用了IIf函数简洁地生成了一个条件文本。这里用空字符串作为false_value实现了“有则加无则不加”的效果。空值处理在格式化日期和面积前都用IsEmpty()和附加条件进行了检查。这是至关重要的错误预防措施。如果不检查当遇到null值或非法值如面积为0或负数时Text函数可能返回null或错误导致整个标注不显示。单位换算与格式化面积除以10000得到公顷并用Round函数控制精度最后用Text函数格式化数字避免显示过多小数位。字符串拼接与换行使用进行拼接“\n”表示换行使得标注信息分层显示更加清晰。最终一个预算1200万元、正在进行中、占地50000平方米的项目标注可能显示为阳光新城项目 进行中 | 始于2024-03-01 | 占地5.00公顷6. 进阶技巧与性能考量当你掌握了基础开始编写更复杂的表达式时就需要关注一些进阶技巧和性能问题。6.1 使用When与IIf处理多级分类对于更复杂的分类逻辑When函数是主力。例如根据人口密度和GDP对城市进行综合分级var density $feature.POPULATION / $feature.AREA; // 人/平方公里 var gdp $feature.GDP; // 亿元 return When( density 1000 gdp 500, “一线城市”, density 500 gdp 200, “二线城市”, density 200 gdp 50, “三线城市”, “其他城市” );注意条件的顺序和逻辑的严谨性确保每个要素都能被正确归类且没有重叠或遗漏。6.2 避免在表达式内进行复杂循环与递归Arcade表达式是为每个要素独立执行的。虽然它支持for循环但如果你对一个拥有成千上万要素的图层在标注表达式中使用循环遍历另一个大型图层性能会急剧下降甚至导致客户端无响应。黄金法则尽量让表达式的逻辑只依赖于当前要素$feature的属性或一些全局常量。如果需要进行跨要素的复杂空间分析或统计例如计算每个要素周围10公里内同类要素的数量这通常不是标注表达式的任务而应该考虑使用“连接”工具预先计算好一个统计字段或者使用Arcade的FeatureSet函数时格外小心并设置空间过滤。6.3 调试与错误排查实战记录即使再小心错误也难免。Arcade表达式构建器通常会有基本的错误提示如语法错误。但逻辑错误需要自己排查。使用Console函数输出日志在ArcGIS Pro的表达式构建器中你可以使用Console(信息)函数。执行后信息会输出到Pro的“Python”窗格旁边的“Arcade”窗格中。这对于查看中间变量值非常有用。var testValue $feature.AREA; Console(“面积原始值” testValue); testValue testValue / 10000; Console(“转换为公顷后” testValue); return Text(testValue, “#.##”);简化与分段测试如果表达式很长很复杂先注释掉一部分确保核心部分能运行。然后逐步取消注释添加功能定位出错的位置。关注数据类型很多错误源于类型不匹配。确保你比较的是同类型数据如数字和数字比文本和文本比。使用TypeOf()函数可以查看变量的类型。Console(“STATUS字段类型是” TypeOf($feature.STATUS));处理空值和除零错误这是最常见的运行时错误。在任何数学运算特别是除法和函数调用前养成检查空值和有效性的习惯。var ratio 0; if (!IsEmpty($feature.A) !IsEmpty($feature.B) $feature.B ! 0) { ratio $feature.A / $feature.B; } return ratio;7. 常见问题速查与避坑指南根据我和社区里很多同行的经验下面这些问题是新手甚至有时是老手最容易遇到的问题现象可能原因解决方案标注什么都不显示或者弹出窗口内容为空。1. 表达式存在语法错误根本未执行。2. 表达式返回了null或undefined值。3. 字段名拼写错误或大小写不对。1. 检查表达式构建器是否有红色错误提示。2. 检查表达式所有分支是否都有返回值。使用IIf或When时确保有默认值。3. 对可能为空的字段用IsEmpty()判断并提供备用值。4. 从字段列表双击插入字段名避免手动输入。标注显示为 “NaN”, “Infinity” 或 “0”。进行了非法的数学运算如除以零、对非数字进行算术运算。在除法运算前判断分母是否为0。确保参与运算的变量是数字类型可用Number()函数强制转换。逻辑判断 (, ) 报错“操作数必须可转换为标量逻辑值”。Decode或When函数没有返回预期的默认值。1. 条件值case与字段值不完全匹配如大小写、空格。2. 在When中前面的条件意外包含了后面的情况。1. 使用Upper()或Lower()统一大小写使用Trim()去除空格后再比较。2. 检查When函数的条件顺序和逻辑确保它们是互斥且全面的。日期显示为奇怪的数字如 “44930”。直接输出了日期字段没有用Text()函数格式化。使用Text($feature.DATE_FIELD, “YYYY-MM-DD”)等格式进行格式化。性能极慢地图操作卡顿。表达式过于复杂可能包含了对大型FeatureSet的未过滤查询或循环。优化表达式逻辑避免在每要素计算中进行全表扫描。考虑将复杂计算提前到字段计算器或属性规则中完成标注表达式仅做简单引用。最后再分享一个我个人的深刻体会写好Arcade表达式的关键不在于记住所有函数而在于清晰的逻辑思维和对数据的深刻理解。在动手写代码之前先用自然语言把你想实现的条件和输出描述清楚。比如“如果项目状态是进行中且预算超千万就用红色粗体显示名称并加感叹号否则正常显示”。把这个描述翻译成When、IIf和字符串拼接就水到渠成了。多利用表达式构建器的预览功能边写边看小步快跑是最高效的学习方式。