本章围绕“地址地理编码”:把用户输入的地址转换为经纬度,在地图上显示位置,并把确认后的坐标保存回应用数据。学习时重点区分两条路径:浏览器页面中的 Geocoded Address 页面项由用户确认地址;Autonomous Database 上的工作流、执行链或自动化可在后台执行服务器端地理编码。

建议按“准备 GeoJSON → 配置 Geocoded Address → 用户确认 → 保存坐标 → 改善页面交互 → 后台批量地理编码”的顺序阅读。所有截图均引用本地捕获路径,代码片段保留官方标识符和语法,便于复制到 APEX Builder、SQL Workshop 或页面级 JavaScript 中验证。

15 地址地理编码#

地址地理编码会识别一个地址对应的地理坐标。获得经度和纬度后,应用就可以把地址显示在地图上、计算它与另一个地址的距离,或者判断它是否位于某个兴趣点的指定半径内。

APEX 的 Geocoded Address 页面项允许你在浏览器页面中执行地理编码。用户输入地址并确认匹配结果后,页面项会得到包含坐标的地址数据。如果应用运行在 Autonomous Database 上,还可以通过专用页面进程或工作流活动执行服务器端地理编码。

学习验证

  • 前提:准备一个可实验的 APEX 应用,最好有员工、地址、经纬度或地图区域示例数据。
  • APEX 区域:Page Designer、Geocoded Address 页面项、Map 区域、Shared Components。
  • 操作:先识别应用需要用户交互式确认,还是后台批量处理,再选择浏览器端页面项或服务器端地理编码。
  • 检查点:能说明地址文本、GeoJSON、经度、纬度和地图标记之间的关系。
  • 预期结果:知道本章每个小节是在解决“如何获得坐标、如何确认、如何保存、如何展示、如何复用”的哪一部分。

15.1 使用用户确认进行地理编码#

如果希望用户在保存前确认地址匹配结果,可以使用 Geocoded Address 页面项。它在页面上显示为地图,并与一个或多个地址文本页面项配合使用。用户修改地址并提交页面时,APEX 会先拦截提交,让用户从可能的匹配结果中确认正确地址;所选结果在内部包含经纬度。

Geocoded Address 页面项使用 Oracle 托管的服务自动匹配地址和坐标。官方说明中特别指出,应用使用 Oracle eLocation 服务不需要额外配置,也不产生额外费用。

  • 理解目标:把地址录入和坐标保存拆成两个动作,用户先确认匹配,再由页面保存结果。
  • 定位对象:Form 区域、地址文本项、Geocoded Address 页面项、隐藏经纬度项、动态操作。
  • 操作路径:先让页面项读到已有坐标,再修改地址触发匹配,最后保存确认后的坐标。
  • 验证结果:用户能看到候选地址并确认;保存后地图标记移动到新位置。

15.1.1 从坐标生成 GeoJSON#

Geocoded Address 页面项使用 GeoJSON,这是 Web 地图信息的标准格式。如果坐标存放在 SDO_GEOMETRY 列,或以 GeoJSON 存放在 VARCHAR2CLOB 列中,页面项可以直接读取并写回坐标。有些表会把 LONGITUDELATITUDE 分开存成两个数值列,这时需要在查询中生成一个 GeoJSON 点。

技术示例:以下 SQL 保留源示例中的对象名和列名,便于在 SQL Workshop 或 Form 区域查询中验证。

create or replace view emp_and_address as
select empno, e.ename, a.address, a.latitude, a.longitude
from emp e
join emp_addresses a using (empno);

USING (EMPNO) 可以在两个表的连接列同名时替代 ON e.EMPNO = a.EMPNO,避免重复写列名。GeoJSON 点的核心结构如下,注意坐标数组顺序是经度在前、纬度在后。

{"type":"Point","coordinates":[longitude, latitude]}
select EMPNO,
       ENAME,
       ADDRESS,
       LATITUDE,
       LONGITUDE,
       case
         when longitude is not null and latitude is not null then
           json_object(
             'type' value 'Point',
             'coordinates' value json_array(longitude, latitude))
       end as geo_json_point
from EMP_AND_ADDRESS

把这个 SQL Expression 加入 Form 区域的数据源查询后,区域中会出现 P60_GEO_JSON_POINT 页面项,可将它的类型设置为 Geocoded Address

