diff --git a/src/App.vue b/src/App.vue index c3b7c92..9e9adbd 100644 --- a/src/App.vue +++ b/src/App.vue @@ -7,14 +7,20 @@ import { RouterView } from 'vue-router' import { ElLoading } from 'element-plus' import { watch } from 'vue'; import { useViewerStore } from './stores/useViewerStore'; -const loadingInstanve = ElLoading.service({ + + +const loadingOption = { fullscreen: true, text: '正在加载配置相关资源中...' -}) +} + +let loadingInstanve = ElLoading.service(loadingOption) watch(() => useViewerStore().getViewerLoadingCompleted(), (val) => { - if (val == true) { + if (val) { loadingInstanve.close() + } else { + loadingInstanve = ElLoading.service(loadingOption) } }) diff --git a/src/component/map/Map.vue b/src/component/map/Map.vue index cc9ef5d..4e4297c 100644 --- a/src/component/map/Map.vue +++ b/src/component/map/Map.vue @@ -12,7 +12,9 @@ import { CesiumUtilsSingleton } from '@/utils/cesium/CesiumUtils'; import AdministrativeDivision from './AdministrativeDivision.vue'; import { useViewerStore } from '@/stores/useViewerStore'; import { useLoadingInformationStore } from '@/stores/useLoadingInformation'; -import Xian from '@/assets/json/XiAn.json' +import xiAnGeoJSON from '@/assets/json/XiAn.json' +import type { GeoJsonFileType } from '@/types/cesium/GeoJsonFileType'; +import { Color } from 'cesium'; onBeforeMount(() => { // 初始化为false @@ -22,11 +24,21 @@ onBeforeMount(() => { useLoadingInformationStore().resetStatue() }) -onMounted(() => { - CesiumUtilsSingleton.initCesiumViewer({ - containerId: 'map-container' - }) +onMounted(async() => { + await CesiumUtilsSingleton.initCesiumViewer({ + containerId: 'map-container', + mark: { + include: true, + geoJson: xiAnGeoJSON as GeoJsonFileType, + color: Color.BLACK.withAlpha(0.8), + border: { + width: 3 + } + } + },) + useViewerStore().setViewerLoadingCompleted(true) + // 注册全局点击监听器 CesiumUtilsSingleton.clickLayer((pickedObject: any) => { if (pickedObject && pickedObject.id && (typeof pickedObject.id === 'string')) { @@ -49,7 +61,7 @@ onMounted(() => { // 风险点 else if (pickedObject.id.startsWith(config.prefix.riskPointId)) { useLoadingInformationStore().setRiskPointId(id) - }else { + } else { // 重置状态 useLoadingInformationStore().resetStatue() } @@ -59,8 +71,6 @@ onMounted(() => { } }) - // 更新完成状态 - useViewerStore().setViewerLoadingCompleted(true) CesiumUtilsSingleton.viewToTarget(config.defaultPosition as [number, number, number]); }) diff --git a/src/config/config.json b/src/config/config.json index 311d90d..d400b08 100644 --- a/src/config/config.json +++ b/src/config/config.json @@ -12,7 +12,7 @@ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1ZDBjZjAxOS0wMDhhLTRmZjEtYjNmOC1iNmM2ZmY2ZmQ1N2IiLCJpZCI6MjAxMDI1LCJpYXQiOjE3MTAxNTgxNjJ9.mdbJYEzXQkBnHNqpozz7MvZjJ_X9a3JZRGPA-ytGhLI", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJiNjczZTVlMy1kNDEwLTRhZWItYWM0NS1mNjYxMzJjODMwYTQiLCJpZCI6MzIxMzI2LCJpYXQiOjE3NzU2NDU1OTd9._MPcZQsxK1dGPl8IMVhKHV3PIPu4-TaOUgzsUUOP6WE" ], - "defaultPosition": [108.948024, 34.263161, 300000], + "defaultPosition": [108.948024, 34.263161, 250000], "prefix": { "hiddenDangerPointId": "hidden-danger-point-", "riskPointId": "risk-point-" diff --git a/src/types/cesium/CesiumInitOptions.ts b/src/types/cesium/CesiumInitOptions.ts index eccdc90..11ebb54 100644 --- a/src/types/cesium/CesiumInitOptions.ts +++ b/src/types/cesium/CesiumInitOptions.ts @@ -1,3 +1,6 @@ +import type { Color } from "cesium" +import type { GeoJsonFileType } from "./GeoJsonFileType" + /** * Cesium 公共配置选项 * 用于初始化时统一配置 Viewer 参数 @@ -19,4 +22,25 @@ export interface CesiumInitOptions { geocoder?: boolean // 搜索(默认:false) sceneMode?: number // 初始场景模式(默认:3D,可选:2D=1, COLUMBUS_VIEW=2) + + // 遮罩配置 + mark?: { + // 是否包含遮罩,默认false + include?: boolean + // GeoJSON 数据,如果要突出显示某一区域,就传递改值 + geoJson?: GeoJsonFileType + // 孔属于半球,默认东半球 + belongingHemisphere?: 'east' | 'west' + // 遮罩颜色,默认黑色 + color?: Color + // 边框 + border?: { + // 是否显示边框,默认true + show?: boolean + // 边框颜色,默认白色 + color?: Color + // 边框宽度,默认1 + width?: number + } + } } diff --git a/src/types/cesium/GeoJsonFileType.ts b/src/types/cesium/GeoJsonFileType.ts new file mode 100644 index 0000000..034b34e --- /dev/null +++ b/src/types/cesium/GeoJsonFileType.ts @@ -0,0 +1,8 @@ +export interface GeoJsonFileType { + type: "FeatureCollection"; + features: { + geometry: { + coordinates: number[][][][]; + }; + }[]; +} \ No newline at end of file diff --git a/src/utils/cesium/CesiumUtils.ts b/src/utils/cesium/CesiumUtils.ts index 3a5dd36..f0fab9e 100644 --- a/src/utils/cesium/CesiumUtils.ts +++ b/src/utils/cesium/CesiumUtils.ts @@ -3,13 +3,14 @@ import type { EntityOptions } from '@/types/cesium/EntityOptions' import type { PrimitiveOptions } from '@/types/cesium/PrimitiveOptions' import type { LayerConfig } from '@/types/cesium/LayerConfig' import type { CustomizeGeoJsonDataSource, GeoJsonOptions } from '@/types/cesium/GeoJsonOptions' -import { Viewer, Entity, DataSource, ImageryLayer, Primitive, BillboardCollection, Cartesian3, ScreenSpaceEventHandler, ScreenSpaceEventType, Cartesian2, SceneTransforms } from 'cesium' +import { Viewer, Entity, DataSource, ImageryLayer, Primitive, BillboardCollection, Cartesian3, ScreenSpaceEventHandler, ScreenSpaceEventType, Cartesian2, SceneTransforms, Color } from 'cesium' import { CesiumViewerManager } from './CesiumViewerManager' import { EntityManager } from './EntityManager' import { PrimitiveManager } from './PrimitiveManager' import { LayerManager } from './LayerManager' import { GeoJsonManager, type ClearType } from './GeoJsonManager' import { CameraController } from './CameraController' +import type { GeoJsonFileType } from '@/types/cesium/GeoJsonFileType' // 导出 ClearType 类型 export type { ClearType } @@ -36,8 +37,8 @@ export class CesiumUtils { * @param type - 底图类型:0=影像图,1=矢量图(默认 0) * @param tdMapToken - 天地图 Token 数组(可选) */ - initCesiumViewer(options: CesiumInitOptions, type: number = 0, tdMapToken?: string[]): void { - this.#viewerManager.initCesiumViewer(options, type, tdMapToken) + async initCesiumViewer(options: CesiumInitOptions, type: number = 0, tdMapToken?: string[]): Promise { + await this.#viewerManager.initCesiumViewer(options, type, tdMapToken) const viewer = this.#viewerManager.getViewer() if (viewer) { diff --git a/src/utils/cesium/CesiumViewerManager.ts b/src/utils/cesium/CesiumViewerManager.ts index d96f10c..332a9b3 100644 --- a/src/utils/cesium/CesiumViewerManager.ts +++ b/src/utils/cesium/CesiumViewerManager.ts @@ -4,6 +4,15 @@ import { Ion, WebMapTileServiceImageryProvider, ImageryProvider, + PolygonHierarchy, + Cartesian3, + PolygonGeometry, + ArcType, + GeometryInstance, + Color, + Material, + MaterialAppearance, + GroundPrimitive, } from 'cesium' import type { CesiumInitOptions } from '@/types/cesium/CesiumInitOptions' import config from '@/config/config.json' @@ -50,7 +59,6 @@ export class CesiumViewerManager { if (!this.#failedTokens.has(nextIndex)) { this.#currentTokenIndex = nextIndex Ion.defaultAccessToken = tokens[nextIndex] - console.log(`已切换到 Cesium Ion Token #${nextIndex + 1}`) return true } } @@ -65,7 +73,7 @@ export class CesiumViewerManager { * @param type - 底图类型:0=影像图,1=矢量图(默认 0) * @param tdMapToken - 天地图 Token 数组(可选) */ - initCesiumViewer(options: CesiumInitOptions, type: number = 0, tdMapToken?: string[]): void { + async initCesiumViewer(options: CesiumInitOptions, type: number = 0, tdMapToken?: string[]): Promise { const defaultOptions: CesiumInitOptions = { containerId: options.containerId, shouldAnimate: true, @@ -80,9 +88,32 @@ export class CesiumViewerManager { sceneModePicker: false, geocoder: false, sceneMode: SceneMode.SCENE3D, + mark: { + include: false, + belongingHemisphere: 'east', + color: Color.BLACK, + border: { + show: true, + color: Color.WHITE, + width: 1 + } + } + } + + // 合并选项 + const finalOptions: CesiumInitOptions = { + ...defaultOptions, + ...options, + mark: options.mark ? { + ...defaultOptions.mark, + ...options.mark, + border: options.mark.border ? { + ...defaultOptions.mark!.border, + ...options.mark.border + } : defaultOptions.mark!.border + } : defaultOptions.mark } - const finalOptions = { ...defaultOptions, ...options } const container = document.getElementById(finalOptions.containerId) if (!container) { @@ -125,6 +156,11 @@ export class CesiumViewerManager { }) this.#viewer = viewer + + // 是否突出显示指定区域 + if (options.mark?.include) { + await this.#highlight(finalOptions) + } } /** @@ -184,4 +220,131 @@ export class CesiumViewerManager { return [vectorProvider] } } + + /** + * 高亮指定区域 + * @param options - 高亮选项 + */ + async #highlight(options: CesiumInitOptions): Promise { + + if (!this.#viewer) { + throw new Error('请先初始化 Cesium Viewer') + } + + if(!options.mark || !options.mark.geoJson) { + throw new Error('请提供 GeoJSON 数据') + } + + // 解析边界坐标和孔洞位置 + const parseCoordinates = () => { + const holes: PolygonHierarchy[] = []; + const boundaryCoords: number[] = []; + const polygons = options.mark?.geoJson!.features[0].geometry.coordinates; + + polygons!.forEach((polygon, index) => { + const flatCoords: number[] = []; + polygon[0].forEach((point) => { + flatCoords.push(point[0], point[1]); + }); + + // 第一个是多边形外边界 + if (index === 0) { + boundaryCoords.push(...flatCoords); + } + + // 坐标反转(用于挖孔) + const positions = Cartesian3.fromDegreesArray(flatCoords).reverse(); + holes.push(new PolygonHierarchy(positions)); + }); + + return { holes, boundaryCoords }; + }; + + const { holes, boundaryCoords } = parseCoordinates(); + + // 东西半球标准坐标 + const westPositions = Cartesian3.fromDegreesArray([ + -0.00001, 85, -0.00001, -85, -180, -85, -180, 85, -0.00001, 85, + ]); + const eastPositions = Cartesian3.fromDegreesArray([ + 0.00001, 85, 0.00001, -85, 180, -85, 180, 85, 0.00001, 85, + ]); + + // 西半球 + const westOption = { + polygonHierarchy: new PolygonHierarchy(westPositions), + arcType: ArcType.GEODESIC, + }; + if (options.mark.belongingHemisphere === 'west') { + westOption.polygonHierarchy = new PolygonHierarchy(westPositions, holes); + } + const westGeometry = new PolygonGeometry(westOption); + const westInstance = new GeometryInstance({ geometry: westGeometry }); + + // 东半球 + const eastOption = { + polygonHierarchy: new PolygonHierarchy(eastPositions), + arcType: ArcType.GEODESIC, + } + if (options.mark.belongingHemisphere === 'east') { + eastOption.polygonHierarchy = new PolygonHierarchy(eastPositions, holes); + } + const eastGeometry = new PolygonGeometry(eastOption); + const eastInstance = new GeometryInstance({ geometry: eastGeometry }); + + // 添加遮罩 + const maskMaterial = new Material({ + fabric: { + type: "Color", + uniforms: { + color: options.mark.color, + }, + }, + }); + const appearance = new MaterialAppearance({ + material: maskMaterial, + closed: true, + }); + + // 合并渲染 + const globalMask = new GroundPrimitive({ + geometryInstances: [westInstance, eastInstance], + appearance: appearance, + }); + + this.#viewer.scene.primitives.add(globalMask); + + // 添加边界线 + if (options.mark.border?.show) { + const boundaryPositions = Cartesian3.fromDegreesArray(boundaryCoords); + // 闭合边界线 + boundaryPositions.push(boundaryPositions[0]); + + this.#viewer.entities.add({ + id: 'holeLine', + polyline: { + positions: boundaryPositions, + width: options.mark.border.width, + material: options.mark.border.color, + clampToGround: true, + }, + }); + } + + // 等待完成渲染 + return new Promise((resolve) => { + const removeListener = this.#viewer!.scene.postRender.addEventListener(() => { + if (globalMask.ready) { + removeListener(); + resolve(); + } + }); + + // 设置超时保护,避免无限等待 + setTimeout(() => { + removeListener(); + resolve(); + }, 6000); + }); + } } diff --git a/src/views/Index.vue b/src/views/Index.vue index 500a8cd..ce8769a 100644 --- a/src/views/Index.vue +++ b/src/views/Index.vue @@ -6,9 +6,9 @@ @@ -42,7 +42,7 @@ const topNavMap = [ const isActive = (identification: number) => { const targetId = identification.toString(); let currentId = route.query.identification; - if(!currentId) return targetId === '1' + if (!currentId) return targetId === '1' if (Array.isArray(currentId)) currentId = currentId[0]; return currentId === targetId || route.query.identification === targetId; };