From 6395d9ea79414e389f0770cdf5d361a85b9dce6e Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Fri, 24 Jul 2026 18:09:36 +0800 Subject: [PATCH] 3D --- eslint.config.js | 15 +- model/LICENSES.md | 28 + model/aircraft.glb | Bin 0 -> 52736 bytes model/base-station.glb | Bin 0 -> 9560 bytes model/track-point.glb | Bin 0 -> 852 bytes package-lock.json | 604 +++++++++++++++- package.json | 4 + src/App.tsx | 112 ++- src/Data_Source/Data_Source.tsx | 29 +- src/Data_Source/Data_Source_Show.tsx | 8 +- src/Map/Aircraft.tsx | 196 ++---- src/Map/Aircraft_Model.tsx | 91 +++ src/Map/Base_Station.tsx | 10 +- src/Map/Cesium_Map.tsx | 919 +++++++++++++++++++++++++ src/Map/Cesium_Render_Settings.tsx | 310 +++++++++ src/Map/Leaflet_Map.tsx | 28 +- src/Map/Map_Resources.tsx | 37 + src/Map/Map_View.tsx | 62 ++ src/Map/Terrarium_Terrain_Provider.tsx | 163 +++++ src/Map/Terrarium_Terrain_Worker.ts | 76 ++ src/Setting.tsx | 6 +- src/vite-env.d.ts | 2 + tsconfig.json | 1 + vite.config.js | 129 +++- 24 files changed, 2607 insertions(+), 223 deletions(-) create mode 100644 model/LICENSES.md create mode 100644 model/aircraft.glb create mode 100644 model/base-station.glb create mode 100644 model/track-point.glb create mode 100644 src/Map/Aircraft_Model.tsx create mode 100644 src/Map/Cesium_Map.tsx create mode 100644 src/Map/Cesium_Render_Settings.tsx create mode 100644 src/Map/Map_Resources.tsx create mode 100644 src/Map/Map_View.tsx create mode 100644 src/Map/Terrarium_Terrain_Provider.tsx create mode 100644 src/Map/Terrarium_Terrain_Worker.ts create mode 100644 src/vite-env.d.ts diff --git a/eslint.config.js b/eslint.config.js index 79ae4fd..61adb31 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -2,14 +2,16 @@ import js from '@eslint/js' import globals from 'globals' import reactHooks from 'eslint-plugin-react-hooks' import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' export default [ - { ignores: ['dist'] }, + { ignores: ['dist/**', 'wwwroot/**', 'htdocs/**', 'cesium/**', 'node_modules/**'] }, { - files: ['**/*.{js,jsx}'], + files: ['src/**/*.{ts,tsx}'], languageOptions: { ecmaVersion: 2020, globals: globals.browser, + parser: tseslint.parser, parserOptions: { ecmaVersion: 'latest', ecmaFeatures: { jsx: true }, @@ -19,12 +21,21 @@ export default [ plugins: { 'react-hooks': reactHooks, 'react-refresh': reactRefresh, + '@typescript-eslint': tseslint.plugin, }, rules: { ...js.configs.recommended.rules, + ...tseslint.configs.recommended.rules, ...reactHooks.configs.recommended.rules, //'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], 'no-unused-vars': "off", + 'no-empty': "off", + 'no-prototype-builtins': "off", + 'no-unreachable': "off", + '@typescript-eslint/no-unused-vars': "off", + '@typescript-eslint/no-explicit-any': "off", + 'react-hooks/rules-of-hooks': "off", + 'react-hooks/exhaustive-deps': "off", 'react-refresh/only-export-components': [ 'warn', { allowConstantExport: true }, diff --git a/model/LICENSES.md b/model/LICENSES.md new file mode 100644 index 0000000..f9bd826 --- /dev/null +++ b/model/LICENSES.md @@ -0,0 +1,28 @@ +# Model Licenses + +## aircraft.glb + +- Name: ECAP detailed low-poly twin-engine aircraft +- Source: Generated locally for this project from simple geometric primitives +- Author: ECAP_Server project asset generation +- License: CC0-1.0 +- Modified: Replaced the original minimal placeholder on 2026-07-24 +- Notes: The model uses no external texture URL. It includes fuselage, canopy, swept main wings, tail wings, vertical stabilizer, engine nacelles and landing gear. The nose points along +X and the up axis is +Z for Cesium heading/pitch/roll orientation. + +## track-point.glb + +- Name: ECAP low-poly track point marker +- Source: Generated locally for this project from simple geometric primitives +- Author: ECAP_Server project asset generation +- License: CC0-1.0 +- Modified: No +- Notes: The model uses no external texture URL and is used for the selected trajectory point or special business trajectory points. + +## base-station.glb + +- Name: ECAP low-poly base station mast +- Source: Generated locally for this project from simple geometric primitives +- Author: ECAP_Server project asset generation +- License: CC0-1.0 +- Modified: No +- Notes: The model uses no external texture URL. It includes a base plate, vertical mast and antenna panels, with +Z as up for Cesium placement. diff --git a/model/aircraft.glb b/model/aircraft.glb new file mode 100644 index 0000000000000000000000000000000000000000..4b1a4489236bcad8d07f1a8410bcb1f5e1839a27 GIT binary patch literal 52736 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 literal 0 HcmV?d00001 diff --git a/model/base-station.glb b/model/base-station.glb new file mode 100644 index 0000000000000000000000000000000000000000..ce5cfd22c3bea3699af99a947fe40d73298881bf GIT binary patch literal 9560 zcmeI12Xq`)8HLB`&FP8bRCyBmT5OTllE7LiLJkhHJ!C%ejVfC0oI)hMIdS=9Y&r*KLc7D!XkPC*4AE zQ;sS=Ff|bK?b3X`yS4+&LEtjF!${GoR4QXV zqUXGOx;W3DYqa)gO}FH97#Yl;<7;{?@o*gQ^48KGl|Q$i@eY+a7uHPVn1`?Ds4zC? zVQ$O^FW=tZ+nGx3YUdrtmn3d$M?oSH{#?V+|g^w=D&Jx{7#UW4D$+M{*YuQT;r ztLg9FdDo@vLu+PQkJj2ltvxn}eyhbZtNJ%ud$bPwZI1Nd&xOy=MD{HE;9RSt`fU#V zdal*X#M#Vt@9e%z>!^O4d z+GBIPerldd$IUxZpF3 zL%*JDwb%cyc09*8Bk%K_I;!91c>Q=e)Q&TauRqgxucz_#*=XJ?eYf%$O|SJ(6Z%DK zkJdUwat|{Z?y_KUaFQbA1o`^gSW1d+>E=FJ|3p?R@5`k>iis zO!1g?m1jZCwOV_$)|pys54Gt*`m~w!Tdh6*T=>J|CP(@=b3zUWJ<$GK(b}W+%z^SH)A%#{W}3M=UpzM7?@f@Ym+G+tHXP- z;|tZ=`OJs3$!8A6jjr-N7C*Eiy;; zhvOy_Ia)KvjLTXx{P{3*|5dy9+;PQTt5<5>xBEUjzDINwe(RyObB)#>t#zi>+9O)` zQ!n3m)Lh$}9;aTZJOQnHP&%*|jxz&iR8=~oHS?`U^d6q6c&P1MqqRqCovF3a7d_wE{?HuqWw_st%81#IMK9KE~1TI^A-;MX5!+E)&s&Hg-JAh*sq zosn|&+I6z(t#>Hbcrf?wy-VK5L$!}Xm3ugPKOAcuIiCZ3EyCydk@GonKKAo4@GM09 z9uSzLzlYS*5p!)%tPk!r7GVec-!v~){CnCt1{@2H6U}-&I02jpP7=*}GB^b+2B(T< zJq?@=&H!hMW<3j>4bB1Qie_B`&IcEO3q`YD1kMAOfQ!LW&;XVJ7hDSPi-pmw`~jN& zhk=Iz`X3G+0Uik+1!AIEmjn7A4H^OcagYE>kOEDjS(^dJ${ z2l~N!(X3Ad^gjt~0Q6rCo(!G>HiD;$W*q?ZUjv>7=)V?Z!5|m{n?$qb0R1Ci7|_2N z;fMY&H5qmVek>KTQuuO!NVX4 zH0xd9v*2^!Zqcms;2!XK@CDJVUj$zQUk3MzX8j8ID)<`sx@gvKfNz4m;6Bl;-vZwT z-vQqh&H6p?eeeVDL(!~10zU>n0Y4SZ`ZMrz@C)!u(X0#LSK!y+e$lMI0lx*m0}qI1 z{XO^t_#^m}Xx2Z2zkt7jzlmo3JNO6qC-|3W)_;Tlfd2w#k*8S~fuq3D;26=Y$AaU) z@!$l}tS5q#z{%hg(X5NXso*qlx@gujz?tAIaJFdHbHKS^2{=zQ>r!w&xBy%zn)M=Z NF}MUYh-O^|{s&_{GvELK literal 0 HcmV?d00001 diff --git a/model/track-point.glb b/model/track-point.glb new file mode 100644 index 0000000000000000000000000000000000000000..2e92e7e44420172b0d7a61883fb642eb65fdaf50 GIT binary patch literal 852 zcmZuw-EP`26gH)OkLX;xC<(M<+)NC$5>p3NLA^m}a)L=MC3a++wkV3!H`^X<$0lKH z%t?;V`TU(f+sX3f{RJUp*(Kx`D&H3KSq01(ZedixE7wLS2_uMmAyCM;Pftt_90|Kp(OJ4mFgC{?0AS zbCEu&UG^fm@c|QN_(bJO-?P-Wx{7Gni-st|A&q=#2e+1hz3Dc>5$g?aYL_(GZMV3M zzU^dh`2&}k#U`WJw|Q887W}QDc)H&&$DVJu{wy%Xsq?3pf@L~$$J8HuzKv^IHJQ#_ z9rx6CF#OxM@2X@e?wV0;oxTWH)LAqvBvnPJB$sx%FY#n=qWIQnU#Bya v?fCdYi2F`QYk&MaMUnR(yF=VRazXGX!S52~CFzhWM4as6e1%9CWe4ZqanbOJ literal 0 HcmV?d00001 diff --git a/package-lock.json b/package-lock.json index 222f7e6..308b10f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@use-gesture/react": "^10.3.1", "antd": "^5.29.1", "axios": "^1.8.4", + "cesium": "^1.143.0", "echarts": "^6.0.0", "framer-motion": "^12.23.24", "gcoord": "^1.0.7", @@ -40,6 +41,7 @@ "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^15.15.0", + "typescript-eslint": "^8.65.0", "vite": "^6.2.0" } }, @@ -896,6 +898,63 @@ "node": ">=6.9.0" } }, + "node_modules/@cesium/engine": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/@cesium/engine/-/engine-26.1.0.tgz", + "integrity": "sha512-WzeJEcmGD7/htBJ8JEZt95OgZjErPwUHqyAlJY/mCzmDm7hfwpuucLBSUhiynWW3khqBT1C7dhibjb7tdic7oA==", + "license": "Apache-2.0", + "dependencies": { + "@cesium/wasm-splats": "^0.1.0-alpha.2", + "@spz-loader/core": "0.3.1", + "@tweenjs/tween.js": "^25.0.0", + "@zip.js/zip.js": "^2.8.1", + "autolinker": "^4.0.0", + "bitmap-sdf": "^1.0.3", + "dompurify": "^3.3.0", + "draco3d": "^1.5.1", + "earcut": "3.0.2", + "grapheme-splitter": "^1.0.4", + "jsep": "^1.3.8", + "kdbush": "^4.0.1", + "ktx-parse": "^1.0.0", + "lerc": "^2.0.0", + "mersenne-twister": "^1.1.0", + "meshoptimizer": "^1.0.1", + "pako": "^3.0.0", + "protobufjs": "^8.6.5", + "rbush": "^4.0.1", + "topojson-client": "^3.1.0", + "urijs": "^1.19.7" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@cesium/engine/node_modules/earcut": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz", + "integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==", + "license": "ISC" + }, + "node_modules/@cesium/wasm-splats": { + "version": "0.1.0-alpha.2", + "resolved": "https://registry.npmjs.org/@cesium/wasm-splats/-/wasm-splats-0.1.0-alpha.2.tgz", + "integrity": "sha512-t9pMkknv31hhIbLpMa8yPvmqfpvs5UkUjgqlQv9SeO8VerCXOYnyP8/486BDaFrztM0A7FMbRjsXtNeKvqQghA==", + "license": "Apache-2.0" + }, + "node_modules/@cesium/widgets": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/@cesium/widgets/-/widgets-16.1.0.tgz", + "integrity": "sha512-c+jt8F3YIZs2x6+X+HS4q0S9nd2jVcbPVShUmlrDK1s+C7IlEvXceDGrkjivHRElAVCzfaWEEwY1g7Lg5HCGmA==", + "license": "Apache-2.0", + "dependencies": { + "@cesium/engine": "^26.1.0", + "nosleep.js": "^0.12.0" + }, + "engines": { + "node": ">=22.0.0" + } + }, "node_modules/@chenshuai2144/sketch-color": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@chenshuai2144/sketch-color/-/sketch-color-1.0.9.tgz", @@ -1426,9 +1485,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", - "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", "dev": true, "license": "MIT", "dependencies": { @@ -1458,9 +1517,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { @@ -2356,6 +2415,16 @@ "win32" ] }, + "node_modules/@spz-loader/core": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@spz-loader/core/-/core-0.3.1.tgz", + "integrity": "sha512-8qJ1WIBXaJu8HjnJAjYniE0kYcr0kCe5Hp7kDzYiGVvvd7zyrOBwbF5imoW5mvwx1Qba0hxGEK5R9jEoaHKJFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=16", + "pnpm": ">=8" + } + }, "node_modules/@turf/area": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/@turf/area/-/area-7.3.1.tgz", @@ -2427,6 +2496,12 @@ "url": "https://opencollective.com/turf" } }, + "node_modules/@tweenjs/tween.js": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz", + "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==", + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2583,6 +2658,308 @@ "@types/geojson": "*" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/type-utils": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.65.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", + "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", + "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@umijs/route-utils": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@umijs/route-utils/-/route-utils-4.0.1.tgz", @@ -2632,6 +3009,17 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" } }, + "node_modules/@zip.js/zip.js": { + "version": "2.8.34", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.34.tgz", + "integrity": "sha512-+6a3lyqq69rpseLbvDPiVIWsZ/HdTGAAD6afFtug6ECPDGttb2dHnPC6cJgdPofYkzL9OvXizegq+DQVfL2rnA==", + "license": "BSD-3-Clause", + "engines": { + "bun": ">=0.7.0", + "deno": ">=1.0.0", + "node": ">=18.0.0" + } + }, "node_modules/abs-svg-path": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz", @@ -2834,6 +3222,18 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, + "node_modules/autolinker": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-4.1.5.tgz", + "integrity": "sha512-vEfYZPmvVOIuE567XBVCsx8SBgOYtjB2+S1iAaJ+HgH+DNjAcrHem2hmAeC9yaNGWayicv4yR+9UaJlkF3pvtw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + }, + "engines": { + "pnpm": ">=10.10.0" + } + }, "node_modules/axios": { "version": "1.8.4", "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", @@ -2992,6 +3392,25 @@ "element-size": "^1.1.1" } }, + "node_modules/cesium": { + "version": "1.143.0", + "resolved": "https://registry.npmjs.org/cesium/-/cesium-1.143.0.tgz", + "integrity": "sha512-EHz61NBp/2UPYE4iWCZ+YK9Jcb0d2hzRfqkj4DRaUM3cJmA9hE2FTmcPPZ2iL7w+W0YUScI0gc9XeSTXlPIQtA==", + "license": "Apache-2.0", + "workspaces": [ + "packages/engine", + "packages/widgets", + "packages/sandcastle" + ], + "dependencies": { + "@cesium/engine": "^26.1.0", + "@cesium/widgets": "^16.1.0", + "protobufjs": "^8.6.5" + }, + "engines": { + "node": ">=22.0.0" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3443,9 +3862,9 @@ "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -3499,6 +3918,21 @@ "integrity": "sha512-I3JIbrnKPAntNLl1I6TpSQQdQ4AutYzv/sKMFKbepawV/hlH0GmYKhUoOEMd4xqaUHT+Bm0f4127lh5qs1m1tw==", "license": "MIT" }, + "node_modules/dompurify": { + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz", + "integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/draco3d": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz", + "integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==", + "license": "Apache-2.0" + }, "node_modules/draw-svg-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/draw-svg-path/-/draw-svg-path-1.0.0.tgz", @@ -4673,6 +5107,12 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "license": "MIT" + }, "node_modules/grid-index": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/grid-index/-/grid-index-1.1.0.tgz", @@ -4982,6 +5422,15 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsep": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", + "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -5068,6 +5517,12 @@ "node": ">=0.10.0" } }, + "node_modules/ktx-parse": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ktx-parse/-/ktx-parse-1.1.0.tgz", + "integrity": "sha512-mKp3y+FaYgR7mXWAbyyzpa/r1zDWeaunH+INJO4fou3hb45XuNSwar+7llrRyvpMWafxSIi99RNFJ05MHedaJQ==", + "license": "MIT" + }, "node_modules/leaflet": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", @@ -5080,6 +5535,12 @@ "integrity": "sha512-yc97gxLXwbZa+Gk9VCcqI0CkvIBC9oNTTjFsHqq4EQvANrvaboib4UdeQLyTnEqDpaXHCqzwwVIDHtvz2mUiDg==", "license": "MIT" }, + "node_modules/lerc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lerc/-/lerc-2.0.0.tgz", + "integrity": "sha512-7qo1Mq8ZNmaR4USHHm615nEW2lPeeWJ3bTyoqFbd35DLx0LUH7C6ptt5FDCTAlbIzs3+WKrk5SkJvw8AFDE2hg==", + "license": "Apache-2.0" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -5126,6 +5587,12 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "license": "MIT" }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -5314,6 +5781,18 @@ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-4.0.3.tgz", "integrity": "sha512-QmpUu4KqDmX0plH4u+tf0riMc1KHE1+lw95cMrLlXQAFOx/xnBtwhZ52XJxd9X2O6kwKBqX32kmhbhlobD0cuw==" }, + "node_modules/mersenne-twister": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mersenne-twister/-/mersenne-twister-1.1.0.tgz", + "integrity": "sha512-mUYWsMKNrm4lfygPkL3OfGzOPTR2DBlTkBNHM//F6hGp8cLThY897crAlk3/Jo17LEOOjQUrNAx6DvgO77QJkA==", + "license": "MIT" + }, + "node_modules/meshoptimizer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.2.0.tgz", + "integrity": "sha512-davRZeIJbxJrE24cwQle7ZDsxjdk/OphNOV83oX+efQinyoHY9Jcyz3MHbaoG0qySZajldGztNZ1RN/T19PZsg==", + "license": "MIT" + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -5490,6 +5969,12 @@ "integrity": "sha512-1/kmYej2iedi5+ROxkRESL/pI02pkg0OBnaR4hJkSIX6+ORzepwbuUXfrdZaPjysTsJInj0Rj5NuX027+dMBvA==", "license": "MIT" }, + "node_modules/nosleep.js": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/nosleep.js/-/nosleep.js-0.12.0.tgz", + "integrity": "sha512-9d1HbpKLh3sdWlhXMhU6MMH+wQzKkrgfRkYV0EBdvt99YJfj0ilCJrWRDYG2130Tm4GXbEoTCx5b34JSaP+HhA==", + "license": "MIT" + }, "node_modules/number-is-integer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-integer/-/number-is-integer-1.0.1.tgz", @@ -5569,6 +6054,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pako": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pako/-/pako-3.0.1.tgz", + "integrity": "sha512-GupotUUI0mlhugKjUs4bjOwLt3nrehy9Ys2dxC0GtgVef5cnKggkDMmf2bq2poCCuVXopWPmqsc9VDT2iJUy+w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -5836,6 +6337,18 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/protobufjs": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.7.1.tgz", + "integrity": "sha512-agdGHrXNTv0IrYscJPDou/PlEJk1c/hBZ9o/B5NH2i/nSPtPqacNxzgwf1CebXxFMjMrZH5sqv9uQuw96aGt/A==", + "license": "BSD-3-Clause", + "dependencies": { + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/protocol-buffers-schema": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", @@ -5873,6 +6386,21 @@ "performance-now": "^2.1.0" } }, + "node_modules/rbush": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-4.0.1.tgz", + "integrity": "sha512-IP0UpfeWQujYC8Jg162rMNc01Rf0gWMMAb2Uxus/Q0qOFw4lCcq6ZnQEZwUoJqWyUGJ9th7JjwI4yIWo+uvoAQ==", + "license": "MIT", + "dependencies": { + "quickselect": "^3.0.0" + } + }, + "node_modules/rbush/node_modules/quickselect": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", + "license": "ISC" + }, "node_modules/rc-cascader": { "version": "3.34.0", "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.34.0.tgz", @@ -7212,6 +7740,19 @@ "topoquantize": "bin/topoquantize" } }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -7257,6 +7798,45 @@ "dup": "^1.0.0" } }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz", + "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.65.0", + "@typescript-eslint/parser": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/unquote": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", @@ -7310,6 +7890,12 @@ "punycode": "^2.1.0" } }, + "node_modules/urijs": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", + "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==", + "license": "MIT" + }, "node_modules/use-sync-external-store": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", diff --git a/package.json b/package.json index ae14646..2f483dc 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,8 @@ "dev": "vite", "build": "vite build", "lint": "eslint .", + "lint:src": "eslint \"src/**/*.{ts,tsx}\"", + "typecheck": "tsc --noEmit", "preview": "vite preview" }, "dependencies": { @@ -17,6 +19,7 @@ "@use-gesture/react": "^10.3.1", "antd": "^5.29.1", "axios": "^1.8.4", + "cesium": "^1.143.0", "echarts": "^6.0.0", "framer-motion": "^12.23.24", "gcoord": "^1.0.7", @@ -42,6 +45,7 @@ "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^15.15.0", + "typescript-eslint": "^8.65.0", "vite": "^6.2.0" } } diff --git a/src/App.tsx b/src/App.tsx index 917bf0a..d698001 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -22,6 +22,7 @@ import axios from "axios"; import { baseURL } from "./Global.tsx"; import L from 'leaflet'; +const Cesium_Map_View = React.lazy(() => import("./Map/Cesium_Map.tsx")); delete (L.Icon.Default.prototype as any)._getIconUrl; @@ -56,10 +57,15 @@ class App extends enhance.Base { // page 是我自定义的 this.menuItems = [ { - label: '地图选项', + label: '2D地图', icon: , key: '/map', }, + { + label: '3D地图', + icon: , + key: '/map3d', + }, { label: '设置', icon: , @@ -107,7 +113,8 @@ class App extends enhance.Base { this.navigate = useNavigate(); this.location = useLocation(); useEffect(() => { - this.selected_key = this.location?.pathname || '/map'; + const path = this.location?.pathname || '/map'; + this.selected_key = path.startsWith('/ui/') ? path.substring(3) : path; this.flush() }, [this.location]); }; @@ -156,46 +163,57 @@ class App extends enhance.Base { height: '100vh' }}> - ({ +
+ ({ key: item.key, icon: item.icon, label: item.label - })), - // 2. 原有版本号固定菜单项(右侧对齐、禁用、自定义样式) - { - key: "version", - label: ( - - {`开机时间:${this.formatTimestamp(this.start_device_time)} 版本:${this.version}`} - - ), - disabled: true, - style: { - marginLeft: 'auto', - paddingRight: '20px', - cursor: 'none', - } - } - ]} - /> + }))} + /> +
+ {`开机时间:${this.formatTimestamp(this.start_device_time)} 版本:${this.version}`} +
+
- + 地图加载中...}> + + + + )} + /> + + 地图加载中...}> + + )} /> @@ -205,6 +223,32 @@ class App extends enhance.Base { )} /> + + )} + /> + + 3D地图加载中...}> + + + + )} + /> + + 3D地图加载中...}> + + + + )} + /> { - if (value) { + if (value !== null) { ds.lat = value this.flush(); } @@ -116,7 +116,7 @@ export class Data_Source_Show extends enhance.Base { placeholder="请输入经度" value={ds.lon} onChange={(value: number | null) => { - if (value) { + if (value !== null) { ds.lon = value this.flush(); } @@ -125,7 +125,7 @@ export class Data_Source_Show extends enhance.Base { { - if (value) { + if (value !== null) { ds.alt = value this.flush(); } @@ -136,7 +136,7 @@ export class Data_Source_Show extends enhance.Base { placeholder="请输入显示大小" value={ds.aircraft_pixel_size} onChange={(value: number | null) => { - if (value) { + if (value !== null) { ds.aircraft_pixel_size = value; // console.log("ds.aircraft_pixel_size", ds.aircraft_pixel_size) diff --git a/src/Map/Aircraft.tsx b/src/Map/Aircraft.tsx index 7678629..b465013 100644 --- a/src/Map/Aircraft.tsx +++ b/src/Map/Aircraft.tsx @@ -5,7 +5,7 @@ import {baseURL, G, StateHolder} from "../Global.js"; import {lightenColor} from "../Global.tsx"; import axios from "axios"; import {Data_Source} from "../Data_Source/Data_Source.tsx"; - +import {Aircraft_Model, Aircraft_Status, get_aircraft_angle} from "./Aircraft_Model.tsx"; // https://leafletjs.cn/reference.html#marker // https://leafletjs.cn/reference.html#circlemarker @@ -186,21 +186,6 @@ class Aircraft_Track_Path extends Polyline { } -function getAngle(p1: L.LatLng, p2: L.LatLng) { - var angle = Math.atan2(p2.lng - p1.lng, p2.lat - p1.lat) * 180 / Math.PI; - return angle; -} - -export enum Aircraft_Status { - Level = 1, // 平飞 - Descending = 2, // 下降 - Ascending = 3, // 上升 - Lost = 4, // 失联 - Need_Init = 5, // 未初始化 -} - - - export class Aircraft { pos_marker: Aircraft_Marker = new Aircraft_Marker([0.0, 0.0]) icao: string @@ -211,6 +196,7 @@ export class Aircraft { track_path: Aircraft_Track_Path = new Aircraft_Track_Path(); track_point_list: Aircraft_Track_Point[] = [] data_list: any[] = []; + data_model: Aircraft_Model; status = new StateHolder(Aircraft_Status.Need_Init); angle = new StateHolder(0); @@ -408,6 +394,7 @@ export class Aircraft { constructor(icao: string, ds: Data_Source) { this.icao = icao + this.data_model = new Aircraft_Model(ds.key, icao) // this.pos_marker.bindPopup("没有数据也不能删除,否则不弹出!"); // this.track_path.setColor(lightenColor(this.color)); this.data_source = ds; @@ -439,7 +426,7 @@ export class Aircraft { if (list.length >= 2) { let second_last_data = list[list.length - 2]; let second_last_pos = L.latLng(second_last_data.lat, second_last_data.lon, second_last_data.alt); - let angle = getAngle(second_last_pos, last_pos); + let angle = get_aircraft_angle(second_last_pos, last_pos); this.angle.set(angle); // @ts-ignore this.pos_marker.setRotationAngle(this.angle); @@ -450,78 +437,14 @@ export class Aircraft { refresh_from_change_list(item) { - // === 1. 位置更新 === - + const previous_alt = this.data_model.status === Aircraft_Status.Need_Init ? undefined : this.data_model.alt + this.data_model.update_from_change(item, previous_alt) this.set_pos(item.latitude, item.longitude, item.altitude); - this.set_angle(item.track) - - - let vs : number | null = item.vert_speed; - if(vs){ - if(vs == 0){ - this.status.set(Aircraft_Status.Level); - } else if(vs > 0){ - this.status.set(Aircraft_Status.Ascending); - } else {vs < 0 }{ - this.status.set(Aircraft_Status.Descending); - } - } else { - // === 2. 状态判定 === - const ALT_THRESHOLD = 5; // 米,防抖 - - if (item.lost === true) { - // 如果后端/数据源明确给了失联标记 - this.status.set(Aircraft_Status.Lost); - } else if (this.data_list.length >= 1) { - const last = this.data_list[this.data_list.length - 1]; - const deltaAlt = item.altitude - last.altitude; - - if (deltaAlt > ALT_THRESHOLD) { - this.status.set(Aircraft_Status.Ascending); - } else if (deltaAlt < -ALT_THRESHOLD) { - this.status.set(Aircraft_Status.Descending); - } else { - this.status.set(Aircraft_Status.Level); - } - } else { - // 第一帧数据 - this.status.set(Aircraft_Status.Level); - } - - } - - - // === 3. UI 刷新 === + this.set_angle(this.data_model.heading) + this.status.set(this.data_model.status) this.refresh(); - - // === 4. 弹窗内容(带状态) === - const fly_status = { - [Aircraft_Status.Level]: '平飞', - [Aircraft_Status.Descending]: '下降', - [Aircraft_Status.Ascending]: '上升', - [Aircraft_Status.Lost]: '失联', - }[this.status.cur]; - - - this.call_sign = item.call_sign - this.fly_status = fly_status - - // 飞机刷新 markerPopup - // const markerPopup = L.popup({ - // content: - // "icao: " + item.icao + - // "
航班号: " + item.call_sign + - // "
状态: " + statusText, - // maxWidth: 300, - // minWidth: 50, - // autoPan: true, - // autoPanPadding: [5, 5], - // closeButton: true, - // closeOnClick: true, - // className: "custom-popup" - // }); - // - // this.pos_marker.bindPopup(markerPopup); + this.call_sign = this.data_model.call_sign + this.fly_status = this.data_model.fly_status } set_pos(lat: number, lon: number, alt : number) { @@ -529,11 +452,11 @@ export class Aircraft { this.pos = pos; let old_pos = this.pos_marker.getLatLng(); if (pos.lat != old_pos.lat || pos.lng != old_pos.lng) { - let angle = getAngle(old_pos, pos); - this.set_angle(angle) - this.pos_marker.setLatLng(pos); + let angle = get_aircraft_angle(old_pos, pos); + this.set_angle(angle) + this.pos_marker.setLatLng(pos); + } } - } /*{ "icao": "780FBD", @@ -562,58 +485,49 @@ export class Aircraft { } ] }*/ - append_path() { - let map = app.leaflet_map.map; - axios.post(`${baseURL}/get_aircraft_track_list`, { + async load_track_data() { + const res = await axios.post(`${baseURL}/get_aircraft_track_list`, { data_source_key: this.data_source.key, icao: this.icao, last_size: this.last_size - }).then(res => { - if(res.data){ - let list = res.data.list; - let end_seq = res.data.end_seq; - let sta_seq = res.data.sta_seq; - let size = res.data.size; - let old_seq = this.last_size; - // end_seq 34 - // sta_seq 34 - // size 0 - // list.size: 0 - // this.data_list.length: 34 + }) + if (!res.data) { + return [] + } + let list = res.data.list; + let end_seq = res.data.end_seq; + let sta_seq = res.data.sta_seq; + let old_seq = this.last_size; + if (sta_seq != old_seq) { + return [] + } + this.last_size = end_seq; + this.data_model.append_track_points(list) + this.data_list.push(...list); + return list + } - // 简单判断 不是那次请求过来的 直接舍弃 - if(sta_seq != old_seq){ - return; - } - this.last_size = end_seq; - - - - - // console.log('old_seq', old_seq) - // console.log('end_seq', end_seq) - // console.log('sta_seq', sta_seq) - // console.log('size', size) - // console.log("list.size: ", res.data.list.length); - // console.log("this.data_list.length: ", this.data_list.length); - // console.log("res.data: ", res.data); - - - - list.forEach((data, i) => { - let point = new Aircraft_Track_Point(data); - point.setColor(lightenColor(this.data_source.color)); + append_path() { + let map = app.leaflet_map.map; + this.load_track_data().then(list => { + list.forEach((data, i) => { + let point = new Aircraft_Track_Point(data); + point.setColor(lightenColor(this.data_source.color)); + if (map) { point.addTo(map); - this.track_point_list.push(point) - this.track_path.addLatLng(point.getLatLng()); - }); - this.track_point_list.forEach((data, i) => { + } + this.track_point_list.push(point) + this.track_path.addLatLng(point.getLatLng()); + }); + this.track_point_list.forEach((data, i) => { + if (map) { data.addTo(map) - }); - this.data_list.push(...list); + } + }); + if (map) { this.track_path.addTo(map) - this.refresh_pos_by_track(); } + this.refresh_pos_by_track(); }) } @@ -633,18 +547,18 @@ export class Aircraft { } refresh() { + const map = app.leaflet_map?.map; if (this.data_source.aircraft_show) { //console.log("aircraft refresh show") - if (app.leaflet_map){ + if (map){ // app.leaflet_map.map.removeLayer(this.pos_marker); - if(app.leaflet_map.map){ - this.pos_marker.addTo(app.leaflet_map.map); - } - + this.pos_marker.addTo(map); } } else { //console.log("aircraft refresh hide") - app.leaflet_map.map.removeLayer(this.pos_marker); + if (map) { + map.removeLayer(this.pos_marker); + } this.hide_path(); } if(this.need_refresh()){ diff --git a/src/Map/Aircraft_Model.tsx b/src/Map/Aircraft_Model.tsx new file mode 100644 index 0000000..2589ce2 --- /dev/null +++ b/src/Map/Aircraft_Model.tsx @@ -0,0 +1,91 @@ +export enum Aircraft_Status { + Level = 1, + Descending = 2, + Ascending = 3, + Lost = 4, + Need_Init = 5, +} +export type Aircraft_Business_Properties = Record +export type Aircraft_Track_Point_Model = { + lat: number + lon: number + alt: number + utc?: number + timestamp?: number + properties: Aircraft_Business_Properties +} +export class Aircraft_Model { + data_source_key: string = "" + icao: string = "" + call_sign: string = "" + fly_status: string = "" + lat: number = 0 + lon: number = 0 + alt: number = 0 + heading: number = 0 + pitch?: number + roll?: number + timestamp?: number + status: Aircraft_Status = Aircraft_Status.Need_Init + properties: Aircraft_Business_Properties = {} + track_points: Aircraft_Track_Point_Model[] = [] + constructor(data_source_key: string, icao: string) { + this.data_source_key = data_source_key + this.icao = icao + } + update_from_change(item: any, previous_alt?: number) { + this.lat = Number(item.latitude) + this.lon = Number(item.longitude) + this.alt = Number(item.altitude || 0) + this.heading = Number(item.track || this.heading || 0) + this.pitch = item.pitch ?? item.pitch_angle ?? this.pitch + this.roll = item.roll ?? item.roll_angle ?? this.roll + this.timestamp = item.utc ?? item.timestamp ?? item.last_seen ?? this.timestamp + this.call_sign = item.call_sign ?? this.call_sign + this.status = resolve_aircraft_status(item, previous_alt) + this.fly_status = aircraft_status_text(this.status) + const { latitude, longitude, altitude, track, pitch, pitch_angle, roll, roll_angle, call_sign, ...rest } = item + this.properties = rest + } + append_track_points(list: any[]) { + for (const item of list) { + const { lat, lon, alt, utc, timestamp, ...rest } = item + this.track_points.push({ + lat: Number(lat), + lon: Number(lon), + alt: Number(alt || 0), + utc, + timestamp: timestamp ?? utc, + properties: rest + }) + } + } +} +export function resolve_aircraft_status(item: any, previous_alt?: number): Aircraft_Status { + const vert_speed = item.vert_speed + if (typeof vert_speed === "number") { + if (vert_speed === 0) return Aircraft_Status.Level + return vert_speed > 0 ? Aircraft_Status.Ascending : Aircraft_Status.Descending + } + if (item.lost === true) { + return Aircraft_Status.Lost + } + if (typeof previous_alt === "number") { + const delta = Number(item.altitude || 0) - previous_alt + if (delta > 5) return Aircraft_Status.Ascending + if (delta < -5) return Aircraft_Status.Descending + } + return Aircraft_Status.Level +} +export function aircraft_status_text(status: Aircraft_Status): string { + return { + [Aircraft_Status.Level]: "平飞", + [Aircraft_Status.Descending]: "下降", + [Aircraft_Status.Ascending]: "上升", + [Aircraft_Status.Lost]: "失联", + [Aircraft_Status.Need_Init]: "未初始化", + }[status] +} +export function get_aircraft_angle(p1: { lat: number, lng: number }, p2: { lat: number, lng: number }) { + return Math.atan2(p2.lng - p1.lng, p2.lat - p1.lat) * 180 / Math.PI +} diff --git a/src/Map/Base_Station.tsx b/src/Map/Base_Station.tsx index ba60daa..f0190b4 100644 --- a/src/Map/Base_Station.tsx +++ b/src/Map/Base_Station.tsx @@ -5,6 +5,7 @@ import {Data_Source} from "../Data_Source/Data_Source.tsx"; export class Base_Station { pos: L.LatLng | null = null + has_valid_position: boolean = false max_plane_pos: L.LatLng | null = null circle: L.Circle = new L.Circle([0, 0], { color: 'blue', @@ -141,7 +142,14 @@ export class Base_Station { this.show_max_distance.remove(); } - set_pos(lat: number, lon: number) { + set_pos(lat: number, lon: number, has_valid_position: boolean = true) { + this.has_valid_position = has_valid_position; + if (!has_valid_position) { + this.pos = null; + this.circle.setRadius(0); + this.show_max_distance.remove(); + return; + } this.pos = new L.LatLng(lat, lon); this.that.setLatLng(this.pos); this.circle.setLatLng(this.pos) diff --git a/src/Map/Cesium_Map.tsx b/src/Map/Cesium_Map.tsx new file mode 100644 index 0000000..41947c6 --- /dev/null +++ b/src/Map/Cesium_Map.tsx @@ -0,0 +1,919 @@ +import "cesium/Build/Cesium/Widgets/widgets.css"; +import * as Cesium from "cesium"; +import React from "react"; +import enhance from "../core/enhance.tsx"; +import {app} from "../App.tsx"; +import {Data_Source} from "../Data_Source/Data_Source.tsx"; +import {Aircraft} from "./Aircraft.tsx"; +import {Aircraft_Model, Aircraft_Track_Point_Model} from "./Aircraft_Model.tsx"; +import {Button, message, Segmented, Select, Space, Tooltip} from "antd"; +import {AimOutlined, EyeOutlined, SaveOutlined} from "@ant-design/icons"; +import { + load_map_resources, + url_template_for_y_axis, + type Map_Imagery_Source_Metadata, + type Map_Resources_Metadata, + type Map_Tile_Metadata +} from "./Map_Resources.tsx"; +import {Terrarium_Terrain_Provider} from "./Terrarium_Terrain_Provider.tsx"; +import { + cesium_graphics_config_event, + default_cesium_graphics_config, + load_cesium_graphics_config, + normalize_cesium_graphics_config, + type Cesium_Graphics_Config +} from "./Cesium_Render_Settings.tsx"; +import { + load_map_view_config, + save_map_view_config, + type Map_Camera_View, + type Map_View_Config, + type Scene_Mode_Key +} from "./Map_View.tsx"; + +type Aircraft_Entity_Record = { + aircraft: Cesium.Entity + track: Cesium.Entity + point_entities: Cesium.Entity[] + point_start_index: number + track_positions: Cesium.Cartesian3[] + track_positions_key: string +} +type Base_Station_Entity_Record = { + model: Cesium.Entity + hemisphere: Cesium.Entity +} +type Aircraft_Position = { + lon: number + lat: number + alt: number +} +type Performance_Info = { + fps: number + frame_ms: number + cpu_pressure: number + js_heap_mb: number | null + gpu_renderer: string + tile_state: string +} +const aircraft_model_uri = "/ui/model/aircraft.glb"; +const track_point_model_uri = "/ui/model/track-point.glb"; +const base_station_model_uri = "/ui/model/base-station.glb"; +const aircraft_model_heading_offset = Cesium.Math.toRadians(180); +const aircraft_model_roll_offset = Cesium.Math.toRadians(-90); +const default_camera_rectangle = Cesium.Rectangle.fromDegrees(119.98, 36.42, 122.56, 38.40); +export class Cesium_Map extends enhance.Base { + viewer: Cesium.Viewer | null = null; + container_id = "cesium-map"; + refresh_timer: number | null = null; + entity_map: Map = new Map(); + base_station_entity_map: Map = new Map(); + selected_aircraft: Aircraft | null = null; + tracking: boolean = false; + mounted: boolean = false; + map_resources: Map_Resources_Metadata | null = null; + terrain_provider: Terrarium_Terrain_Provider | null = null; + map_view_config: Map_View_Config | null = null; + scene_mode: Scene_Mode_Key = "3d"; + current_imagery_key: string = ""; + graphics_config: Cesium_Graphics_Config = {...default_cesium_graphics_config}; + graphics_config_listener: ((event: Event) => void) | null = null; + terrain_signature: string = ""; + performance_timer: number | null = null; + post_render_listener: (() => void) | null = null; + performance_frame_count = 0; + performance_frame_time_total = 0; + performance_last_render_time = 0; + performance_long_task_total = 0; + performance_observer: PerformanceObserver | null = null; + performance_info: Performance_Info = {fps: 0, frame_ms: 0, cpu_pressure: 0, js_heap_mb: null, gpu_renderer: "", tile_state: ""}; + max_track_points_per_aircraft = 1000; + constructor(scene_mode: Scene_Mode_Key = "3d") { + super(); + this.scene_mode = scene_mode; + } + on_mount() { + this.mounted = true; + this.graphics_config_listener = (event: Event) => { + const custom_event = event as CustomEvent; + this.graphics_config = normalize_cesium_graphics_config(custom_event.detail); + this.apply_graphics_config(); + this.update_terrain_provider(); + this.flush(); + }; + window.addEventListener(cesium_graphics_config_event, this.graphics_config_listener); + this.load_map(); + this.refresh_timer = window.setInterval(() => { + this.refresh_data_sources(); + }, 1000); + this.refresh_data_sources(); + } + on_un_mount() { + this.mounted = false; + if (this.refresh_timer) { + window.clearInterval(this.refresh_timer); + this.refresh_timer = null; + } + if (this.graphics_config_listener) { + window.removeEventListener(cesium_graphics_config_event, this.graphics_config_listener); + this.graphics_config_listener = null; + } + if (this.performance_timer) { + window.clearInterval(this.performance_timer); + this.performance_timer = null; + } + this.performance_observer?.disconnect(); + this.performance_observer = null; + if (this.viewer && this.post_render_listener) { + this.viewer.scene.postRender.removeEventListener(this.post_render_listener); + this.post_render_listener = null; + } + this.entity_map.clear(); + this.base_station_entity_map.clear(); + this.terrain_provider?.destroy(); + this.terrain_provider = null; + if (this.viewer) { + this.viewer.destroy(); + this.viewer = null; + } + } + list(): Data_Source[] { + if (!app.setting) return []; + return app.setting.data_source_config.list.list; + } + async load_map() { + const [resources, graphics_config, view_config] = await Promise.all([load_map_resources(), load_cesium_graphics_config(), load_map_view_config()]); + if (!this.mounted) return; + this.map_resources = resources; + this.graphics_config = graphics_config; + this.map_view_config = view_config; + this.scene_mode = view_config.scene_mode; + this.current_imagery_key = view_config.current_imagery_key || resources.current_imagery_key; + Cesium.Ion.defaultAccessToken = ""; + const imagery_provider = this.create_imagery_provider(this.current_imagery_resource()); + const terrain_provider = this.create_terrain_provider(); + this.viewer = new Cesium.Viewer(this.container_id, { + baseLayerPicker: false, + geocoder: false, + homeButton: false, + sceneModePicker: false, + timeline: false, + animation: false, + navigationHelpButton: false, + fullscreenButton: false, + infoBox: true, + selectionIndicator: false, + sceneMode: this.to_cesium_scene_mode(this.scene_mode), + baseLayer: new Cesium.ImageryLayer(imagery_provider), + terrainProvider: terrain_provider, + requestRenderMode: true, + maximumRenderTimeChange: Number.POSITIVE_INFINITY + }); + this.viewer.scene.globe.depthTestAgainstTerrain = false; + this.apply_graphics_config(); + this.restore_camera_view(); + this.viewer.screenSpaceEventHandler.setInputAction((movement: any) => { + this.handle_click(movement.position); + }, Cesium.ScreenSpaceEventType.LEFT_CLICK); + this.viewer.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK); + this.start_performance_monitor(); + this.sync_data_sources(); + } + current_imagery_resource(): Map_Tile_Metadata { + const source = this.current_imagery_source(); + return source.resource; + } + current_imagery_source(): Map_Imagery_Source_Metadata { + const sources = this.map_resources?.imagery_sources || []; + const source = sources.find(item => item.key === this.current_imagery_key) || sources[0]; + if (source) return source; + return {key: "imagery", name: "本地卫星影像", resource: this.map_resources!.imagery}; + } + create_imagery_provider(resource: Map_Tile_Metadata): Cesium.UrlTemplateImageryProvider { + return new Cesium.UrlTemplateImageryProvider({ + url: url_template_for_y_axis(resource), + minimumLevel: resource.minimum_level, + maximumLevel: resource.maximum_level, + tileWidth: resource.tile_size, + tileHeight: resource.tile_size, + tilingScheme: new Cesium.WebMercatorTilingScheme(), + credit: resource.credit ? new Cesium.Credit(resource.credit) : undefined + }); + } + change_imagery_source(key: string) { + if (!this.viewer || !this.map_resources) return; + this.current_imagery_key = key; + this.viewer.imageryLayers.removeAll(); + this.viewer.imageryLayers.addImageryProvider(this.create_imagery_provider(this.current_imagery_resource())); + this.viewer.scene.requestRender(); + this.flush(); + } + async save_current_map_view() { + if (!this.viewer) return; + this.map_view_config = await save_map_view_config({ + current_imagery_key: this.current_imagery_key, + scene_mode: this.scene_mode, + camera: this.current_camera_view() + }); + this.current_imagery_key = this.map_view_config.current_imagery_key; + this.scene_mode = this.map_view_config.scene_mode; + message.success("地图视图已保存"); + this.flush(); + } + change_scene_mode(mode: Scene_Mode_Key) { + if (!this.viewer) return; + this.scene_mode = mode; + this.update_terrain_provider(); + if (mode === "2d") { + this.viewer.scene.morphTo2D(0.5); + } + else if (mode === "2.5d") { + this.viewer.scene.morphToColumbusView(0.5); + } + else { + this.viewer.scene.morphTo3D(0.5); + } + this.flush(); + } + to_cesium_scene_mode(mode: Scene_Mode_Key): Cesium.SceneMode { + if (mode === "2d") return Cesium.SceneMode.SCENE2D; + if (mode === "2.5d") return Cesium.SceneMode.COLUMBUS_VIEW; + return Cesium.SceneMode.SCENE3D; + } + should_use_terrarium_terrain(): boolean { + return this.scene_mode === "3d" && this.graphics_config.terrainEnabledIn3D && Boolean(this.map_resources); + } + effective_terrain_maximum_level(): number | undefined { + if (!this.map_resources || !this.graphics_config.terrainLimitMaximumLevel) return undefined; + return Math.min(this.graphics_config.terrainMaximumLevel, this.map_resources.terrain.maximum_level); + } + current_terrain_signature(): string { + if (!this.should_use_terrarium_terrain()) return "ellipsoid"; + const maximum_level = this.effective_terrain_maximum_level(); + return `terrarium:${maximum_level === undefined ? "unlimited" : maximum_level}:${this.graphics_config.terrainCacheTiles}`; + } + create_terrain_provider(): Cesium.TerrainProvider { + this.terrain_provider = null; + this.terrain_signature = this.current_terrain_signature(); + if (!this.should_use_terrarium_terrain() || !this.map_resources) { + return new Cesium.EllipsoidTerrainProvider(); + } + this.terrain_provider = new Terrarium_Terrain_Provider(this.map_resources.terrain, { + maximum_level: this.effective_terrain_maximum_level(), + max_cached_height_tiles: this.graphics_config.terrainCacheTiles + }); + return this.terrain_provider.provider; + } + update_terrain_provider() { + if (!this.viewer || !this.map_resources) return; + const signature = this.current_terrain_signature(); + if (signature === this.terrain_signature) return; + const old_terrain_provider = this.terrain_provider; + this.viewer.terrainProvider = this.create_terrain_provider(); + if (old_terrain_provider && old_terrain_provider !== this.terrain_provider) { + old_terrain_provider.destroy(); + } + this.viewer.scene.requestRender(); + } + apply_graphics_config() { + if (!this.viewer) return; + const config = this.graphics_config; + this.viewer.scene.debugShowFramesPerSecond = false; + this.viewer.targetFrameRate = config.targetFrameRate; + this.viewer.useBrowserRecommendedResolution = config.useBrowserRecommendedResolution; + this.viewer.resolutionScale = config.resolutionScale; + this.viewer.scene.msaaSamples = config.msaaSamples; + this.viewer.scene.postProcessStages.fxaa.enabled = config.fxaa; + this.viewer.shadows = config.shadows; + this.viewer.scene.globe.enableLighting = config.enableLighting; + this.viewer.scene.globe.maximumScreenSpaceError = config.maximumScreenSpaceError; + this.viewer.scene.verticalExaggeration = config.terrainExaggeration; + this.viewer.scene.verticalExaggerationRelativeHeight = 0.0; + } + start_performance_monitor() { + if (!this.viewer || this.performance_timer) return; + this.performance_info.gpu_renderer = this.read_gpu_renderer(); + this.post_render_listener = () => this.capture_performance_frame(); + this.viewer.scene.postRender.addEventListener(this.post_render_listener); + this.performance_timer = window.setInterval(() => this.flush_performance_info(), 1000); + try { + this.performance_observer = new PerformanceObserver((list) => { + for (const entry of list.getEntries()) { + this.performance_long_task_total += entry.duration; + } + }); + this.performance_observer.observe({entryTypes: ["longtask"]}); + } catch { + this.performance_observer = null; + } + } + capture_performance_frame() { + const now = performance.now(); + if (this.performance_last_render_time > 0) { + this.performance_frame_time_total += now - this.performance_last_render_time; + } + this.performance_last_render_time = now; + this.performance_frame_count++; + } + flush_performance_info() { + const frame_count = this.performance_frame_count; + const frame_ms = frame_count > 1 ? this.performance_frame_time_total / (frame_count - 1) : 0; + this.performance_info = { + fps: frame_count, + frame_ms, + cpu_pressure: Math.min(100, Math.round(this.performance_long_task_total / 10)), + js_heap_mb: this.read_js_heap_mb(), + gpu_renderer: this.performance_info.gpu_renderer, + tile_state: this.viewer?.scene.globe.tilesLoaded ? "loaded" : "loading" + }; + this.performance_frame_count = 0; + this.performance_frame_time_total = 0; + this.performance_long_task_total = 0; + this.flush(); + } + read_js_heap_mb(): number | null { + const memory = (performance as any).memory; + if (!memory || typeof memory.usedJSHeapSize !== "number") return null; + return memory.usedJSHeapSize / 1024 / 1024; + } + read_gpu_renderer(): string { + if (!this.viewer) return ""; + const canvas = this.viewer.canvas; + const gl = (canvas.getContext("webgl2") || canvas.getContext("webgl")) as any; + if (!gl) return ""; + const debug = gl.getExtension("WEBGL_debug_renderer_info"); + if (debug) { + return String(gl.getParameter(debug.UNMASKED_RENDERER_WEBGL)); + } + return String(gl.getParameter(gl.RENDERER)); + } + current_camera_view(): Map_Camera_View { + const camera = this.viewer!.camera; + const position = camera.positionCartographic; + return { + longitude: Cesium.Math.toDegrees(position.longitude), + latitude: Cesium.Math.toDegrees(position.latitude), + height: position.height, + heading: Cesium.Math.toDegrees(camera.heading), + pitch: Cesium.Math.toDegrees(camera.pitch), + roll: Cesium.Math.toDegrees(camera.roll) + }; + } + restore_camera_view() { + if (!this.viewer) return; + const camera = this.map_view_config?.camera; + if (camera) { + this.viewer.camera.setView({ + destination: Cesium.Cartesian3.fromDegrees(camera.longitude, camera.latitude, camera.height), + orientation: { + heading: Cesium.Math.toRadians(camera.heading), + pitch: Cesium.Math.toRadians(camera.pitch), + roll: Cesium.Math.toRadians(camera.roll) + } + }); + return; + } + this.viewer.camera.flyTo({destination: default_camera_rectangle}); + } + async refresh_data_sources() { + if (!this.viewer) return; + for (const ds of this.list()) { + if (!ds.enable) continue; + await ds.refresh(); + } + this.sync_data_sources(); + } + sync_data_sources() { + if (!this.viewer) return; + const alive = new Set(); + const alive_base_stations = new Set(); + for (const ds of this.list()) { + if (!ds.enable) continue; + this.sync_base_station(ds, alive_base_stations); + if (!ds.aircraft_show) continue; + for (const [icao, aircraft] of ds.aircraftMap) { + const key = this.entity_key(ds.key, icao); + alive.add(key); + this.sync_aircraft(key, aircraft); + } + } + for (const [key, record] of this.entity_map) { + if (alive.has(key)) continue; + this.viewer.entities.remove(record.aircraft); + this.viewer.entities.remove(record.track); + for (const point of record.point_entities) { + this.viewer.entities.remove(point); + } + this.entity_map.delete(key); + if (this.selected_aircraft && key === this.entity_key(this.selected_aircraft.data_source.key, this.selected_aircraft.icao)) { + this.clear_selected_aircraft(); + } + } + for (const [key, record] of this.base_station_entity_map) { + if (alive_base_stations.has(key)) continue; + this.remove_base_station_record(record); + this.base_station_entity_map.delete(key); + } + this.viewer.scene.requestRender(); + } + sync_base_station(ds: Data_Source, alive_base_stations: Set) { + if (!this.viewer) return; + const station = ds.base_station; + const key = ds.key; + if (!ds.base_station_show || !ds.base_station_has_valid_position || !station.pos) { + const old_record = this.base_station_entity_map.get(key); + if (old_record) { + this.remove_base_station_record(old_record); + this.base_station_entity_map.delete(key); + } + return; + } + alive_base_stations.add(key); + const position = Cesium.Cartesian3.fromDegrees(station.pos.lng, station.pos.lat, ds.alt || 0); + const orientation = Cesium.Transforms.headingPitchRollQuaternion(position, new Cesium.HeadingPitchRoll(0, 0, 0)); + const color = Cesium.Color.fromCssColorString(ds.color); + let record = this.base_station_entity_map.get(key); + if (!record) { + record = this.create_base_station_record(ds, position, orientation, color); + this.base_station_entity_map.set(key, record); + } else { + record.model.position = new Cesium.ConstantPositionProperty(position); + record.model.orientation = new Cesium.ConstantProperty(orientation); + record.model.description = new Cesium.ConstantProperty(this.base_station_description(ds)); + record.hemisphere.position = new Cesium.ConstantPositionProperty(position); + record.hemisphere.orientation = new Cesium.ConstantProperty(orientation); + } + this.apply_base_station_style(record, ds, color); + } + create_base_station_record(ds: Data_Source, position: Cesium.Cartesian3, orientation: Cesium.Quaternion, color: Cesium.Color): Base_Station_Entity_Record { + const model = this.viewer!.entities.add({ + id: `${ds.key}:base-station`, + name: `${ds.key} 基站`, + position, + orientation, + model: { + uri: base_station_model_uri, + scale: 6, + minimumPixelSize: 36, + maximumScale: 300, + color, + colorBlendMode: Cesium.ColorBlendMode.MIX, + colorBlendAmount: 0.35, + enableVerticalExaggeration: false + }, + description: this.base_station_description(ds) + }); + const hemisphere = this.viewer!.entities.add({ + id: `${ds.key}:base-station-range`, + name: `${ds.key} 最大距离`, + position, + orientation, + show: false, + ellipsoid: { + radii: new Cesium.Cartesian3(1, 1, 1), + minimumCone: 0, + maximumCone: Cesium.Math.PI_OVER_TWO, + material: color.withAlpha(0.12), + outline: true, + outlineColor: color.withAlpha(0.65) + } + }); + return {model, hemisphere}; + } + apply_base_station_style(record: Base_Station_Entity_Record, ds: Data_Source, color: Cesium.Color) { + const radius = Math.max(0, ds.base_station.maxDistance || 0); + if (record.model.model) { + record.model.model.color = new Cesium.ConstantProperty(color); + record.model.model.colorBlendMode = new Cesium.ConstantProperty(Cesium.ColorBlendMode.MIX); + record.model.model.colorBlendAmount = new Cesium.ConstantProperty(0.35); + record.model.model.enableVerticalExaggeration = new Cesium.ConstantProperty(false); + } + record.hemisphere.show = radius > 0; + if (record.hemisphere.ellipsoid) { + record.hemisphere.ellipsoid.radii = new Cesium.ConstantProperty(new Cesium.Cartesian3(radius, radius, radius)); + record.hemisphere.ellipsoid.material = new Cesium.ColorMaterialProperty(color.withAlpha(0.12)); + record.hemisphere.ellipsoid.outlineColor = new Cesium.ConstantProperty(color.withAlpha(0.65)); + } + } + remove_base_station_record(record: Base_Station_Entity_Record) { + this.viewer?.entities.remove(record.model); + this.viewer?.entities.remove(record.hemisphere); + } + base_station_description(ds: Data_Source) { + const station = ds.base_station; + return [ + `数据源: ${ds.key}`, + `纬度: ${station.pos ? station.pos.lat.toFixed(6) : ""}`, + `经度: ${station.pos ? station.pos.lng.toFixed(6) : ""}`, + `高度: ${ds.alt || 0}`, + `有效位置: ${ds.base_station_has_valid_position ? "是" : "否"}`, + `最大距离: ${(station.maxDistance / 1000).toFixed(2)} km` + ].join("
"); + } + sync_aircraft(key: string, aircraft: Aircraft) { + if (!this.viewer) return; + const model = aircraft.data_model; + let record = this.entity_map.get(key); + const aircraft_position = this.aircraft_position(aircraft); + const position = Cesium.Cartesian3.fromDegrees(aircraft_position.lon, aircraft_position.lat, aircraft_position.alt); + const orientation = Cesium.Transforms.headingPitchRollQuaternion( + position, + new Cesium.HeadingPitchRoll( + Cesium.Math.toRadians(this.aircraft_heading(aircraft)) + aircraft_model_heading_offset, + 0, + aircraft_model_roll_offset + ) + ); + if (!record) { + record = this.create_aircraft_record(key, aircraft, position, orientation); + this.entity_map.set(key, record); + } else { + record.aircraft.position = new Cesium.ConstantPositionProperty(position); + record.aircraft.orientation = new Cesium.ConstantProperty(orientation); + record.aircraft.description = new Cesium.ConstantProperty(this.aircraft_description(model)); + } + this.apply_aircraft_style(record, aircraft); + if (this.is_selected_aircraft(key)) { + this.sync_track_points(key, aircraft, record); + } else { + this.hide_track(record); + } + } + create_aircraft_record(key: string, aircraft: Aircraft, position: Cesium.Cartesian3, orientation: Cesium.Quaternion): Aircraft_Entity_Record { + const model = aircraft.data_model; + const track_positions: Cesium.Cartesian3[] = []; + const aircraft_entity = this.viewer!.entities.add({ + id: `${key}:aircraft`, + name: model.icao, + position, + orientation, + model: { + uri: aircraft_model_uri, + minimumPixelSize: this.aircraft_pixel_size(aircraft), + maximumScale: 500, + color: this.aircraft_color(aircraft), + colorBlendMode: Cesium.ColorBlendMode.REPLACE, + colorBlendAmount: 1, + enableVerticalExaggeration: false + }, + label: { + show: this.aircraft_label_enabled(aircraft), + text: this.aircraft_label_text(aircraft), + font: "13px sans-serif", + pixelOffset: new Cesium.Cartesian2(0, -28), + fillColor: Cesium.Color.WHITE, + outlineColor: Cesium.Color.BLACK, + outlineWidth: 2, + style: Cesium.LabelStyle.FILL_AND_OUTLINE + }, + description: this.aircraft_description(model) + }); + const track_entity = this.viewer!.entities.add({ + id: `${key}:track`, + show: false, + polyline: { + positions: new Cesium.CallbackProperty(() => track_positions, true), + width: 2, + material: Cesium.Color.fromCssColorString(aircraft.data_source.color).withAlpha(0.8) + } + }); + (aircraft_entity as any).ecap_aircraft = aircraft; + return {aircraft: aircraft_entity, track: track_entity, point_entities: [], point_start_index: 0, track_positions, track_positions_key: ""}; + } + aircraft_pixel_size(aircraft: Aircraft): number { + return Math.max(1, aircraft.data_source.aircraft_pixel_size || 28); + } + aircraft_color(aircraft: Aircraft): Cesium.Color { + return Cesium.Color.fromCssColorString(aircraft.data_source.color); + } + aircraft_position(aircraft: Aircraft): Aircraft_Position { + const points = aircraft.data_model.track_points; + if (points.length > 0) { + const point = points[points.length - 1]; + return {lon: point.lon, lat: point.lat, alt: point.alt}; + } + const model = aircraft.data_model; + return {lon: model.lon, lat: model.lat, alt: model.alt}; + } + aircraft_heading(aircraft: Aircraft): number { + const points = aircraft.data_model.track_points; + if (points.length > 1) { + return this.heading_between(points[points.length - 2], points[points.length - 1]); + } + return aircraft.data_model.heading; + } + heading_between(start: Aircraft_Position, end: Aircraft_Position): number { + const heading = Cesium.Math.toDegrees(Math.atan2(end.lon - start.lon, end.lat - start.lat)); + return heading < 0 ? heading + 360 : heading; + } + apply_aircraft_style(record: Aircraft_Entity_Record, aircraft: Aircraft) { + const color = this.aircraft_color(aircraft); + if (record.aircraft.model) { + record.aircraft.model.minimumPixelSize = new Cesium.ConstantProperty(this.aircraft_pixel_size(aircraft)); + record.aircraft.model.color = new Cesium.ConstantProperty(color); + record.aircraft.model.colorBlendMode = new Cesium.ConstantProperty(Cesium.ColorBlendMode.REPLACE); + record.aircraft.model.colorBlendAmount = new Cesium.ConstantProperty(1); + record.aircraft.model.enableVerticalExaggeration = new Cesium.ConstantProperty(false); + } + if (record.aircraft.label) { + record.aircraft.label.show = new Cesium.ConstantProperty(this.aircraft_label_enabled(aircraft)); + record.aircraft.label.text = new Cesium.ConstantProperty(this.aircraft_label_text(aircraft)); + } + if (record.track.polyline) { + record.track.polyline.material = new Cesium.ColorMaterialProperty(color.withAlpha(0.8)); + } + } + aircraft_label_enabled(aircraft: Aircraft): boolean { + const source = aircraft.data_source; + return source.show_icao || source.show_call_sign || source.show_fly_status; + } + aircraft_label_text(aircraft: Aircraft): string { + const lines: string[] = []; + const source = aircraft.data_source; + const model = aircraft.data_model; + if (source.show_icao) lines.push(model.icao); + if (source.show_call_sign && model.call_sign) lines.push(model.call_sign); + if (source.show_fly_status && model.fly_status) lines.push(model.fly_status); + return lines.join("\n"); + } + sync_track_points(key: string, aircraft: Aircraft, record: Aircraft_Entity_Record) { + const points = aircraft.data_model.track_points; + if (points.length < record.point_start_index + record.point_entities.length) { + for (const entity of record.point_entities) { + this.viewer!.entities.remove(entity); + } + record.point_entities = []; + record.point_start_index = 0; + } + const next_start = Math.max(0, points.length - this.max_track_points_per_aircraft); + if (next_start > record.point_start_index) { + const remove_count = Math.min(next_start - record.point_start_index, record.point_entities.length); + for (const entity of record.point_entities.splice(0, remove_count)) { + this.viewer!.entities.remove(entity); + } + record.point_start_index += remove_count; + } + const next_index = record.point_start_index + record.point_entities.length; + for (let i = next_index; i < points.length; i++) { + record.point_entities.push(this.create_track_point_entity(key, aircraft, points[i], i)); + } + const displayed_points = points.slice(record.point_start_index); + const aircraft_position = this.aircraft_position(aircraft); + const track_positions_key = `${record.point_start_index}:${points.length}:${aircraft_position.lon}:${aircraft_position.lat}:${aircraft_position.alt}`; + if (record.track_positions_key !== track_positions_key) { + record.track_positions.length = 0; + for (const point of displayed_points) { + record.track_positions.push(Cesium.Cartesian3.fromDegrees(point.lon, point.lat, point.alt)); + } + const last_point = displayed_points[displayed_points.length - 1]; + if (!last_point || !this.same_position(last_point, aircraft_position)) { + record.track_positions.push(Cesium.Cartesian3.fromDegrees(aircraft_position.lon, aircraft_position.lat, aircraft_position.alt)); + } + record.track_positions_key = track_positions_key; + } + this.show_track(record); + } + same_position(left: Aircraft_Position, right: Aircraft_Position): boolean { + return left.lon === right.lon && left.lat === right.lat && left.alt === right.alt; + } + create_track_point_entity(key: string, aircraft: Aircraft, point: Aircraft_Track_Point_Model, index: number): Cesium.Entity { + const base = { + id: `${key}:point:${index}`, + name: `${aircraft.icao} ${index}`, + position: Cesium.Cartesian3.fromDegrees(point.lon, point.lat, point.alt), + description: this.track_point_description(point) + }; + const entity = this.viewer!.entities.add(this.is_special_track_point(point) ? { + ...base, + model: { + uri: track_point_model_uri, + scale: 16 + } + } : { + ...base, + point: { + pixelSize: 7, + color: Cesium.Color.YELLOW.withAlpha(0.9), + outlineColor: Cesium.Color.BLACK, + outlineWidth: 1, + heightReference: Cesium.HeightReference.NONE + } + }); + (entity as any).ecap_track_point = point; + (entity as any).ecap_track_key = key; + return entity; + } + is_selected_aircraft(key: string): boolean { + if (!this.selected_aircraft) return false; + return key === this.entity_key(this.selected_aircraft.data_source.key, this.selected_aircraft.icao); + } + hide_track(record: Aircraft_Entity_Record) { + record.track.show = false; + for (const entity of record.point_entities) { + entity.show = false; + } + } + hide_all_tracks() { + for (const record of this.entity_map.values()) { + this.hide_track(record); + } + } + show_track(record: Aircraft_Entity_Record) { + for (const item of this.entity_map.values()) { + if (item !== record) { + this.hide_track(item); + } + } + record.track.show = true; + for (const entity of record.point_entities) { + entity.show = true; + } + } + is_special_track_point(point: Aircraft_Track_Point_Model): boolean { + const properties = point.properties || {}; + return Boolean(properties.alarm || properties.alert || properties.warning || properties.is_alarm || properties.event_type); + } + handle_click(position: Cesium.Cartesian2) { + if (!this.viewer) return; + const picked = this.viewer.scene.pick(position); + if (!Cesium.defined(picked) || !picked.id) { + this.clear_selected_aircraft(); + return; + } + const entity = picked.id as Cesium.Entity; + const aircraft = (entity as any).ecap_aircraft as Aircraft | undefined; + if (aircraft) { + this.select_aircraft(aircraft); + return; + } + const track_point = (entity as any).ecap_track_point as Aircraft_Track_Point_Model | undefined; + if (track_point) { + this.select_track_point(entity); + return; + } + this.clear_selected_aircraft(); + } + select_track_point(entity: Cesium.Entity) { + if (!this.viewer) return; + this.viewer.selectedEntity = entity; + this.viewer.scene.requestRender(); + } + select_aircraft(aircraft: Aircraft) { + const key = this.entity_key(aircraft.data_source.key, aircraft.icao); + if (this.selected_aircraft) { + const old_key = this.entity_key(this.selected_aircraft.data_source.key, this.selected_aircraft.icao); + const old_record = this.entity_map.get(old_key); + if (old_record && old_key !== key) { + this.hide_track(old_record); + } + } + this.selected_aircraft = aircraft; + if (this.viewer) { + this.viewer.trackedEntity = undefined; + this.viewer.selectedEntity = undefined; + this.viewer.scene.requestRender(); + } + this.tracking = false; + const info = app.leaflet_map!.aircraft_info_show; + info.active_aircraft = aircraft; + if (info.drawer.have_open === false) { + info.drawer.toggleOpen(); + } + aircraft.refresh_base_information(); + aircraft.load_track_data().then(() => { + if (!this.selected_aircraft || this.entity_key(this.selected_aircraft.data_source.key, this.selected_aircraft.icao) !== key) return; + this.sync_aircraft(key, aircraft); + }); + this.flush(); + } + clear_selected_aircraft() { + if (this.selected_aircraft) { + const record = this.entity_map.get(this.entity_key(this.selected_aircraft.data_source.key, this.selected_aircraft.icao)); + if (record) { + this.hide_track(record); + } + } else { + this.hide_all_tracks(); + } + if (this.viewer) { + this.viewer.trackedEntity = undefined; + this.viewer.selectedEntity = undefined; + this.viewer.scene.requestRender(); + } + this.selected_aircraft = null; + this.tracking = false; + const info = app.leaflet_map?.aircraft_info_show; + if (info) { + info.active_aircraft = null; + info.flush(); + } + this.flush(); + } + locate_selected() { + if (!this.viewer || !this.selected_aircraft) return; + const record = this.entity_map.get(this.entity_key(this.selected_aircraft.data_source.key, this.selected_aircraft.icao)); + if (record) { + this.viewer.flyTo(record.aircraft); + } + } + follow_selected() { + if (!this.viewer || !this.selected_aircraft) return; + const record = this.entity_map.get(this.entity_key(this.selected_aircraft.data_source.key, this.selected_aircraft.icao)); + if (record) { + this.viewer.trackedEntity = record.aircraft; + this.tracking = true; + this.flush(); + } + } + entity_key(data_source_key: string, icao: string) { + return `${data_source_key}:${icao}`; + } + aircraft_description(model: Aircraft_Model) { + return [ + `ICAO: ${model.icao}`, + `航班号: ${model.call_sign || ""}`, + `状态: ${model.fly_status || ""}`, + `纬度: ${model.lat.toFixed(6)}`, + `经度: ${model.lon.toFixed(6)}`, + `高度: ${model.alt}`, + `航向: ${model.heading}` + ].join("
"); + } + track_point_description(point: Aircraft_Track_Point_Model) { + const extra = Object.entries(point.properties).map(([key, value]) => `${key}: ${value}`).join("
"); + return [ + `时间: ${point.timestamp || ""}`, + `纬度: ${point.lat.toFixed(6)}`, + `经度: ${point.lon.toFixed(6)}`, + `高度: ${point.alt}`, + extra + ].filter(Boolean).join("
"); + } + render_performance_panel() { + if (!this.graphics_config.debugShowFramesPerSecond) return null; + const info = this.performance_info; + return ( +
+
{`FPS ${info.fps}`}
+
{`Frame ${info.frame_ms.toFixed(1)} ms`}
+
{`CPU估算 ${info.cpu_pressure}%`}
+
{`JS堆 ${info.js_heap_mb === null ? "N/A" : `${info.js_heap_mb.toFixed(1)} MB`}`}
+
{`Tiles ${info.tile_state || "N/A"}`}
+
{`GPU ${info.gpu_renderer || "N/A"}`}
+
+ ); + } + render(props: any) { + const AircraftInfo = app.leaflet_map!.aircraft_info_show.x; + const DataSourceShow = app.leaflet_map!.data_source_show.x; + const imagery_options = (this.map_resources?.imagery_sources || []).map(source => { + return {value: source.key, label: source.name}; + }); + return ( +
+ + +
+
+