sdo_util.to_geojson(make_point(longitude, latitude)) as geo_json_point

注意:应使用 JSON_OBJECTJSON_ARRAY 构造 GeoJSON。它们会把小数坐标格式化为 JSON 兼容的十进制值,不受最终用户浏览器语言影响。不要简单拼接字符串,因为意大利语等区域设置会把小数写成 33,12345,导致 JSON 坐标格式错误。若必须拼接字符串,需要用 TO_CHAR() 并显式指定 'NLS_NUMERIC_CHARACTERS=.,'

学习验证

  • 前提:EMPEMP_ADDRESSES 或等价示例表中有地址、经度和纬度。
  • APEX 区域:Form 区域的数据源 SQL Query 与页面项类型。
  • 操作:把表单数据源从表或视图切换为 SQL Query,加入 GEO_JSON_POINT 表达式。
  • 检查点:经纬度非空时返回 GeoJSON 点;任一坐标为空时返回 null
  • 预期结果:P60_GEO_JSON_POINT 能作为 Geocoded Address 页面项读取已有位置。

15.1.2 避免 SQL Expression 更新错误#

如果 Form 区域中包含类似 GEO_JSON_POINT 的 SQL Expression 列,需要在属性编辑器中把该列的 Query Only 打开。否则用户提交表单时,APEX 会尝试更新这个计算列,可能触发 ORA-01733: virtual column not allowed here

学习验证

  • 前提:Form 区域数据源中包含计算出来的 GEO_JSON_POINT
  • APEX 区域:Page Designer 的页面项属性编辑器。
  • 操作:选择 P60_GEO_JSON_POINT 或对应计算列页面项,将 Query Only 设为开启。
  • 检查点:该页面项只参与查询和显示,不作为 DML 更新目标。
  • 预期结果:提交页面时不会尝试更新计算列,也不会出现虚拟列更新错误。

15.1.3 识别地址文本字段#

需要把 Geocoded Address 页面项关联到一个或多个显示地址文本的页面项。如果使用非结构化地址,一个附加的 Address Item 就足够,例如 P60_ADDRESS,用户可以输入类似 3130 Pacific Ave, San Francisco, CA 的地址。

也可以使用多个页面项分别处理结构化地址的街道、城市、州、省、邮编和国家。Trigger Geocoding 默认是 Automatic,表示页面提交前会先触发地理编码并要求用户确认修改后的地址。Oracle 托管的定位服务需要国家上下文;你可以选择静态国家值,也可以配置 Country 页面项让最终用户决定。

图 15-1 配置 Geocoded Address 页面项

学习验证

  • 前提:页面中已有地址文本项或结构化地址项。
  • APEX 区域:Geocoded Address 页面项的 Address Source、Country 和 Trigger Geocoding 属性。
  • 操作:P60_ADDRESS 或结构化地址项关联到 Geocoded Address 页面项,并设置国家来源。
  • 检查点:修改地址后,提交会先显示可确认的匹配结果。
  • 预期结果:页面项能把用户输入的地址文本转换成待确认的地理编码候选。

15.1.4 交互式确认地址#

本节展示用户录入或编辑地理编码地址时的完整体验。假设员工 KING 最近搬家,用户需要修改她的地址。页面可以把基于 EMP_WITH_ADDRESS 视图的 Cards 区域和显示所有员工地址的 Map 区域放在一起;地图工具提示可使用下面的 HTML Expression 显示员工名和已有地址。

<strong>&ENAME.</strong>{if ADDRESS/}<br>&ADDRESS.{endif/}
图 15-2 在地图提示中查看 KING 的现有地址

接着创建一个 Full Card 动作打开模态抽屉页面,把当前员工的 EMPNO 传给主键页面项 P60_EMPNO。点击 KING 的卡片后,模态抽屉对话框打开,P60_ADDRESS 显示非结构化地址,P60_GEO_JSON_POINT 使用 SQL 表达式根据该行的 LONGITUDELATITUDE 显示已有坐标。

图 15-3 页面加载时 Geocoded Address 显示已有地址

