3d基础模块添加,和地图接口加排序修改

This commit is contained in:
扈兆增 2026-07-15 11:13:25 +08:00
parent abfa77f645
commit 6e5dc9188d
3 changed files with 1339 additions and 285 deletions

View File

@ -240,7 +240,7 @@
- 快速切换基地不会出现旧数据覆盖新数据的问题。
- 多次切换 2D/3D 后基地裁切行为正常。
### Step 5打通 3D popup 与 hover
### Step 5打通 3D popup 与 hover ✅️
目标:
@ -337,7 +337,7 @@
- 图例单项勾选/取消时3D 对应类别锚点同步显隐。
- 图例分组勾选/取消时3D 行为与 2D 保持一致。
### Step 9补足收口与稳定性
### Step 9补足收口与稳定性 ✅️
目标:
@ -369,95 +369,6 @@
8. 补图例联动
9. 做切换稳定性收口
## 9. 必须补齐的隐藏逻辑清单
下面这些逻辑不是表面接口能直接看出来的,但如果 `3D` 不补,最终效果就会和 `2D` 明显不一致。
### 9.1 图层与菜单切换
- 菜单切换时,只隐藏旧页点图层,不隐藏底图。
- 图层树勾选前要先经过互斥规则归一化:
- `rare_fish_point``fish_along_point` 互斥
- `stinfo/stinfo_video_point``stinfo_ai_video_point` 互斥
- `facilities``facilities_built` 两个分支互斥
- 点图层取消勾选时只隐藏,不删除缓存;重新勾选时优先从缓存恢复。
- **图层树勾选时按 `type` 字段走不同分支**(见 `updateLayerData`
- `type === 'pointMap'` → 走 `handlePointMapLayer` → 调用 `mdLayerTreeShowOrHidden`(控制锚点显隐)
- `type === 'GISMap'` → 走 `controlBaseLayerTreeShowAndHidden`(控制底图/叠加影像图层显隐)
- 3D Cesium 必须同时实现这两个方法才能与图层树完整联动。
### 9.2 图例与点内显隐
- 图例控制的是图层内部锚点是否可见,不是图层本身是否可见。
- 图层恢复显示时,图例要先整层全关,再按当前 checked 状态逐项恢复。
- 环保设施图例组当前是只展示不交互。
### 9.3 缩放驱动显示
- `eng_point` 的中型水电站只在 `ENG_POINT_MEDIUM_VISIBLE_ZOOM = 7.5` 以上显示。
- 水电开发页缩放跨过 `12` 级时,会自动补开一批动态图层;缩回去时再自动收口。
- `2D` 存在"缩放越大显示越多"的行为,不是所有锚点在所有层级都等量显示。
- `2D` 还存在 hover、批量 popup、密度控制等缩放阈值
- hover 低于一定缩放不触发
- 高缩放会进入批量 popup 模式
- 点位会随着缩放提升逐步放开显示限制
- **图标动态缩放**`scale = 0.7 + (zoom - 4.5) * 0.08`,钳位到 `[0.5, 3.0]`
- **字体动态大小**`fontSize = clamp(12 * scale, 10, 24)`,即 10px~24px 之间随缩放变化。
- **文字在图标上方**2D 中 `text.offsetY = -22 * scale`,负值表示文字偏移到图标上方。
- **注意**:上述 2D scale 公式是针对 OpenLayers 2D 投影校准的Cesium 3D 球面视角需要更高的基准值(建议 1.0 起步),且 `scaleByDistance` 在 9,000km 中国高度处不应过度压缩(建议 near/far 范围设为 1km→2x, 20,000km→0.6x)。
- **Cesium 中必须使用 `CallbackProperty` 实现动态缩放**3D 中 `billboard.scale` 不能是静态值,否则放大后图标不会跟随变大。使用 `CallbackProperty` 每帧重新计算当前 zoom 对应的 scale与 2D `getDynamicPointScale` 行为对齐。
### 9.4 锚点碰撞与控量
- `2D` 中锚点是否可见,不只受图层开关控制,还受以下门闩共同影响:
- 图例可见性
- 区域裁切可见性
- 密度阈值
- 图标碰撞结果
- 标签碰撞结果
- 低缩放下,为避免堆叠,部分锚点或标签会被主动隐藏。
- 高缩放下,会逐步放开图标/标签限制,显示更多内容。
- 因此 `3D` 不能简单把所有 entity 永久全开,否则视觉密度会明显失控。
### 9.5 Popup 与点击
- hover popup 不是全时开启的,有缩放门槛。
- 点击和 hover 的命中对象是图标本体,不是任意文字区域。
- `ylfb` 与普通锚点的点击详情弹框走不同业务分支。
### 9.6 基地与区域裁切
- 基地切换不仅影响搜索候选,还会影响地图上的点位可见范围。
- 区域裁切会直接把区域外点位隐藏,不是只改底图遮罩。
### 9.7 筛选器特殊逻辑
- 装机容量筛选本质是批量切 `eng_point` 图例状态,不是简单切图层。
- 鱼类分布模式会接管 `ylfb_point` 的数据、缓存、图例与显隐。
- 时间筛选当前只重载指定时间相关图层,不是全量重载。
### 9.8 数据进入 addInitDataLayer 前的预处理
- 所有点位数据在进入 `addInitDataLayer` 前会经过 `normalizePointLayerItems()` 过滤:
- 没有 `iconCode``code !== 'colorLayer'` 的项会被抛弃
- `baseId == 'all'``anchoPointState` 结尾为 `_nbuilt` 的项会被抛弃
- `_id` 默认值生成规则:如果项没有 `_id`,会生成 `${sttpMap || iconType || 'point'}_${stcd}`
- **stcd+经纬度重复锚点处理**:同一个 layerKey 下,相同 stcd 和经纬度但不同 sttpMap/anchoPointState 的锚点需要全部保留,不能因为 Feature/Entity ID 冲突而被覆盖。2D 使用 `${layerKey}:${_id}` 作为 ID其中 `_id` 默认为 `${sttpMap}_${stcd}`,天然区分了同 stcd 不同业务类型的点。
### 9.9 图标渐进加载机制
- 2D 中图标不是一次性全加载完。有 `loading → loaded → error` 三种状态跟踪。
- 只有 `loaded` 状态的图标才参与样式渲染loader 失败的不渲染。
- 如果在图标加载过程中就触发了样式刷新,需要通过 `requestAnimationFrame` 回刷。
- 3D Cesium 中如果要实际渲染图标纹理,同样需要这种渐进加载机制,或在实现时先以简单 billboard 色块兜底。
### 9.10 密度分档与近邻点元数据
- 每个锚点的 `distance` 字段表示密度分档值(不是物理距离),值越大表示越稀疏。
- 不同 density 值对应不同的 `minZoom` 显示门槛(见 `nearby-point-rules.ts``densityDisplayRules`)。
- 全量数据加载后store 会调用 `attachNearbyPointRuntimeMeta()` 给所有点打上 `_nearbyRule`、`_nearbyGroupId`、`_nearbyPriority` 等元数据。
- **孤立点例外规则**:如果一个点在当前视口内 ~56px 范围内没有同 layerKey 的其他可见点,它可以绕过密度门槛直接显示。
## 7. 预计修改文件
- `src/components/gis/map.cesium.ts`
@ -478,3 +389,91 @@
- 默认飞到中国
这一步最小、最容易验证,也能先把“打开 3D 看起来不对”的核心问题解决掉。
## 9. 必须补齐的隐藏逻辑清单
下面这些逻辑不是表面接口能直接看出来的,但如果 `3D` 不补,最终效果就会和 `2D` 明显不一致。
### 9.1 图层与菜单切换 ✅️
- ✅ 菜单切换时,只隐藏旧页点图层,不隐藏底图。(编排器 `handlePageChange` 控制Cesium 被动接收数据)
- ✅ 图层树勾选前要先经过互斥规则归一化store 层 `updateLayerData` 处理Cesium 不感知)
- `rare_fish_point``fish_along_point` 互斥
- `stinfo/stinfo_video_point``stinfo_ai_video_point` 互斥
- `facilities``facilities_built` 两个分支互斥
- ✅ 点图层取消勾选时只隐藏,不删除缓存;重新勾选时优先从缓存恢复。`mdLayerTreeShowOrHidden` 设置 `_layerVisible` 标志,不删除 `pointLayerRegistry` 条目。
- ✅ **图层树勾选时按 `type` 字段走不同分支**(见 `updateLayerData`store 层处理):
- `type === 'pointMap'` → 走 `handlePointMapLayer` → 调用 `mdLayerTreeShowOrHidden`已实现L1624
- `type === 'GISMap'` → 走 `controlBaseLayerTreeShowAndHidden`已实现L1345
- 3D Cesium 已同时实现这两个方法。
### 9.2 图例与点内显隐 ✅️
- ✅ 图例控制的是图层内部锚点是否可见,不是图层本身是否可见。`setLegendPointVisible` 使用 `_legendVisible` 标志,与 `_layerVisible` 独立。
- ✅ 图层恢复显示时,图例要先整层全关,再按当前 checked 状态逐项恢复。支持 `anchoPointState=''` 全关 → 逐项 `(state, true)` 恢复两步模式L1764-1787
- ✅ 环保设施图例组当前是只展示不交互。编排器层面控制Cesium 不感知)
### 9.3 缩放驱动显示 ✅️
- ✅ `eng_point` 的中型水电站只在 `ENG_POINT_MEDIUM_VISIBLE_ZOOM = 7.5` 以上显示。store `filterPointLayerDataForDisplay` 在数据进入 Cesium 前过滤)
- ✅ 水电开发页缩放跨过 `12` 级时,会自动补开一批动态图层;缩回去时再自动收口。(编排器层面处理)
- ✅ `2D` 存在"缩放越大显示越多"的行为。碰撞检测中密度门控逐级放开。
- ✅ `2D` 还存在 hover、批量 popup、密度控制等缩放阈值
- hover 低于 ~zoom 4.5 不触发(`HOVER_POPUP_MAX_HEIGHT = 9_000_000`
- 高缩放进入批量 popup 模式(`BATCH_POPUP_MODE_ZOOM = 14.9`
- 点位随着缩放提升逐步放开密度限制(密度门控 + `DENSITY_ISOLATION_THRESHOLD`
- ✅ **图标动态缩放**`CallbackProperty` 实现 `billboard.scale`,公式 `clamp(0.7 + (zoom - 4.5) * 0.035, 0.4, 1.2)`L1522
- ✅ **字体动态大小**`CallbackProperty` 实现 `label.font`,钳位 `[10, 20]`L1535
- ✅ **文字在图标上方**`CallbackProperty` 实现 `label.pixelOffset`,负 Y 偏移L1549
- ✅ **Cesium 中必须使用 `CallbackProperty` 实现动态缩放**:已使用 3 个 `CallbackProperty`scale/font/offset`isConstant=false` 每帧重算。
### 9.4 锚点碰撞与控量 ✅️
- ✅ `2D` 中锚点是否可见,不只受图层开关控制,还受以下门闩共同影响:
- 图例可见性 `_legendVisible`
- 区域裁切可见性 `_regionVisible`
- 密度阈值(碰撞循环中 inline 计算,逐级放开)
- 图标碰撞结果 `_iconCollisionVisible`
- 标签碰撞结果 `_labelCollisionVisible`
- ✅ 低缩放下,为避免堆叠,部分锚点或标签会被主动隐藏。(`refreshPointLabelVisibility` 碰撞检测L2117-2455
- ✅ 高缩放下,会逐步放开图标/标签限制,显示更多内容。(密度门控 + 碰撞容差随 zoom 调整)
- ✅ 因此 `3D` 不能简单把所有 entity 永久全开。所有 entity 初始 `_iconCollisionVisible = _labelCollisionVisible = true`,碰撞检测运行后动态调整。
### 9.5 Popup 与点击 ✅️
- ✅ hover popup 不是全时开启的,有缩放门槛。相机高度 > `HOVER_POPUP_MAX_HEIGHT` 时不触发 hover。
- ✅ 点击和 hover 的命中对象是图标本体,不是任意文字区域。`ScreenSpaceEventHandler` 基于 `scene.pick` 命中 entity。
- ✅ `ylfb` 与普通锚点的点击详情弹框走不同业务分支。`sttpMap === 'ylfb'` 打开 `ylfbModalVisible`,其他走 `modalVisible`L366-375
### 9.6 基地与区域裁切 ✅️
- ✅ 基地切换不仅影响搜索候选,还会影响地图上的点位可见范围。`jdPanelControlShowAndHidden` 实现L1684-1748
- ✅ 区域裁切会直接把区域外点位隐藏,不是只改底图遮罩。`filterPointsByRegion` 设置 `_regionVisible = false`L1824-1848遮罩多边形 + 裁剪双重处理。
### 9.7 筛选器特殊逻辑 ✅️
- ✅ 装机容量筛选本质是批量切 `eng_point` 图例状态不是简单切图层。store 层处理Cesium 通过 `setLegendPointVisible` 响应)
- ✅ 鱼类分布模式会接管 `ylfb_point` 的数据、缓存、图例与显隐。store 层处理)
- ✅ 时间筛选当前只重载指定时间相关图层不是全量重载。store 层处理)
### 9.8 数据进入 addInitDataLayer 前的预处理 ✅️
- ✅ 所有点位数据在进入 `addInitDataLayer` 前会经过 `normalizePointLayerItems()` 过滤store 层 `map.ts` 执行Cesium 接收时已过滤)
- 没有 `iconCode``code !== 'colorLayer'` 的项会被抛弃
- `baseId == 'all'``anchoPointState` 结尾为 `_nbuilt` 的项会被抛弃
- ✅ `_id` 默认值生成规则store 层生成 `${sttpMap || iconType || 'point'}_${stcd}`
- ✅ **stcd+经纬度重复锚点处理**Cesium entity ID 使用 `${layerKey}:${item._id}`,与 2D 对齐,同 stcd 不同 sttpMap 的点不会互相覆盖。
### 9.9 图标渐进加载机制
- ❌ 2D 中图标不是一次性全加载完。有 `loading → loaded → error` 三种状态跟踪。
- ❌ 只有 `loaded` 状态的图标才参与样式渲染loader 失败的不渲染。
- ❌ 如果在图标加载过程中就触发了样式刷新,需要通过 `requestAnimationFrame` 回刷。
- ❌ 3D Cesium 中当前所有 entity 同步创建billboard `image` URL 直接赋值无渐进加载。Cesium 内部纹理加载无显式状态跟踪。
### 9.10 密度分档与近邻点元数据 ✅️
- ✅ 每个锚点的 `distance` 字段表示密度分档值(不是物理距离),值越大表示越稀疏。(已使用 `getEntityDensityValue``_rawData.distance` 读取L2460
- ✅ 不同 density 值对应不同的 `minZoom` 显示门槛。`getEntityDensityMinZoom` 映射 `nearby-point-rules.ts``densityDisplayRules`L2491
- ✅ 全量数据加载后store 会调用 `attachNearbyPointRuntimeMeta()` 给所有点打上 `_nearbyRule`、`_nearbyGroupId`、`_nearbyPriority` 等元数据。store 层处理Cesium 碰撞检测中读取)
- ✅ **孤立点例外规则**:如果一个点在当前视口内 ~56px 范围内没有同 layerKey 的其他可见点,它可以绕过密度门槛直接显示。`DENSITY_ISOLATION_THRESHOLD = 56` + `shouldBypassDensityGateForIsolatedEntity`L2506-2556

