WholeProcessPlatform/frontend/src/components/gis/osgbUtils.ts

748 lines
22 KiB
TypeScript
Raw Normal View History

/**
* (OSGB)
* osgbTool.ts
*
* 使用字段: url, boundary, location, height, accuracy
*/
import * as turf from '@turf/turf';
import * as Cesium from 'cesium';
import { getToken } from '@/utils/auth';
// ==================== 类型定义 ====================
export interface OSGBLocation {
destination: number[]; // [x, y, z] 或 [lng, lat, height]
orientation: {
heading: number;
pitch: number;
roll: number;
};
center: number[];
}
export interface OSGBItem {
/** 3D Tileset 服务地址 */
url: string;
/** GeoJSON Polygon 边界(用于裁切) */
boundary?: any;
/** 定位信息 */
location?: OSGBLocation | string;
/** 模型高度偏移 */
height?: number;
/** 屏幕空间误差 (默认 16) */
accuracy?: number;
/** 内部状态 - 不要手动设置 */
osgbtitles?: any;
cartesian3?: any;
clippingPlanes?: any;
eventListener?: any;
_loading?: boolean;
_loadId?: number;
/** 用户手动关闭开关标志:为 true 时不自动加载/显示,只做显隐切换 */
_switchOff?: boolean;
}
// ==================== 内部状态(每个 OSGB 实例独立) ====================
interface OSGBInstanceState {
destination: Cesium.Cartesian3 | null;
orientation: any;
timelookat: any;
time: number;
num: number;
witetime: number;
timeout: any;
}
const instanceStates = new WeakMap<OSGBItem, OSGBInstanceState>();
function getState(obj: OSGBItem): OSGBInstanceState {
let state = instanceStates.get(obj);
if (!state) {
state = {
destination: null,
orientation: null,
timelookat: null,
time: 0,
num: 0,
witetime: 0,
timeout: undefined
};
instanceStates.set(obj, state);
}
return state;
}
function clearState(obj: OSGBItem) {
const state = instanceStates.get(obj);
if (state) {
clearTimeout(state.timeout);
clearInterval(state.timelookat);
}
instanceStates.delete(obj);
}
// ==================== 3D Tileset 场景管理 ====================
/**
* tileset
*/
const addOSGB = (viewer: Cesium.Viewer, tileset: Cesium.Cesium3DTileset) => {
if (!viewer || viewer.isDestroyed()) return;
viewer.scene.primitives.add(tileset);
};
/**
* tilesetPrimitiveCollection.remove destroy=true tileset
*/
const removeOSGB = (viewer: Cesium.Viewer, tileset: Cesium.Cesium3DTileset) => {
if (!viewer || viewer.isDestroyed()) return;
viewer.scene.primitives.remove(tileset);
};
// ==================== 裁切相关 ====================
/**
* GeoJSON Geometry
* Feature / FeatureCollection / Geometry
*/
const normalizeGeometry = (geo: any): any => {
if (!geo) return null;
// 已经是 Geometry 类型(有 type 且 coordinates
if (geo.type && geo.coordinates) return geo;
// Feature → 提取 geometry
if (geo.type === 'Feature' && geo.geometry) return geo.geometry;
// FeatureCollection → 取第一个 feature 的 geometry
if (geo.type === 'FeatureCollection' && geo.features?.length) {
return normalizeGeometry(geo.features[0]);
}
return null;
};
const get_verts_core = (poly: any): Cesium.Cartesian3[] => {
const verts_carte3: Cesium.Cartesian3[] = [];
if (!poly) return verts_carte3;
try {
// 规范化输入:兼容 Feature / FeatureCollection / Geometry
const geom = normalizeGeometry(poly);
if (!geom) return verts_carte3;
let verts = turf.coordAll(geom);
const clockwiseRing = turf.booleanClockwise(turf.lineString(verts));
if (clockwiseRing) {
verts = verts.reverse();
}
for (let i = 0; i < verts.length - 1; i++) {
const vert = verts[i];
verts_carte3.push(Cesium.Cartesian3.fromDegrees(vert[0], vert[1]));
}
} catch (e) {
console.warn('get_verts_core 解析 boundary 失败:', e);
}
return verts_carte3;
};
const get_clipping_planes = (
points: Cesium.Cartesian3[]
): Cesium.ClippingPlane[] => {
const pointsLength = points.length;
const clippingPlanes: Cesium.ClippingPlane[] = [];
for (let i = 0; i < pointsLength; ++i) {
const nextIndex = (i + 1) % pointsLength;
let midpoint = Cesium.Cartesian3.add(
points[i],
points[nextIndex],
new Cesium.Cartesian3()
);
midpoint = Cesium.Cartesian3.multiplyByScalar(midpoint, 0.5, midpoint);
const up = Cesium.Cartesian3.normalize(midpoint, new Cesium.Cartesian3());
let right = Cesium.Cartesian3.subtract(
points[nextIndex],
midpoint,
new Cesium.Cartesian3()
);
right = Cesium.Cartesian3.normalize(right, right);
let normal = Cesium.Cartesian3.cross(right, up, new Cesium.Cartesian3());
normal = Cesium.Cartesian3.normalize(normal, normal);
const originCenteredPlane = new Cesium.Plane(normal, 0.0);
const distance = Cesium.Plane.getPointDistance(
originCenteredPlane,
midpoint
);
clippingPlanes.push(new Cesium.ClippingPlane(normal, distance));
}
return clippingPlanes;
};
const clipping_terrain = (
viewer: Cesium.Viewer,
clippingPlanes: Cesium.ClippingPlane[]
) => {
const globe = viewer.scene.globe;
globe.clippingPlanes = new Cesium.ClippingPlaneCollection({
planes: clippingPlanes,
edgeWidth: 0,
edgeColor: Cesium.Color.GREEN,
enabled: true
});
globe.backFaceCulling = true;
globe.showSkirts = true;
};
const clipping_3dtiles = (
clippingPlanes: Cesium.ClippingPlane[],
tileset: Cesium.Cesium3DTileset
) => {
// Cesium 1.141: root.transform 是内部属性,安全访问
const rootTransform = (tileset as any)?.root?.transform;
if (!rootTransform) return;
const invtrans = Cesium.Matrix4.inverse(rootTransform, new Cesium.Matrix4());
for (let i = 0; i < clippingPlanes.length; i++) {
clippingPlanes[i] = Cesium.Plane.transform(clippingPlanes[i], invtrans);
}
tileset.clippingPlanes = new Cesium.ClippingPlaneCollection({
planes: clippingPlanes,
unionClippingRegions: true,
edgeWidth: 0,
edgeColor: Cesium.Color.RED,
enabled: false
});
};
const Clip3DTile = (boundary: any, tileset: Cesium.Cesium3DTileset) => {
try {
const geom = normalizeGeometry(boundary);
if (!geom) return;
const buffered = turf.buffer(geom, 10, { units: 'meters' });
const verts_core = get_verts_core(buffered).reverse();
if (!verts_core.length) return;
const planes = get_clipping_planes(verts_core);
clipping_3dtiles(planes, tileset);
} catch (e) {
console.warn('Clip3DTile 裁切失败:', e);
}
};
const ClipTerrain = (viewer: Cesium.Viewer, osgbObj: OSGBItem) => {
// Cesium 1.141: 安全访问 clippingPlanes 内部属性 _planes
const existingPlanes = (viewer.scene.globe.clippingPlanes as any)?._planes;
if (
!osgbObj.clippingPlanes ||
(existingPlanes &&
!compareClippingPlanes(osgbObj.clippingPlanes, existingPlanes))
) {
const verts_core = get_verts_core(osgbObj.boundary);
if (!verts_core.length) return;
const planes = get_clipping_planes(verts_core);
clipping_terrain(viewer, planes);
osgbObj.clippingPlanes = planes;
}
};
const removeClip = (viewer: Cesium.Viewer, osgbObj: OSGBItem) => {
if (osgbObj.clippingPlanes) {
removeClippingPlanes(viewer);
osgbObj.clippingPlanes = undefined;
}
};
const removeClippingPlanes = (viewer: Cesium.Viewer) => {
if (!Cesium.defined(viewer) || viewer.isDestroyed()) {
return;
}
if (Cesium.defined(viewer.scene.globe.clippingPlanes)) {
viewer.scene.globe.clippingPlanes.removeAll();
}
};
const compareClippingPlanes = (planes1: any, planes2: any): boolean => {
let result = true;
if (planes1.length === planes2?.length) {
for (let index = 0; index < planes1.length; index++) {
if (planes1[index]._distance !== planes2[index]._distance) {
result = false;
break;
}
}
} else {
result = false;
}
return result;
};
const getMinDistanceOfOSGB = (viewer: Cesium.Viewer): number => {
let result = Infinity;
const length = viewer.scene.primitives.length;
for (let i = 0; i < length; i++) {
const p = viewer.scene.primitives.get(i);
// Cesium 1.141: _url 和 _distanceToCamera 是内部属性,安全访问
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((p as any)?._url) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const d = (p as any)?.root?._distanceToCamera;
if (d != null && d < result) result = d;
}
}
return result;
};
// ==================== 动态显隐 ====================
const dynamicSetVisible = (viewer: Cesium.Viewer, osgbObj: OSGBItem) => {
if (!Cesium.defined(viewer) || viewer.isDestroyed()) return;
if (!Cesium.defined(osgbObj?.cartesian3)) return;
let cameraPosition: Cesium.Cartesian3 | undefined;
if (viewer.trackedEntity) {
cameraPosition = viewer.trackedEntity.position?.getValue(
viewer.clock.currentTime
);
}
if (!cameraPosition) {
cameraPosition = viewer.camera.position.clone();
}
const distance = Cesium.Cartesian3.distance(
osgbObj.cartesian3,
cameraPosition
);
if (distance < 12000) {
// 用户手动关闭了开关 → 不自动加载
if (osgbObj._switchOff) return;
// 匹配旧代码逻辑osgbObj.osgbtitles 同步设置为 nonClassificationTileset 防重入
// 新代码用 _loading + _loadId 实现同样的效果(因为 fromUrl 是异步的)
if (!osgbObj.osgbtitles && !osgbObj._loading) {
osgbObj._loading = true;
osgbObj._loadId = (osgbObj._loadId || 0) + 1;
const loadId = osgbObj._loadId;
const url = osgbObj.url.includes('?')
? osgbObj.url + `&token=${getToken()}`
: osgbObj.url + `?token=${getToken()}`;
Cesium.Cesium3DTileset.fromUrl(url, {
maximumScreenSpaceError: Number(osgbObj?.accuracy) || 16,
show: true,
maximumMemoryUsage: 128
})
.then((tileset: Cesium.Cesium3DTileset) => {
// 竞态保护版本号变了removeQxsy 被调用或新一轮加载开始)→ 销毁退出
if (
osgbObj._loadId !== loadId ||
!Cesium.defined(viewer) ||
viewer.isDestroyed()
) {
tileset.destroy();
return;
}
osgbObj._loading = false;
if (!Cesium.defined(tileset)) return;
tileset.maximumScreenSpaceError = 32;
// height 向上偏移模型几何自带高程height 只是微调(如 44m
if (osgbObj.height != 0) {
const bs = Cesium.Cartographic.fromCartesian(
tileset.boundingSphere.center
);
const surface = Cesium.Cartesian3.fromRadians(
bs.longitude,
bs.latitude,
0.0
);
const offset = Cesium.Cartesian3.fromRadians(
bs.longitude,
bs.latitude,
osgbObj.height
);
const translation = Cesium.Cartesian3.subtract(
offset,
surface,
new Cesium.Cartesian3()
);
tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation);
}
addOSGB(viewer, tileset);
osgbObj.osgbtitles = tileset;
})
.catch((e: Error) => {
osgbObj._loading = false;
console.log('dynamicSetVisible 加载倾斜摄影报错', e);
});
}
} else if (distance > 13000) {
// 匹配旧代码逻辑:镜头拉远 → 销毁 tileset 释放内存
if (osgbObj.osgbtitles) {
removeOSGB(viewer, osgbObj.osgbtitles);
osgbObj.osgbtitles = undefined;
}
// 不重置 _switchOff开关状态由用户手动控制不受距离影响
}
};
// ==================== 地形动态裁切 ====================
const dynamicClipTerrain = (viewer: Cesium.Viewer, osgbObj: OSGBItem) => {
if (!osgbObj?.osgbtitles) {
removeClip(viewer, osgbObj);
return;
}
// Cesium 1.141: root 和 _distanceToCamera 是内部属性,安全访问
const root = (osgbObj.osgbtitles as any)?.root;
if (!Cesium.defined(root)) {
removeClip(viewer, osgbObj);
if (osgbObj.osgbtitles?.clippingPlanes?.enabled) {
osgbObj.osgbtitles.clippingPlanes.enabled = false;
}
return;
}
if (!osgbObj?.osgbtitles?.show) {
removeClip(viewer, osgbObj);
if (osgbObj.osgbtitles?.clippingPlanes?.enabled) {
osgbObj.osgbtitles.clippingPlanes.enabled = false;
}
return;
}
const distance = root._distanceToCamera;
const tilesLoaded = osgbObj.osgbtitles.tilesLoaded;
if (osgbObj.boundary && tilesLoaded) {
if (distance != null && getMinDistanceOfOSGB(viewer) === distance) {
ClipTerrain(viewer, osgbObj);
if (
osgbObj.osgbtitles?.clippingPlanes &&
!osgbObj.osgbtitles.clippingPlanes.enabled
) {
osgbObj.osgbtitles.clippingPlanes.enabled = true;
}
} else {
removeClip(viewer, osgbObj);
if (osgbObj.osgbtitles?.clippingPlanes?.enabled) {
osgbObj.osgbtitles.clippingPlanes.enabled = false;
}
}
}
};
// ==================== 公开 API ====================
/**
* 3D Tileset
*
* Cesium 1.141 使 Cesium3DTileset.fromUrl()
* new Cesium3DTileset({url})
*/
export const LoadOSGB = (viewer: Cesium.Viewer, osgbObj: OSGBItem) => {
if (!osgbObj.url) return;
// 已加载则跳过
if (osgbObj.osgbtitles && !osgbObj.osgbtitles.isDestroyed?.()) return;
// 正在加载中也跳过(防止重复调用 fromUrl
if (osgbObj._loading) return;
osgbObj._loading = true;
// 自增版本号:每次 LoadOSGB 都 +1.then() 里靠版本号判断是否已失效
osgbObj._loadId = (osgbObj._loadId || 0) + 1;
const loadId = osgbObj._loadId;
const url = osgbObj.url.includes('?')
? osgbObj.url + `&token=${getToken()}`
: osgbObj.url + `?token=${getToken()}`;
Cesium.Cesium3DTileset.fromUrl(url, {
maximumScreenSpaceError: Number(osgbObj?.accuracy) || 16,
show: true,
maximumMemoryUsage: 128
})
.then((tileset: Cesium.Cesium3DTileset) => {
// 竞态保护:版本号变了 → 销毁退出
if (
osgbObj._loadId !== loadId ||
!Cesium.defined(viewer) ||
viewer.isDestroyed()
) {
tileset.destroy();
return;
}
if (!Cesium.defined(tileset)) return;
osgbObj._loading = false;
tileset.maximumScreenSpaceError = 32;
// height 向上偏移模型几何自带高程height 只是微调(如 44m
if (osgbObj.height != 0) {
const bs = Cesium.Cartographic.fromCartesian(
tileset.boundingSphere.center
);
const surface = Cesium.Cartesian3.fromRadians(
bs.longitude,
bs.latitude,
0.0
);
const offset = Cesium.Cartesian3.fromRadians(
bs.longitude,
bs.latitude,
osgbObj.height
);
const translation = Cesium.Cartesian3.subtract(
offset,
surface,
new Cesium.Cartesian3()
);
tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation);
}
// 加入场景(匹配旧代码 readyPromise 中 addOSGB
addOSGB(viewer, tileset);
osgbObj.osgbtitles = tileset;
// 始终设置 cartesian3保证 dynamicSetVisible 能计算距离
osgbObj.cartesian3 = tileset.boundingSphere.center.clone();
// boundary 裁切(匹配旧代码 readyPromise 中处理)
if (osgbObj.boundary) {
if (typeof osgbObj.boundary === 'string') {
osgbObj.boundary = JSON.parse(osgbObj.boundary);
}
Clip3DTile(osgbObj.boundary, tileset);
}
// 注册 postRender 动态显隐300ms 节流,匹配旧代码 throttle
let lastTime = 0;
osgbObj.eventListener = viewer.scene.postRender.addEventListener(() => {
if (!Cesium.defined(viewer) || viewer.isDestroyed()) return;
const now = performance.now();
if (now - lastTime < 300) return;
lastTime = now;
if (!getState(osgbObj).timelookat) {
dynamicSetVisible(viewer, osgbObj);
}
dynamicClipTerrain(viewer, osgbObj);
});
})
.catch((e: Error) => {
osgbObj._loading = false;
console.log('加载倾斜摄影报错', e);
});
};
/**
*
* 2D osgbChangeClick
* removeqxsy(osgbTool.ts:233-244) cartesian3
*/
export const removeQxsy = (viewer: Cesium.Viewer, osgbObj: OSGBItem) => {
// 递增版本号:使所有进行中的 fromUrl().then() 失效
osgbObj._loadId = (osgbObj._loadId || 0) + 1;
removeClippingPlanes(viewer);
osgbObj.clippingPlanes = undefined;
osgbObj._loading = false;
osgbObj._switchOff = undefined;
if (typeof osgbObj.eventListener === 'function') {
osgbObj.eventListener();
osgbObj.eventListener = undefined;
}
if (osgbObj.osgbtitles) {
removeOSGB(viewer, osgbObj.osgbtitles);
osgbObj.osgbtitles = undefined;
}
// 注意:不清理 cartesian3匹配旧代码行为
};
/**
*
* -
* - 12km /
* - 12km tileset tileset
* - 2D removeQxsy
*/
export const osgbChangeClick = (
viewer: Cesium.Viewer,
osgbObj: OSGBItem,
checked: boolean
) => {
// 先记录开关状态(无论距离远近)
osgbObj._switchOff = !checked;
// 12km 以外:只记状态不操作 tileset此时 tileset 可能不存在或已销毁)
if (osgbObj.cartesian3) {
const cameraPosition = viewer.camera.position.clone();
const distance = Cesium.Cartesian3.distance(
osgbObj.cartesian3,
cameraPosition
);
if (distance >= 12000) return;
}
if (checked) {
// 开关 ON已有未销毁的 tileset → 直接显示;否则加载
if (osgbObj.osgbtitles && !osgbObj.osgbtitles.isDestroyed?.()) {
osgbObj.osgbtitles.show = true;
} else {
LoadOSGB(viewer, osgbObj);
}
} else {
// 开关 OFF隐藏 tileset移除 terrain 裁切
if (osgbObj.osgbtitles && !osgbObj.osgbtitles.isDestroyed?.()) {
osgbObj.osgbtitles.show = false;
}
removeClip(viewer, osgbObj);
}
};
/**
* location
*/
const parseLocation = (location: any): OSGBLocation | null => {
if (!location) return null;
// 如果已经是对象,直接用
if (typeof location === 'object' && location.destination) {
return location as OSGBLocation;
}
// 如果是字符串,尝试解析
if (typeof location === 'string') {
try {
const jsonString = location.replace(/\n/g, '');
const startIndex = jsonString.indexOf('{');
const endIndex = jsonString.lastIndexOf('}');
if (startIndex === -1 || endIndex === -1) return null;
const extractedData = jsonString.substring(startIndex, endIndex + 1);
const parsed = eval('(' + extractedData + ')');
return {
destination: parsed.destination,
orientation: parsed.orientation,
center: parsed.center
};
} catch {
return null;
}
}
return null;
};
/**
*
* flyTo +
*/
export const osgbLocation = (viewer: Cesium.Viewer, osgbObj: OSGBItem) => {
const state = getState(osgbObj);
clearTimeout(state.timeout);
state.num = 0;
state.time = 0;
state.witetime = 0;
viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
let center = Cesium.Cartesian3.fromDegrees(
100.271729604817,
35.3107902811941,
2800
);
let heading = Cesium.Math.toRadians(0.0);
const pitch = Cesium.Math.toRadians(-30.0);
const range = 2000.0;
const locationInfor = parseLocation(osgbObj.location);
if (!locationInfor) {
console.warn('该模型定位坐标未配置!');
return;
}
// 判断是经纬度还是笛卡尔坐标
const destCoords = locationInfor.destination;
if (destCoords[0] <= 180 && destCoords[0] >= -180) {
state.destination = Cesium.Cartesian3.fromDegrees(
destCoords[0],
destCoords[1],
destCoords[2]
);
} else {
state.destination = new Cesium.Cartesian3(
destCoords[0],
destCoords[1],
destCoords[2]
);
}
state.orientation = locationInfor.orientation;
center = Cesium.Cartesian3.fromDegrees(
locationInfor.center[0],
locationInfor.center[1],
locationInfor.center[2]
);
if (!state.destination || !state.orientation) return;
viewer.camera.cancelFlight();
viewer.camera.flyTo({
destination: state.destination,
orientation: state.orientation,
complete: () => {
state.timeout = setTimeout(() => {
rotate();
}, 2000);
}
});
const rotate = () => {
clearInterval(state.timelookat);
state.timelookat = setInterval(() => {
if (!Cesium.defined(viewer) || viewer.isDestroyed()) {
return;
}
state.witetime += 0.7;
if (state.witetime > 180 && state.witetime < 360) {
state.time += 0.7;
heading = Cesium.Math.toRadians(state.time);
viewer.camera.lookAt(
center,
new Cesium.HeadingPitchRange(heading, pitch, range)
);
} else if (state.witetime > 360) {
viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
clearInterval(state.timelookat);
state.timelookat = null;
viewer.camera.flyTo({
destination: state.destination!,
orientation: state.orientation,
duration: 2
});
return;
}
}, 10);
};
// 点击 body 两次退出旋转
const body = document.getElementsByTagName('body')[0];
body.onclick = function () {
if (viewer && !viewer.isDestroyed() && viewer.camera) {
state.num = state.num + 1;
if (state.num > 1) {
viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
clearInterval(state.timelookat);
clearTimeout(state.timeout);
state.timelookat = null;
}
}
};
};