用户输入新地址并点击 Apply Changes 保存。因为 P60_GEO_JSON_POINTTrigger Geocoding 设置为 Automatic,APEX 会先拦截页面提交并验证修改后的地址。匹配地址会显示在模态 Geocoding Results 对话框中。

图 15-4 用户确认正确地址

可以通过创建名为 APEX.ITEM.GEOCODE.DIALOG_TITLE 的 Shared Component Text Message,并启用 Used in JavaScript,来自定义 Geocoding Results 对话框标题。用户确认地址后,再次点击 Apply Changes 即可保存变更,地图会显示新的地址位置。

图 15-5 Geocoded Address 地图更新为新地址

学习验证

  • 前提:有员工卡片、地图区域、员工地址编辑模态页面和坐标列。
  • APEX 区域:Cards 动作、Map 区域、Page 60 Form、Geocoded Address 页面项。
  • 操作:点击员工卡片打开编辑抽屉,修改地址,确认候选结果,再保存。
  • 检查点:第一次保存触发确认;确认后第二次保存写入地址和坐标。
  • 预期结果:返回调用页后,地图上对应员工的位置更新。

15.1.5 提取坐标用于存储#

如果需要把地理编码地址中的经度和纬度保存到单独页面项,应在 Result Selection [Geocoded Address] 事件上定义动态操作。一个 Execute JavaScript Code 动作即可完成赋值。

$s('P60_LONGITUDE', this.data.longitude);
apex.items.P60_LATITUDE.value = this.data.latitude;

第一行使用快捷语法 $s('item', value) 设置 P60_LONGITUDE。第二行使用显式页面项 API 设置 P60_LATITUDE,写起来稍长,但在 Code Editor 中输入点号时可以获得补全提示。使用显式语法时,最后要记得访问页面项名后的 .value

图 15-6 从 Geocoded Address 提取经度和纬度

Result Selection 事件动作中的 this.data 对象还包含其他属性,可通过 this.data.propName 引用。

{
  "sequence": 0,
  "latitude": 37.77112,
  "longitude": -122.45304,
  "matchCode": 1,
  "matchVector": "??010101010??004?",
  "matchVectorScore": 100,
  "houseNumber": "2045",
  "street": "Oak St",
  "settlement": "San Francisco",
  "municipality": "SAN FRANCISCO",
  "region": "CA",
  "country": "US",
  "language": "ENG",
  "postalCode": "94117",
  "side": "R",
  "percent": 0.24,
  "edgeId": 120887751
}

如果 P60_GEO_JSON_POINT 直接基于 SDO_GEOMETRY 列、或包含 GeoJSON 的 VARCHAR2/CLOB 列,则该列可以直接更新(Query Only = OFF),坐标保存会自动发生,不需要额外提取经纬度,也不需要用 SQL Expression 创建点 GeoJSON。

学习验证

  • 前提:页面中有 P60_LONGITUDEP60_LATITUDE 隐藏项或等价存储项。
  • APEX 区域:Dynamic Actions、Geocoded Address 的 Result Selection 事件。
  • 操作:在 Result Selection 事件中设置经纬度页面项。
  • 检查点:选择地理编码结果后,隐藏项立即得到对应坐标值。
  • 预期结果:表单 DML 可把坐标保存到单独列。

15.1.6 取消客户端设置隐藏项的值保护#

当隐藏页面项的值由客户端逻辑设置时,需要关闭 Value Protected。例如 P60_LONGITUDEP60_LATITUDE 是 Hidden 页面项,值由页面动态操作设置。如果忘记关闭该开关,最终用户提交页面时会看到类似下面的错误:Session state protection violation: This may be caused by manual alteration of protected page item P60_LATITUDE.

图 15-7 对动态操作设置的隐藏项关闭 Value Protected

学习验证

  • 前提:隐藏项值由浏览器端动态操作写入。
  • APEX 区域:隐藏页面项的 Security 属性。
  • 操作:选择 P60_LONGITUDEP60_LATITUDE,关闭 Value Protected
  • 检查点:关闭的是客户端需要写入的隐藏项,不是放松整个页面安全边界。
  • 预期结果:提交页面不会因隐藏项值由客户端改变而触发会话状态保护违规。

15.1.7 探索其他页面特性#

