P4 Gotham
时空分析:把事件放到地图和时间轴上
一张 Leaflet 地图看 6 个地理要素(事件点 + 航线),bbox / 半径空间查询锁定区域;一条时间轴看 2026-07~08 的 9 条事件,范围查询 + 日 / 周 / 月桶聚合看事件节奏。看完这 4 个故事,你就能把"哪段时间、哪个区域发生了什么"讲清楚。
情报分析员
作战值班员
地图要素
空间查询
时间轴
桶聚合
共 4 个故事
能 / 不能速览
✅ 这个主题能做
- 地图查看点 / 线要素(GeoJSON),Leaflet 前端渲染,popup 查看详情
- bbox 包围盒查询(minLng / minLat / maxLng / maxLat)锁定矩形区域
- radius 半径查询(圆心 + 半径米数,100km 硬上限),Haversine 距离判定
- 区域聚合(/map/aggregate/regions)按行政区标签统计要素数量
- 时间轴事件 CRUD + 范围查询(start / end)+ day / week / month / year 桶聚合
- 事件按 event_type / severity / entity_id / location 过滤,severity 含 critical / high / medium / low
⛔ 这个主题做不了
- 未接真实时序库与真实底图服务:Leaflet 用静态瓦片底图,时间桶聚合为内存 / SQL 计算
- 半径查询有 100km 硬上限,超过返回错误
- 要素几何类型仅 point / linestring / polygon,无复杂几何(multi-polygon 等)
- 区域聚合的 region 是业务标签(如"北京"),不是真实行政区划数据,统计口径由数据方决定
适用角色
本主题面向两类角色:
- 情报分析员:核心使用者,把事件落到地图与时间轴,做时空关联研判。
- 作战值班员:用区域聚合与桶聚合快速掌握"哪里、哪段时间最热",支撑调度决策。
地图与时间轴上的要素 / 事件都支持 CRUD,情报分析员可自行补充,改动落库。
能力速览(能做什么)
地图要素
GeoJSON 点 / 线要素:5 个事件点(北京 / 上海 / 广州 / 青岛 / 深圳)+ 1 条航线线要素,Leaflet 渲染 + popup 详情。
空间查询
bbox 包围盒与 radius 半径两类查询:bbox 判点在框内 / 线面相交,radius 用 Haversine 距离判定(100km 上限)。
时间轴事件
9 条 2026-07~08 事件(meeting / movement / transaction / incident / registration),支持 event_type / severity / entity / location 过滤。
聚合统计
时间桶聚合 day / week / month / year 看事件节奏;区域聚合(regions)按区域统计要素数量。
调整指南(怎么调整)
- 改查询范围:bbox 参数是 minLng / minLat / maxLng / maxLat;radius 参数是 lng / lat / radiusMeters(米),别超过 100000。
- 改时间粒度:/timeline/aggregate 的 bucket 支持 day / week / month / year;start / end 支持 RFC3339 或 yyyy-MM-dd。
- 改过滤口径:事件按 event_type 过滤看某类事件(如只查 incident);地图按 geometry_type / region / entity_id 过滤。
- 改要素:地图支持新增要素(point / linestring / polygon),新情报随时上图;时间轴支持新增事件,改动落库。
做得好的场景
时空分析最擅长"用位置和时间锁定线索":
- 空间聚焦:bbox / 半径查询几秒圈出目标区域内的全部要素,替代手工翻地图定位。
- 时间节奏一眼看:周 / 月桶聚合柱状图直接反映事件是变密还是变疏。
- 事件可回放:范围查询把某段时间内的事件按时间排开,串成一条可汇报的脉络。
- 轻量自维护:要素与事件都支持页面新增,情报员自己上图、上事件,不用等开发。
限制与不足
以下是明确的边界,使用前先知道:
- 底图与时序库:Leaflet 用静态瓦片底图,未接真实地图服务;时间轴未接真实时序库,聚合为内存 / SQL 时间桶计算。
- 半径上限:radius 查询 100km 硬上限,超出直接拒绝。
- 几何类型受限:仅 point / linestring / polygon,无复杂几何类型。
- 区域是标签:region 是业务标签("北京""沿海"),不是真实行政区划统计,口径由录入方决定。
场景故事
故事 1
打开地图,6 个地理要素(事件点 + 航线)一次看全
场景:地图要素
角色:情报分析员
耗时:约 3 分钟
- 背景
- 苏分析师打开 /gotham/map(Leaflet 地图),演示数据里已 seed 好 6 个地理要素:北京联络点、上海货栈、广州港口、青岛锚地、深圳观察点 5 个事件点,外加一条"华北-华东航线"线要素。她要先在地图上建立全局空间认知。
- 传统做法对比
- 以前要把各条情报里的地名一个个在地图软件里手查坐标、标点,标完还要人肉记"哪个点对应哪个实体"。现在打开页面就是一张标好要素的地图,点一下就弹详情。
- 角色
- 苏分析师(情报分析员,只读浏览即可)。
- 操作步骤
-
- 打开地图页 /gotham/map
- 确认 5 个点要素:北京联络点 / 上海货栈 / 广州港口 / 青岛锚地 / 深圳观察点
- 确认 1 条线要素:华北-华东航线(北京 → 上海 → 青岛)
- 点击北京联络点 popup,看关联实体与属性
- 系统响应
- 要素接口返回结构示例:
{
"name": "北京联络点",
"geometry_type": "point",
"coordinates": [116.40, 39.90],
"properties": { "entity_type": "person" },
"entity_id": "graph:person:zhy",
"region": "北京"
}
线要素 coordinates 为 [[lng,lat], ...] 数组。
- 结果洞察
- 北京联络点关联核心人物张远(entity_id=graph:person:zhy),上海货栈关联天河贸易,广州港口关联青龙航运,青岛锚地是远航号所在——地图上的点与图上的节点一一对应,空间分布勾勒出"华北人、华东货、华南船"的格局。
- 调整建议
- 用 region 过滤要素(北京 / 上海 / 广州 / 山东 / 广东 / 沿海);点要素 popup 里有"在图谱中查看"入口,可跳到对应图节点。
- 动手试一试
- 操作:/gotham/map 地图页 + /map/features。预期结果:地图上出现 5 个点要素与 1 条线要素,北京联络点 popup 显示 entity_id=graph:person:zhy、region=北京。
- 限制提示
- Leaflet 使用静态瓦片底图,未接真实底图服务;要素几何类型仅 point / linestring / polygon;region 是业务标签不是真实行政区划数据。
故事 2
bbox / 半径空间查询,把华东区域和上海周边一次锁定
场景:空间查询
角色:情报分析员
耗时:约 4 分钟
- 背景
- 苏分析师要聚焦华东方向:先用 bbox 圈出华东矩形区域看哪些要素在里面,再用半径查询看上海货栈周边 50 公里内有什么。两次查询都要在几秒内完成。
- 传统做法对比
- 以前要在地图上人工框选、逐点量距离,一次空间筛选耗掉半小时,还容易把区域边界算错。现在 bbox / 半径两个接口秒出结果,框选范围随意调整。
- 角色
- 苏分析师(情报分析员,只读查询)。
- 操作步骤
-
- 调用 bbox 查询:/map/features/bbox?minLng=118&minLat=29&maxLng=123&maxLat=33
- 查看落在华东框内的要素
- 调用半径查询:/map/features/radius?lng=121.47&lat=31.23&radiusMeters=50000
- 对比两次结果,确认"上海周边"的覆盖范围
- 系统响应
- bbox 返回框内要素(上海货栈、华北-华东航线的华东段等):
[
{ "name": "上海货栈", "geometry_type": "point", "coordinates": [121.47, 31.23], "region": "上海" }
]
radius 返回距 (121.47, 31.23) 50km 内的要素,判定用 Haversine 大圆距离。
- 结果洞察
- 华东 bbox 内命中上海货栈;半径 50km 内除了货栈还覆盖到华北-华东航线线段——空间关系直接提示"天河贸易的货栈正处在重点航线的中继位置上",为后续研判提供了方向。
- 调整建议
- bbox 用 minLng / minLat / maxLng / maxLat 四个角点;radius 的 radiusMeters 单位是米,想圈更大区域先确认在 100km 上限内;点 / 线判定规则不同(点看圆心距离,线按任意点在半径内)。
- 动手试一试
- 输入内容:bbox 用 118,29,123,33;radius 用 lng=121.47、lat=31.23、radiusMeters=50000。预期结果:bbox 命中华东要素;radius 命中距上海货栈 50km 内的要素(含航线线段)。
- 限制提示
- 半径查询硬上限 100km(MaxRadiusMeters=100000),超过返回错误;线 / 面要素按"任意点在查询范围内"判定,细长线要素跨区域时注意边界口径。
故事 3
时间轴查 9 条事件 + 范围查询,串出 7-8 月的活动脉络
场景:时间轴
角色:作战值班员
耗时:约 5 分钟
- 背景
- 作战值班员王涛要复盘 2026 年 7 月到 8 月的活动脉络。演示时间轴里有 9 条事件:会晤、船舶离港、大宗交易、登记、货栈异常、靠泊、港口检查、集会、航线异常。他先看全量,再按时间段范围查询,再过滤高严重度事件。
- 传统做法对比
- 以前把 9 条事件从不同通报里摘出来按时间排 Excel,光排序对日期就要半天,还容易漏掉某一条。现在时间轴默认按时间排好,范围查询 + 类型过滤几秒钟搞定。
- 角色
- 王涛(作战值班员,只读查询)。
- 操作步骤
-
- 打开时间轴页 /gotham/timeline,查看 9 条事件的默认列表
- 调用范围查询:/timeline/events/range?start=2026-07-20&end=2026-08-10
- 按 event_type=incident 过滤看异常事件
- 按 severity 排序看高危 / 危重事件
- 系统响应
- 事件结构示例:
{
"title": "上海货栈异常",
"event_time": "2026-07-25T16:45:00Z",
"event_type": "incident",
"severity": "high",
"entity_id": "graph:org:tianhe",
"location": "上海"
}
范围查询 start / end 支持 RFC3339 或 yyyy-MM-dd。
- 结果洞察
- 7-20 至 8-10 区间内命中:7-25 上海货栈异常(incident / high)、8-1 远航号靠泊青岛(movement / low)、8-6 广州港口检查(incident / medium);全量 9 条里 3 条 incident,8-15 华南航线异常是唯一的 critical。活动脉络:7 月上旬节奏平稳,8 月中旬异常集中。
- 调整建议
- 用 event_type 过滤聚焦某类事件(incident / meeting / movement / transaction / registration);用 entity_id 看单实体的事件史(如只查天河贸易相关的 3 条)。
- 动手试一试
- 输入内容:range?start=2026-07-20&end=2026-08-10;再试 event_type=incident。预期结果:范围查询返回 3 条事件;incident 过滤返回上海货栈异常、广州港口检查、华南航线异常 3 条,其中华南航线异常 severity=critical。
- 限制提示
- 时间范围 start / end 需按 RFC3339 或 yyyy-MM-dd 传;未接真实时序库,列表与过滤为 SQL 查询;severity 可为空,空值事件不参与严重度排序。
故事 4
日 / 周 / 月桶聚合看事件节奏,区域聚合看哪里最热
场景:聚合统计
角色:作战值班员
耗时:约 4 分钟
- 背景
- 王涛要给指挥员汇报"近期态势"。他要用时间桶聚合回答"事件是变密还是变疏",用区域聚合回答"哪里最热"。两张图就能把 7-8 月态势讲清楚。
- 传统做法对比
- 以前按月手工数事件、按地区数通报,两张统计表要花一个上午。现在两个聚合接口一次出数,柱状图直接上图。
- 角色
- 王涛(作战值班员,只读查询)。
- 操作步骤
-
- 调用周桶聚合:/timeline/aggregate?bucket=week&start=2026-07-01&end=2026-08-31
- 调用月桶聚合:/timeline/aggregate?bucket=month&start=2026-07-01&end=2026-08-31
- 调用区域聚合:/map/aggregate/regions
- 把两组数字整理进汇报材料
- 系统响应
- 时间桶聚合返回 [{bucket, count}](bucket 按 ISO 周 / 自然月格式化):
[
{ "bucket": "2026-W27", "count": 2 },
{ "bucket": "2026-W28", "count": 1 },
{ "bucket": "2026-W29", "count": 1 },
{ "bucket": "2026-W33", "count": 2 }
]
区域聚合返回(按要素 region 计数):[{ "region": "北京", "count": 1 }, { "region": "上海", "count": 1 }, { "region": "广州", "count": 1 }, { "region": "山东", "count": 1 }, { "region": "广东", "count": 1 }, { "region": "沿海", "count": 1 }]
- 结果洞察
- 时间上:7 月上旬(W27,含会晤与离港)与 8 月中旬(W33,含集会和航线异常)各 2 起最密,7 月共 5 起、8 月共 4 起;3 条 incident 分布在 W30(7-25 货栈异常)、W32(8-6 港口检查)、W33(8-15 航线异常),30 天内 incident≥3 正好触发"高频事件预警"规则。空间上:6 个要素覆盖 6 个区域(北京 / 上海 / 广州 / 山东 / 广东 / 沿海),沿海航线把 5 个事件点串成一条线——"点线结合"的格局说明活动沿重点航线展开。结论:态势重心在华南—华东一线,近期异常频次在抬升。
- 调整建议
- bucket 支持 day / week / month / year,看短期节奏用 day / week,看趋势用 month;聚合可按 type 过滤(如只统计 incident);区域聚合口径由要素的 region 标签决定。
- 动手试一试
- 输入内容:aggregate?bucket=month&start=2026-07-01&end=2026-08-31;再试 bucket=week 同范围;/map/aggregate/regions。预期结果:月桶返回 2026-07:5、2026-08:4;周桶返回 2026-W27:2、2026-W33:2 等 7 个桶;区域聚合返回 6 个区域各 1(北京 / 上海 / 广州 / 山东 / 广东 / 沿海)。
- 限制提示
- 聚合是内存 / SQL 时间桶计算,大数据量性能有限;region 是业务标签非真实行政区划统计,跨源数据 region 口径不一致时汇总数字仅供参考。
常见问题
地图底图是真实的吗?
地图用 Leaflet + 静态瓦片底图渲染,方便离线演示;未接真实地图服务。地理要素(点 / 线 / 面)坐标与属性是本系统数据,可在页面新增维护。
半径查询最大能查多大?
radiusMeters 上限 100km(100000 米),超过直接拒绝。bbox 没有类似上限,但框选范围过大时返回全量要素,注意过滤。
时间轴的时间格式要求?
范围查询的 start / end 支持 RFC3339(如 2026-08-01T00:00:00Z)或 yyyy-MM-dd(如 2026-08-01)。聚合的 bucket 取 day / week / month / year。
区域聚合的数字怎么来的?
/map/aggregate/regions 按要素的 region 字段分组计数(region 为空归入"未标注")。region 是录入方打的业务标签,不是真实行政区划,口径需要数据提供方对齐。
要素和事件能自己加吗?
能。地图支持新增要素(point / linestring / polygon),时间轴支持新增事件(标题 / 时间 / 类型 / 严重度 / 关联实体 / 位置),页面操作即可,改动落库,重启保留。
主题小结
一句话:时空分析把"事件"放进地图和时间轴两个坐标系里——bbox / 半径圈空间,范围查询 + 桶聚合看时间节奏,区域聚合看热度分布。记住边界:Leaflet 静态底图、未接真实时序库、半径 100km 上限、几何仅点线面、region 是业务标签。