地理围栏
地理围栏是地球表面上有名称的边界。设备上报位置,检测规则判断上报位置是否在围栏内。两者都会随时间变化:设备曾在哪里,以及围栏当时是什么形状。因此,准确回答取决于比较两者的正确版本。
已提供:通过控制台(绘制、编辑、删除)或 GraphQL 编写围栏、带孔洞的 POLYGON_2D 边界、检测规则中的球面包含判断,以及每个围栏集合的冻结归档与可浏览历史。计划中:其他几何类型;模式保留了 POLYGON_2_5D 和 VOXEL_3D,但目前写入时都会被拒绝。
绘制围栏
控制台中的围栏位于区域 → 地理围栏(Areas → Geofences)。点击地图放置顶点,点击第一个顶点闭合形状,然后保存。对于现有围栏,你可以:
- 拖动顶点改变位置;
- 按住 Alt 点击顶点将其删除;
- 点击边上的小手柄添加顶点。
围栏携带令牌,即规则引用它的标识符,以及可选名称和描述。令牌创建后固定:编辑时控制台只读显示,API 拒绝更改它的更新。名称随时可以更改,不会破坏引用。
令牌固定,是因为规则在文本中通过令牌引用围栏,平台无法代你重写。更改令牌会让所有 geo.inFence("old-token") 不再指向任何对象。如果需要不同令牌:
- 创建新围栏。
- 修改引用旧围栏的规则。
- 删除旧围栏。
这一顺序让你先处理规则,而不是之后才发现问题。
围栏背后的地图
编辑器使用租户底图。新实例已配置底图,无需设置即可显示瓦片。
租户管理员在**设置 → 地图(Settings → Map)**中统一更改瓦片来源。编辑器中的字段是在其上叠加的个人覆盖值,只保存在浏览器中,便于先试用提供方再为整个租户启用。参见底图,了解层级解析、URL 与署名为何必须一起继承、默认提供方,以及如何保护 API 密钥。
如果任何层级都没有瓦片来源——运维人员将实例默认值设为 {},租户也没有设置——编辑器回退到内置世界地图。它显示编译进控制台的公共领域大陆和国界,街道级缩放没有细节。仍可绘图,两种情况下坐标都精确,因为使用与瓦片地图相同的投影。
边界规则
边界以 GeoJSON 保存,坐标顺序为 [longitude, latitude],环必须显式闭合(最后重复第一个位置)。控制台自动处理;通过 GraphQL 编写的集成直接提供文档。
三个限制分别约束不同成本:
| 限制 | 默认值 | 约束内容 |
|---|---|---|
| 单个围栏的位置数量 | 512 | 编译和保存已编译围栏的成本 |
| 每租户围栏数量 | 100 | 围栏集合变更通知的大小 |
| 整个围栏集合的位置总数 | 51,200 | 围栏在共享检测引擎中占用的几何容量 |
位置数量包含每个环的闭合位置。集合总数按不同形状计数:两个形状完全相同的围栏只消耗一个形状,而不是两个。
这些是默认值,不是平台固定限制。每项都是你的服务方案的一部分,运维人员可以为租户提高或降低。默认值彼此一致:100 个、每个 512 个位置的围栏正好为 51,200,因此从未调整过限制的租户可以同时达到三项上限。
保存围栏时检查限制,因为规则编写的成本检查看不到保存在围栏、而不是规则中的数量。围栏总数不影响单次包含测试耗时:规则引用一个围栏,引擎直接访问该围栏。
已超限时保存
租户已超限时,所有现有围栏都会保留。只有修改使数量比现有数量更大时,保存才被拒绝。
租户可能因为方案调整,或围栏早于限制创建而超限。此时:
- 编辑名称或描述始终可行。
- 删除围栏始终可行。
- 将围栏变得更小几乎总是可行。例外是:总数按不同形状计数,多个相同围栏中的一个被修改后会成为独立形状,即使它更小,也可能提高总数。发生时,拒绝信息会说明。
- 删除会降低保存的总数,因此超限租户删除围栏后无法重建它。要将围栏移到新令牌,先创建新的,再删除旧的。
环必须围出面积
边界必须围出面积。边相交的环,例如蝴蝶结形,没有明确定义的内部,无法准确回答包含问题。因此,保存时会使用与检测引擎编译围栏集合几何时相同的检查,拒绝这些环。
这一点比看起来重要。在编写阶段检查之前,这种围栏可以正常保存,在注册表中看起来健康,直到后来有规则引用它时才失败。
绘图期间,控制台立即提示自相交形状。它的检查是服务器球面检查的平面近似,因此对极大围栏或跨越反经线的围栏,两者可能不一致。这就是控制台只警告、最终由服务器决定的原因。
球面包含判断
包含判断在球面计算,而不是平面地图上,这不是可有可无的精细化。把经纬度当作 x、y,会在真实围栏可能出现的两种位置产生错误结果:
- 跨越反经线。 从东经 179° 到西经 179° 的围栏只有 2° 宽。按平面坐标解释,会变成覆盖大部分世界的 358° 宽带状区域,把大西洋中的设备判为“内部”,却把真正站在围栏中的设备判为“外部”。
- 高纬度。 同纬度两点之间的最短路径向极点弯曲,因此四个顶点画出的“矩形”边界不是等纬度线。对于西经 10°–东经 10°、北纬 80°–81° 的范围,北纬 80.05° 的点在中心处位于真实围栏外部,在边缘处却位于内部。平面计算会把两者都判为内部。
边界如何计入
三个边界情况各有统一规则:
- 边界属于内部。 正好位于围栏边上的位置视为包含。如果直接使用底层几何库,边界会被相邻区域分割,两个共边围栏各自认领其中一部分,其余两者都不认领。显式边上测试避免这一点,地面容差约为 6 毫米。
- 孔洞边界也属于内部,遵循相同规则。严格位于孔洞内部的位置在围栏外;位于孔洞环上的位置在围栏内。两个相邻围栏,或围栏与自身孔洞,不会对共享点给出不同结论。
- 环方向不重要。 同一个环的顺时针或逆时针表示得到相同答案。平台规范化方向而非信任输入,因此通过 API 编写 GeoJSON 的集成无需确保方向正确。唯一无法挽救的情况,是大到包裹地球大部分区域、无法明确“较小区域”的所谓围栏。位置限制与必须围出面积的要求,使其远超真实围栏的范围。
围栏集合版本与历史
围栏集合发生变化——创建围栏、编辑边界、删除围栏——会将整个集合冻结为新版本。每个版本保存当时所有围栏的几何形状,因此之后编辑和删除不会破坏旧形状。
重命名围栏,或编辑描述、元数据,不会创建版本。名称不改变任何事件的判断结果,新版本只会冻结与上一版本相同的形状。因此,保存明显改变了内容后,版本数量也可能不变。
每个位置事件都会标记到达时生效的围栏集合版本。该标记使过去事件上的规则重放有意义:上周的事件根据上周的围栏判断。没有它,预览就会使用今天的形状,悄悄给出关于从未存在过的世界的、看似可信的答案。
围栏的**历史(History)**选项卡显示任意版本的边界,并在后面绘制当前形状供比较。它给出三种结果之一:
| 显示内容 | 含义 |
|---|---|
| 边界 | 该版本下此令牌保存的形状。 |
| “版本 N 的集合中不存在” | 当时不存在该围栏:之后才创建,或已删除后令牌被复用。这与存在但没有形状不同。 |
| “此查看器无法绘制的形状” | 当时它在集合中且执行了检测,只是控制台无法渲染。 |
删除围栏是永久操作,并释放令牌供复用。因此,旧版本条目说明某个令牌当时保存的形状,不一定属于你现在查看的围栏。
在规则中使用围栏
检测规则通过令牌访问围栏:
geo.inFence("yard-perimeter")
谓词针对被评估事件的位置,使用事件所标记的围栏集合给出结果。
引用因环未围出面积而无法编译的围栏的规则,仍可编译和发布。围栏保留在集合中并携带错误。评估时,对它的所有样本都会跳过,并计为评估错误,而不是任意回答;与下文描述的未知围栏令牌结果相同。
围栏测试与测量测试不能放在同一条件中
同时调用 geo.inFence(...) 并且读取测量的条件,会在发布时被拒绝:
geo.inFence("yard") && m["temp"] > 80 ← refused
这不是风格要求:没有事件能够满足它。位置事件上报位置,不携带测量;测量事件携带读数,不上报位置。同时需要两者的条件永远得不到输入,会在规则列表中显示健康,却永不触发。只有发布时能同时看到两部分;评估时,每部分都只是一个不符合条件的样本。
请改为两条规则:一条测试围栏,一条测试测量。或者,如果测试的值很少变化,将它作为设备属性,位置事件会携带属性。
未知或缺失的围栏
geo.inFence("typo") 可以编译和发布:发布时不检查令牌是否指向真实围栏。评估时调用无法回答,平台不会编造答案。样本会被跳过并计为评估错误,绝不回答“外部”。回答“外部”比无用更糟:对于持续条件规则,它看起来会像设备离开围栏。
四种情况会产生此结果,只有第一种是错误:
| 情况 | 现象 |
|---|---|
| 令牌拼错,或围栏已删除 | 规则生效后,每个位置事件都产生评估错误 |
| 对绘制围栏之前的事件预览规则 | 围栏存在前的整个时段都产生错误,因为当时集合确实没有该围栏 |
| 规则使用的围栏存在于比重放事件更晚的版本 | 结果和原因相同 |
| 租户第一条围栏规则发布后的数秒内 | 暂时性错误,引擎随规则到达加载围栏集合 |
评估错误只出现在两个地方:
- 编写预览中,按规则显示评估错误计数;
- 检测引擎的汇总指标
devicechain_eventprocessing_detect_fanout_eval_errors_total,启用 Helm Chart 指标告警后由DetectFanoutEvalErrors监测。
规则健康视图不显示这些错误,每个样本都产生错误的规则仍显示 ACTIVE。没有内容指出地理围栏是原因。如果围栏规则只有错误,没有其他结果,请先检查令牌。
内存中的围栏集合
实时引擎为每个租户保留最近四个围栏集合版本:当前版本和三个已被替代版本。这是为仍在传递中的、数秒至数分钟前的事件设计的。要达到上限,需要某事件从接入路径到达引擎期间发生四次围栏集合变更。事件标记的版本若已被逐出,会报告与未知围栏相同的计数评估错误。
这不会丢失任何历史。每个版本快照都持久保存,预览和重放从存储读取,而不是实时缓存。只有实时评估受限,并会自行恢复:后续事件携带当前版本。
引擎还会每隔几分钟从存储历史重新读取各租户当前围栏集合。通常没有变化,因为围栏编辑发生时就会通知引擎。它对一种少见情况很重要:保存围栏不会因通知发送失败而失败,因此通知丢失时,引擎原本会一直使用旧版本,直到下次重启。定期重新读取补上这一缺口,因此即使通知从未到达,围栏编辑最迟也会在几分钟内生效,无需重启,也无需再次保存。
权限
读取围栏和历史需要 device:read,删除需要 device:write,与设备注册表其余内容相同。
创建或修改围栏还需要 location:read。 绘制围栏不只是写入,也是在询问设备在哪里。若只有 device:write 就能创建围栏,用户可以放置小围栏、观察规则是否响应、移动围栏,再从结果推导设备群位置,而无需查看坐标的权限。删除仍只需要 device:write,因为删除围栏不查询任何位置。