Geocoded Address 示例页面还使用了几项值得单独学习的页面技术:点击地图上的员工地址图钉打开编辑对话框;编辑对话框关闭后刷新调用页的地图;两处地图都纵向拉伸以使用可用空间;对话框阻止 Enter 键直接提交页面,从而让它触发地理编码;应用级 JavaScript 帮助函数设置对话框标题;标题文本使用带占位符的可翻译 Text Message。

  • 理解目标:把“地图编辑地址”的页面体验从能用推进到顺手、可维护、可翻译。
  • 定位对象:Map Layer Link、Dialog Closed 动态操作、页面级 CSS、键盘事件、Static Application Files、Text Messages。
  • 操作路径:逐项配置并验证每个页面交互点。
  • 验证结果:用户能从地图点进入编辑、返回后看到刷新、按 Enter 得到预期行为,对话框标题能动态显示上下文。

15.1.7.1 点击地图点打开编辑对话框#

可以通过设置地图图层项的 Link > Target 来配置 drill down。示例页面的地图区域在调用页上显示员工地址。用户查看地图点时,很自然会点击某个点来编辑该员工地址;与其要求他们必须点击卡片,不如让地图点也能打开同一个编辑入口。

图 15-8 点击地图点后返回并显示 KING 更新后的地址

选择 Addresses 地图图层后,可以像配置按钮或普通链接一样,用 Link Builder 配置 Link > Target。示例选择 Page 60,并把 P60_EMPNO 的值设置为当前点的 EMPNO 列值。这样用户无论点击卡片还是地图点,都能用符合直觉的方式打开员工地址编辑模态页。

图 15-9 从地图点链接到另一个页面

15.1.7.2 在对话框关闭时刷新区域#

用户点击卡片或地图点后,Page 60 以模态抽屉打开,用来编辑员工地址。如果用户点击关闭图标或按 Esc 取消,对地址没有影响;但如果点击 Apply Changes 关闭,地址可能已经更新。调用页上的地图应立即反映最新位置,所以应在 Dialog Closed 事件处理器中对地图区域执行 Refresh 动作。

图 15-10 对话框返回后地图刷新并显示 KING 的新地址

要定义这个动态操作,在 Page Designer 中选择 Dynamic Actions 标签,右键 Dialog Closed 事件并选择 Create Dynamic Action。在属性编辑器的 When 区域中,通过 Selection Type 指定哪个对话框关闭时触发。可以按按钮、区域组件或 jQuery Selector 定位。如果希望任何元素打开的对话框关闭时都刷新地图,可以选择 jQuery Selector,并把 Selector 设置为 body

图 15-11 配置 Dialog Closed 动态操作事件处理器

15.1.7.3 拉伸以填满垂直空间#

APEX 的 12 栏网格系统很适合填充水平空间,但有些组件还需要按可用高度纵向拉伸,例如地图区域。常见做法是先理解视口高度,再用浏览器开发者工具找出需要设置高度的容器元素,最后在页面级或对话框级 CSS 中用 calc() 计算剩余高度。

15.1.7.3.1 使用 CSS 纵向拉伸#

在 CSS 中,viewport 表示浏览器可见区域。一种纵向拉伸策略是:先计算扣除固定页面元素后的剩余高度,再把组件高度设置为这个值。表达式 100vh 表示视口高度的 100%。rem 是相对单位,等于当前页面根字体的字符高度,因此用户放大或缩小字体时仍能正确适配。

100vh - 5rem
图 15-12 从视口高度中减去固定页面元素高度
15.1.7.3.2 检查潜在拉伸目标#

纵向拉伸前,需要检查页面 DOM,确定 CSS 规则应该作用到哪个容器。右键页面并选择 Inspect 打开浏览器 Developer Tools,在 Elements 标签中寻找真正设置组件高度的元素。示例中,地图区域的 <div> 元素带有 a-MapRegion-container 类,并显式设置了 400px 高度,因此它就是合适的 CSS 目标。

图 15-13 确定 CSS 规则要定位的元素或类
15.1.7.3.3 在 Inline CSS 中计算高度#

CSS 规则可以使用计算表达式。下面的规则定位 a-MapRegion-container 类,并用 calc() 把元素高度设置为 100vh - 5rem。类名前要写点号;!important 让该规则优先于页面默认高度。浏览器尺寸变化时,这个高度会像电子表格公式一样即时重新计算。