File diff suppressed because it is too large Load Diff

View File

@ -122,19 +122,48 @@ const normalizeRequestParams = (rawParams: any) => {
};
};
const parseAnchorParamFilters = (anchorParamJson?: string) => {
if (!anchorParamJson) return [];
const parseAnchorParamJson = (anchorParamJson?: string) => {
const result: { filters: any[]; orders: any[] | null } = {
filters: [],
orders: null
};
if (!anchorParamJson) return result;
try {
const parsedParams = JSON.parse(anchorParamJson);
if (!parsedParams || typeof parsedParams !== 'object') {
return [];
const parsed = JSON.parse(anchorParamJson);
if (!parsed || typeof parsed !== 'object') return result;
// 解析 params -> filters兼容旧格式如果没有 params 字段,整个对象即为 params
const paramsObj = parsed.params || parsed;
if (
paramsObj &&
typeof paramsObj === 'object' &&
!Array.isArray(paramsObj)
) {
result.filters = buildFiltersFromParamsObject(paramsObj);
}
return buildFiltersFromParamsObject(parsedParams);
// 解析 orders -> sort 数组
if (parsed.orders) {
let ordersObj: Record<string, any> = {};
if (typeof parsed.orders === 'string') {
ordersObj = JSON.parse(parsed.orders);
} else if (typeof parsed.orders === 'object') {
ordersObj = parsed.orders;
}
if (ordersObj && typeof ordersObj === 'object') {
result.orders = Object.entries(ordersObj).map(([field, dir]) => ({
field,
dir
}));
}
}
return result;
} catch (error) {
console.error('解析 anchorParamJson 失败:', error);
return [];
return result;
}
};
@ -1158,9 +1187,12 @@ export const useMapStore = defineStore('map', () => {
requestParams = normalizeRequestParams(requestParams);
const anchorParamFilters = parseAnchorParamFilters(anchorParamJson);
if (anchorParamFilters.length) {
requestParams.filters.push(...anchorParamFilters);
const anchorParamResult = parseAnchorParamJson(anchorParamJson);
if (anchorParamResult.filters.length) {
requestParams.filters.push(...anchorParamResult.filters);
}
if (anchorParamResult.orders) {
requestOrders = anchorParamResult.orders;
}
if (timeRangeLayerKeys.includes(layerKey)) {
@ -1194,7 +1226,7 @@ export const useMapStore = defineStore('map', () => {
}
}
if (layer?.orders) {
if (!requestOrders && layer?.orders) {
try {
const ordersObj = JSON.parse(layer.orders);
const ordersArray = Object.entries(ordersObj).map(([field, dir]) => ({