.a-MapRegion-container {
  height: calc(100vh - 5rem) !important;
}

选择页面节点后,可把规则放入页面级 CSS > Inline

图 15-14 给页面加入 Inline CSS 规则

运行后,地图会使用剩余垂直高度,让用户获得更大的查看区域。

图 15-15 普通页面中地图区域纵向拉伸的效果

由于使用相对单位 rem,当用户把页面缩放到 67% 或 175% 时,表达式仍能正确调整地图高度。

图 15-16 相对 rem 单位在任意缩放比例下都能正确工作
15.1.7.3.4 在对话框中应用垂直拉伸#

模态对话框中也可以用 CSS 拉伸组件。示例要拉伸模态抽屉页中的 Geocoded Address 地图,需要扣除顶部标题栏、Address 字段,以及底部包含按钮的对话框 footer。经过试验后,100vh - 14rem 是比较合适的近似表达式;实际要扣除多少,取决于你自己的对话框布局。

图 15-17 垂直拉伸可能需要扣除上下固定内容

使用浏览器 Developer Tools 确认目标类是 a-GeoCoder-map 后,在模态抽屉页面的 CSS > Inline 中加入下面的规则。

.a-GeoCoder-map {
  height: calc(100vh - 14rem) !important;
}
图 15-18 模态抽屉中 Geocoded Address 地图的垂直拉伸效果

15.1.7.4 阻止 Enter 键提交页面#

可以通过加入一个视觉上隐藏的文本字段,阻止单字段对话框在按 Enter 时直接提交页面。历史原因导致浏览器在页面只有一个输入字段时,按 Enter 会提交页面;如果有两个或更多输入字段,则不会这样做。Employee Address 模态抽屉技术上只有 P60_ADDRESS 一个输入字段,因为 P60_GEO_JSON_POINT 渲染为地图。

最简单的解决方法是增加第二个输入字段,并把它隐藏。创建这个字段时,给它一个说明用途的名称;页面项类型仍保留为 Text Field,不要改成 Hidden。然后在 Advanced > CSS Classes 中加入 Universal Theme 的 u-hidden 类,标签留空,并把 Appearance > Template 设置为 Hidden。虽然用户看不到它,浏览器仍会把它算作第二个输入字段,从而避免 Enter 直接提交页面。

图 15-19 添加 CSS 隐藏字段以阻止 Enter 键提交页面

15.1.7.5 使用 Enter 键触发地理编码#

可以使用原生 Trigger Geocoding 动态操作步骤,按需执行地址确认。例如在 P60_ADDRESS 页面项的 Key Press 事件上创建动态操作,并在事件处理器的 Client-side Condition 中用下面的 JavaScript 表达式判断用户是否按下 Enter

this.browserEvent.key === "Enter"

Trigger Geocoding 动作步骤的 Client-side Condition 中,再用下面的表达式确保只有地址发生变化时才触发地理编码。

apex.items.P60_ADDRESS.isChanged()

配置完成后,用户按 Enter 即可立即获得地理编码反馈。稍后点击 Apply Changes 时,Automatic 地理编码触发器会识别已经按需执行过,不会重复地理编码。

图 15-20 使用 Enter 键触发地址地理编码

15.1.7.6 在对话框标题中包含上下文信息#

打开模态对话框时,在标题中显示上下文信息通常很有帮助,例如正在编辑哪个员工的地址。

图 15-21 在模态对话框中显示 ENAME 作为有用上下文

Page 60 模态抽屉中的 Form 区域包含 P60_ENAME 页面项,Form Initialization 页面进程会根据传入的主键 P60_EMPNO 自动取回 EMP_AND_ADDRESS 视图行。因此页面已经能得到 ENAME。直觉上可能会先尝试在页面 Title 中写 &P60_ENAME.

图 15-22 第一次尝试在对话框标题中包含上下文信息

但运行页面时,标题里的 &P60_ENAME. 会是空的。这是因为运行时确定对话框标题的时机早于页面项值可用于替换的时机。

图 15-23 对话框标题中的 P60_ENAME 为空

解决方法是在 Page Load 事件中用 JavaScript 动态设置对话框标题。

// JavaScript in Page Load event action to set dialog title
apex.util.getTopApex()
  .jQuery(".ui-dialog-content")
  .dialog("option", "title", "Address for Employee " + $v('P60_ENAME'));

如果不希望静态标题在 Page Load 动态设置前短暂显示,可以在 Page Designer 的页面 Title 中引用一个不存在的替换字符串,例如 &INITIALLY_BLANK.

15.1.7.7 提供应用级帮助函数#

多页面都会使用的 JavaScript 帮助函数应放入应用级库。如果你预期许多页面都要设置动态对话框标题,那么把这段逻辑放入应用级帮助函数库会更易维护。每个使用位置只需要传入不同的标题文本,避免复制粘贴同一段代码。

创建应用级 JavaScript 函数库通常有两步:先新增一个 Static Application File 存放帮助代码,再在应用的 User Interface JavaScript File URLs 列表中引用该文件。

15.1.7.7.1 添加 Static Application JavaScript 文件#

可以通过 Shared Components > Static Application Files 为应用加入任意类型的文件。示例建议新增一个名为 app.js 的文件,用来保存应用级 JavaScript 帮助函数。文件名可以按需调整,但应保留 .js 扩展名。

15.1.7.7.1.1 使用 JavaScript 命名空间模板#

可以在 app.js 中定义单一全局命名空间,集中放置可复用 JavaScript 帮助函数。下面的模板定义了名为 app 的帮助函数命名空间,并公开两个函数:setDialogTitle()areDifferent()。它类似 PL/SQL package:返回对象中的函数是 public,闭包内部但未返回的函数是 private。

// Export single global symbol "app" to keep code clean
const app = (function($) {

  // --- Private Functions ---
  // function myPrivateFunction(itemName) {
  //   ...
  // }

  // --- Public Functions (returned below!) ---

  //----------------------------------------------------
  // Set the title of the current dialog window
  //----------------------------------------------------
  function setDialogTitle(title) {
    apex.util.getTopApex()
      .jQuery(".ui-dialog-content")
      .dialog("option", "title", title);
  }

  //----------------------------------------------------
  // Return true if x and y are different, treating null
  // and empty string as equivalent in the comparison
  //----------------------------------------------------
  function areDifferent(x, y) {
    return (x || '') !== (y || '');
  }

  // Only these functions are exposed to JavaScript code
  // in APEX pages; everything else stays private.
  return {
    setDialogTitle,
    areDifferent
  };

// Ensure $ in app namespace resolves to correct jQuery
})(apex.jQuery);

这种方式只创建一个全局符号 app,避免向全局作用域加入许多独立函数名,从而降低命名冲突风险。

15.1.7.7.1.2 将命名空间与 PL/SQL Package 对比#

JavaScript 没有 PL/SQL 那样的 package,因此开发者常用 IIFE(Immediately Invoked Function Expression,立即调用函数表达式)在 app.js 中模拟模块结构。高层模式如下。

// Define an "app" namespace with exported functions
const app = (function($) {
  // --- Private Functions ---
  function myPrivateFunction(itemName) {...}

  // --- Public Functions (returned below!) ---
  function setDialogTitle(title) {...}
  function areDifferent(x, y) {...}

  return {
    setDialogTitle,
    areDifferent
  };
})(apex.jQuery);

(function($) {...})(apex.jQuery); 会先定义函数,再立即调用它。单个参数 $ 会接收 APEX 使用的正确 jQuery 版本。函数返回一个对象,其中包含要公开的函数。返回语句中的 {setDialogTitle, areDifferent} 是对象属性简写,等价于:

{
  setDialogTitle: setDialogTitle,
  areDifferent: areDifferent
}

该对象成为 app 变量的值。以后在 APEX 页面中写下面这行代码,就会调用 app 函数库里的 setDialogTitle

app.setDialogTitle("Hello");

通过把帮助函数挂到单一命名空间对象 app 下,可以减少全局顶层名称、把相关逻辑放在一起、让代码更容易组织维护、定义公开函数规格,并保留修改命名空间内部 private 函数的自由。

注意:与 PL/SQL 不同,JavaScript 不支持使用不同参数列表定义多个同名函数。如果不小心这样做,文件中最后定义的函数会覆盖前面的函数。若要达到类似效果,应定义包含所有可能参数的函数,再判断某个参数是否未传入。

if (typeof yourParam === "undefined") {
  // yourParam was not passed
}
15.1.7.7.2 在所有页面中包含帮助函数#

把 JavaScript 文件加入 Static Application Files 后,需要在应用设置中引用它的 Reference URL,才能让所有页面加载该文件。在 Shared Components > Static Application Files 列表页中,可以找到任何文件的 Reference URL。因为该列表是 Interactive Report,可用过滤条件只显示 JavaScript 文件。JavaScript 文件的 Reference 列通常包含 #APP_FILES##MIN# 替换字符串。

图 15-24 Static Application Files 列表显示文件引用路径

APEX 在保存 app.js 这样的 JavaScript 文件时,会自动创建压缩版本 app.min.js。压缩会去掉空白并缩短名称,但不改变代码逻辑,以减少运行时加载体积。启用 Debug tracing 时,APEX 会使用原始 JavaScript 源文件,便于调试。

要让应用每个页面都加载该文件,先复制 Static Application Files 中的 Reference path,然后粘贴到 Shared Component > User Interface Attributes 页面的 JavaScript 标签下的 File URLs 列表字段中。如果有多个文件,每行写一个。

图 15-25 在应用 JavaScript File URLs 中包含静态应用文件引用

15.1.7.8 在 JavaScript 中使用可翻译文本#

从 JavaScript 设置动态对话框标题时,如果应用将来可能需要翻译,应使用可翻译的 Text Message,不要硬编码英文标题。应用级 app 命名空间可用后,Page Load 动态操作可以先用下面的方式设置标题。

app.setDialogTitle('Address for Employee ' + $v('P60_ENAME'));

更好的做法是在 Shared Components > Text Messages 中创建名为 ADDRESS_FOR_EMPLOYEE 的文本消息。英文 Text 可以写为 Address for Employee %0,其中 %0 是第一个参数占位符。因为调用发生在 JavaScript 中,必须启用 Used in JavaScript

图 15-26 配置可在 JavaScript 中使用的可翻译 Text Message

定义好 Text Message 后,可在 Page Load 动态操作中这样引用它。

app.setDialogTitle(
  apex.lang.formatMessage(
    'ADDRESS_FOR_EMPLOYEE',
    apex.items.P60_ENAME.value));

15.2 后台地理编码#

如果应用运行在 Autonomous Database 上,可以在后台对地址执行地理编码。可用位置包括:Workflow 中的原生活动、Execution Chain 中的原生页面进程,以及 Automation 中的原生 Server-side Geocoding 动作类型。

配置时,地址既可以是单个非结构化地址字段,也可以是一组结构化地址字段,每个字段提供地址的一部分。设置一个或多个地址项后,可以指定 Coordinates Item 接收最佳匹配的 GeoJSON 点,也可以指定 Collection Name 处理可能存在的多个匹配地址。使用非结构化地址时,记得在地址值末尾包含 ISO 两位国家缩写,帮助服务定位地址。

学习验证

  • 前提:应用运行在 Autonomous Database,并有可后台处理的地址来源。
  • APEX 区域:Workflow、Execution Chain、Automation、Server-side Geocoding。
  • 操作:选择地址输入来源,配置 Coordinates Item 或 Collection Name。
  • 检查点:知道结果是只取最佳匹配,还是保留多个候选供后续逻辑处理。
  • 预期结果:后台过程能够生成 GeoJSON 坐标或匹配结果集合。

15.2.1 为服务器端地理编码选择项目#

执行服务器端地理编码时,应根据上下文选择合适的项目类型。原生 Workflow Activity 可以使用 Additional Data 列、参数或工作流变量;原生页面进程可以使用页面项或应用项;原生 Automation 动作类型可以使用查询列或应用项。

Automation 动作使用绑定变量引用当前查询行的列值,也可以给这些绑定变量赋值。因此,列项不仅能提供待地理编码的地址值,也可以作为接收结果的 Coordinates Item。后续动作能看到更新后的行值,但这些值不会自动永久保存到任何地方。

学习验证

  • 前提:明确后台地理编码运行在工作流、页面进程还是自动化中。
  • APEX 区域:Workflow Activity 参数、Page Process 页面项、Automation 查询列。
  • 操作:为当前上下文选择能被该后台动作读取和写入的项目。
  • 检查点:后续步骤能引用坐标结果;需要永久保存时另行执行 DML。
  • 预期结果:地址输入和坐标输出在后台流程中都能被正确访问。

15.2.2 引用 Coordinates Item 的值#

如果 Coordinates Item 命名为 COORDINATES,可以按数据类型用多种方式读取经度和纬度。使用 SDO_GEOMETRY 时,可在 PL/SQL 中先把 GeoJSON 转换为几何对象。

declare
  l_coords    sdo_geometry := sdo_util.from_geojson(:COORDINATES);
  l_longitude number       := l_coords.sdo_point.x;
  l_latitude  number       := l_coords.sdo_point.y;
begin
  -- Do something with l_longitude & l_latitude here
end;

也可以在 SQL 中读取 SDO_GEOMETRY 点坐标。

for j in (
  select coords.sdo_point.x as longitude,
         coords.sdo_point.y as latitude
  from (
    select sdo_util.from_geojson(:COORDINATES) as coords
    from dual))
loop
  -- Do something with j.longitude & j.latitude here
end loop;

如果更愿意直接处理 GeoJSON 文档,可在 PL/SQL 中使用 json_value

declare
  l_longitude number;
  l_latitude  number;
begin
  l_longitude := json_value(:COORDINATES,'$.coordinates[0]');
  l_latitude  := json_value(:COORDINATES,'$.coordinates[1]');
  -- Do something with l_longitude & l_latitude here
end;

也可以在 SQL 中通过 json_table 读取 GeoJSON 坐标数组。

for j in (
  select longitude, latitude
  from json_table(:COORDINATES, '$.coordinates'
    columns (
      longitude number path '$[0]',
      latitude  number path '$[1]')))
loop
  -- Do something with j.longitude & j.latitude here
end loop;

学习验证

  • 前提:后台地理编码动作已把 GeoJSON 点写入 COORDINATES
  • APEX 区域:后续 PL/SQL 进程、Workflow 活动代码或 Automation 动作。
  • 操作:根据后续逻辑选择 SDO_GEOMETRY 或 JSON 路径读取方式。
  • 检查点:确认 coordinates[0] 是经度,coordinates[1] 是纬度。
  • 预期结果:后续逻辑能稳定取得数值经纬度。

15.2.3 处理匹配结果集合#

如果 Collection Name 设置为 GEOCODE_RESULTS,可以通过 apex_collections 查询地址匹配结果。

select seq_id,
       c001 as street,
       c002 as house_number,
       c003 as postalcode,
       c004 as municipality,
       c005 as settlement,
       c006 as region,
       c007 as country,
       c011 as matchvector,
       n001 as longitude,
       n002 as latitude,
       d001 as geocoding_timestamp
from apex_collections
where collection_name = 'GEOCODE_RESULTS'
order by seq_id

结果会按地理编码服务提供的“最佳匹配优先”顺序加入 collection。因此按 SEQ_ID 排序可以保留原始匹配顺序,便于后续处理。

学习验证

  • 前提:服务器端地理编码配置了 Collection Name。
  • APEX 区域:后续 SQL、PL/SQL、Automation 或 Workflow 处理步骤。
  • 操作:apex_collections 读取匹配结果,并按 seq_id 处理。
  • 检查点:能看到地址组成部分、匹配向量、经纬度和地理编码时间戳。
  • 预期结果:可以基于最佳匹配顺序执行接受、人工复核或记录审计。

15.2.4 配置 Match Mode#

使用结构化地址执行服务器端地理编码时,Match Mode 决定匹配的严格程度。该设置位于 Shared Components > Component Settings 页面中的 Server Side Geocoding 组件。对非结构化地址执行地理编码时,这个设置没有影响。

图 15-27 配置服务器端地理编码的 Match Mode

学习验证

  • 前提:服务器端地理编码使用结构化地址字段。
  • APEX 区域:Shared Components > Component Settings > Server Side Geocoding。
  • 操作:选择符合业务容忍度的 Match Mode。
  • 检查点:明确严格匹配、近似匹配和最佳结果对业务数据质量的影响。
  • 预期结果:后台地理编码的接受标准与地址质量要求一致。