3D
This commit is contained in:
+13
-2
@@ -2,14 +2,16 @@ import js from '@eslint/js'
|
|||||||
import globals from 'globals'
|
import globals from 'globals'
|
||||||
import reactHooks from 'eslint-plugin-react-hooks'
|
import reactHooks from 'eslint-plugin-react-hooks'
|
||||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||||
|
import tseslint from 'typescript-eslint'
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{ ignores: ['dist'] },
|
{ ignores: ['dist/**', 'wwwroot/**', 'htdocs/**', 'cesium/**', 'node_modules/**'] },
|
||||||
{
|
{
|
||||||
files: ['**/*.{js,jsx}'],
|
files: ['src/**/*.{ts,tsx}'],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
ecmaVersion: 2020,
|
ecmaVersion: 2020,
|
||||||
globals: globals.browser,
|
globals: globals.browser,
|
||||||
|
parser: tseslint.parser,
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 'latest',
|
ecmaVersion: 'latest',
|
||||||
ecmaFeatures: { jsx: true },
|
ecmaFeatures: { jsx: true },
|
||||||
@@ -19,12 +21,21 @@ export default [
|
|||||||
plugins: {
|
plugins: {
|
||||||
'react-hooks': reactHooks,
|
'react-hooks': reactHooks,
|
||||||
'react-refresh': reactRefresh,
|
'react-refresh': reactRefresh,
|
||||||
|
'@typescript-eslint': tseslint.plugin,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
...js.configs.recommended.rules,
|
...js.configs.recommended.rules,
|
||||||
|
...tseslint.configs.recommended.rules,
|
||||||
...reactHooks.configs.recommended.rules,
|
...reactHooks.configs.recommended.rules,
|
||||||
//'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
|
//'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
|
||||||
'no-unused-vars': "off",
|
'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': [
|
'react-refresh/only-export-components': [
|
||||||
'warn',
|
'warn',
|
||||||
{ allowConstantExport: true },
|
{ allowConstantExport: true },
|
||||||
|
|||||||
@@ -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.
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Generated
+595
-9
@@ -15,6 +15,7 @@
|
|||||||
"@use-gesture/react": "^10.3.1",
|
"@use-gesture/react": "^10.3.1",
|
||||||
"antd": "^5.29.1",
|
"antd": "^5.29.1",
|
||||||
"axios": "^1.8.4",
|
"axios": "^1.8.4",
|
||||||
|
"cesium": "^1.143.0",
|
||||||
"echarts": "^6.0.0",
|
"echarts": "^6.0.0",
|
||||||
"framer-motion": "^12.23.24",
|
"framer-motion": "^12.23.24",
|
||||||
"gcoord": "^1.0.7",
|
"gcoord": "^1.0.7",
|
||||||
@@ -40,6 +41,7 @@
|
|||||||
"eslint-plugin-react-hooks": "^5.1.0",
|
"eslint-plugin-react-hooks": "^5.1.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.19",
|
"eslint-plugin-react-refresh": "^0.4.19",
|
||||||
"globals": "^15.15.0",
|
"globals": "^15.15.0",
|
||||||
|
"typescript-eslint": "^8.65.0",
|
||||||
"vite": "^6.2.0"
|
"vite": "^6.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -896,6 +898,63 @@
|
|||||||
"node": ">=6.9.0"
|
"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": {
|
"node_modules/@chenshuai2144/sketch-color": {
|
||||||
"version": "1.0.9",
|
"version": "1.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@chenshuai2144/sketch-color/-/sketch-color-1.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/@chenshuai2144/sketch-color/-/sketch-color-1.0.9.tgz",
|
||||||
@@ -1426,9 +1485,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint-community/eslint-utils": {
|
"node_modules/@eslint-community/eslint-utils": {
|
||||||
"version": "4.5.1",
|
"version": "4.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz",
|
||||||
"integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==",
|
"integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1458,9 +1517,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint-community/regexpp": {
|
"node_modules/@eslint-community/regexpp": {
|
||||||
"version": "4.12.1",
|
"version": "4.12.2",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
|
||||||
"integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
|
"integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2356,6 +2415,16 @@
|
|||||||
"win32"
|
"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": {
|
"node_modules/@turf/area": {
|
||||||
"version": "7.3.1",
|
"version": "7.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@turf/area/-/area-7.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@turf/area/-/area-7.3.1.tgz",
|
||||||
@@ -2427,6 +2496,12 @@
|
|||||||
"url": "https://opencollective.com/turf"
|
"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": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||||
@@ -2583,6 +2658,308 @@
|
|||||||
"@types/geojson": "*"
|
"@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": {
|
"node_modules/@umijs/route-utils": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@umijs/route-utils/-/route-utils-4.0.1.tgz",
|
"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"
|
"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": {
|
"node_modules/abs-svg-path": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz",
|
"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==",
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/axios": {
|
||||||
"version": "1.8.4",
|
"version": "1.8.4",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
|
||||||
@@ -2992,6 +3392,25 @@
|
|||||||
"element-size": "^1.1.1"
|
"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": {
|
"node_modules/chalk": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
@@ -3443,9 +3862,9 @@
|
|||||||
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
|
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
|
||||||
},
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -3499,6 +3918,21 @@
|
|||||||
"integrity": "sha512-I3JIbrnKPAntNLl1I6TpSQQdQ4AutYzv/sKMFKbepawV/hlH0GmYKhUoOEMd4xqaUHT+Bm0f4127lh5qs1m1tw==",
|
"integrity": "sha512-I3JIbrnKPAntNLl1I6TpSQQdQ4AutYzv/sKMFKbepawV/hlH0GmYKhUoOEMd4xqaUHT+Bm0f4127lh5qs1m1tw==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/draw-svg-path": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/draw-svg-path/-/draw-svg-path-1.0.0.tgz",
|
"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==",
|
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||||
"license": "ISC"
|
"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": {
|
"node_modules/grid-index": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/grid-index/-/grid-index-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/grid-index/-/grid-index-1.1.0.tgz",
|
||||||
@@ -4982,6 +5422,15 @@
|
|||||||
"js-yaml": "bin/js-yaml.js"
|
"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": {
|
"node_modules/jsesc": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||||
@@ -5068,6 +5517,12 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/leaflet": {
|
||||||
"version": "1.9.4",
|
"version": "1.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
||||||
@@ -5080,6 +5535,12 @@
|
|||||||
"integrity": "sha512-yc97gxLXwbZa+Gk9VCcqI0CkvIBC9oNTTjFsHqq4EQvANrvaboib4UdeQLyTnEqDpaXHCqzwwVIDHtvz2mUiDg==",
|
"integrity": "sha512-yc97gxLXwbZa+Gk9VCcqI0CkvIBC9oNTTjFsHqq4EQvANrvaboib4UdeQLyTnEqDpaXHCqzwwVIDHtvz2mUiDg==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/levn": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||||
@@ -5126,6 +5587,12 @@
|
|||||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/loose-envify": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-4.0.3.tgz",
|
||||||
"integrity": "sha512-QmpUu4KqDmX0plH4u+tf0riMc1KHE1+lw95cMrLlXQAFOx/xnBtwhZ52XJxd9X2O6kwKBqX32kmhbhlobD0cuw=="
|
"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": {
|
"node_modules/mime-db": {
|
||||||
"version": "1.52.0",
|
"version": "1.52.0",
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
"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==",
|
"integrity": "sha512-1/kmYej2iedi5+ROxkRESL/pI02pkg0OBnaR4hJkSIX6+ORzepwbuUXfrdZaPjysTsJInj0Rj5NuX027+dMBvA==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/number-is-integer": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/number-is-integer/-/number-is-integer-1.0.1.tgz",
|
"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"
|
"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": {
|
"node_modules/parent-module": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
"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": {
|
"node_modules/protocol-buffers-schema": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz",
|
||||||
@@ -5873,6 +6386,21 @@
|
|||||||
"performance-now": "^2.1.0"
|
"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": {
|
"node_modules/rc-cascader": {
|
||||||
"version": "3.34.0",
|
"version": "3.34.0",
|
||||||
"resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.34.0.tgz",
|
"resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.34.0.tgz",
|
||||||
@@ -7212,6 +7740,19 @@
|
|||||||
"topoquantize": "bin/topoquantize"
|
"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": {
|
"node_modules/tslib": {
|
||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
@@ -7257,6 +7798,45 @@
|
|||||||
"dup": "^1.0.0"
|
"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": {
|
"node_modules/unquote": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz",
|
||||||
@@ -7310,6 +7890,12 @@
|
|||||||
"punycode": "^2.1.0"
|
"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": {
|
"node_modules/use-sync-external-store": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
|
"lint:src": "eslint \"src/**/*.{ts,tsx}\"",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -17,6 +19,7 @@
|
|||||||
"@use-gesture/react": "^10.3.1",
|
"@use-gesture/react": "^10.3.1",
|
||||||
"antd": "^5.29.1",
|
"antd": "^5.29.1",
|
||||||
"axios": "^1.8.4",
|
"axios": "^1.8.4",
|
||||||
|
"cesium": "^1.143.0",
|
||||||
"echarts": "^6.0.0",
|
"echarts": "^6.0.0",
|
||||||
"framer-motion": "^12.23.24",
|
"framer-motion": "^12.23.24",
|
||||||
"gcoord": "^1.0.7",
|
"gcoord": "^1.0.7",
|
||||||
@@ -42,6 +45,7 @@
|
|||||||
"eslint-plugin-react-hooks": "^5.1.0",
|
"eslint-plugin-react-hooks": "^5.1.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.19",
|
"eslint-plugin-react-refresh": "^0.4.19",
|
||||||
"globals": "^15.15.0",
|
"globals": "^15.15.0",
|
||||||
|
"typescript-eslint": "^8.65.0",
|
||||||
"vite": "^6.2.0"
|
"vite": "^6.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+78
-34
@@ -22,6 +22,7 @@ import axios from "axios";
|
|||||||
import { baseURL } from "./Global.tsx";
|
import { baseURL } from "./Global.tsx";
|
||||||
|
|
||||||
import L from 'leaflet';
|
import L from 'leaflet';
|
||||||
|
const Cesium_Map_View = React.lazy(() => import("./Map/Cesium_Map.tsx"));
|
||||||
|
|
||||||
delete (L.Icon.Default.prototype as any)._getIconUrl;
|
delete (L.Icon.Default.prototype as any)._getIconUrl;
|
||||||
|
|
||||||
@@ -56,10 +57,15 @@ class App extends enhance.Base {
|
|||||||
// page 是我自定义的
|
// page 是我自定义的
|
||||||
this.menuItems = [
|
this.menuItems = [
|
||||||
{
|
{
|
||||||
label: '地图选项',
|
label: '2D地图',
|
||||||
icon: <SettingOutlined />,
|
icon: <SettingOutlined />,
|
||||||
key: '/map',
|
key: '/map',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '3D地图',
|
||||||
|
icon: <SettingOutlined />,
|
||||||
|
key: '/map3d',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '设置',
|
label: '设置',
|
||||||
icon: <SettingOutlined />,
|
icon: <SettingOutlined />,
|
||||||
@@ -107,7 +113,8 @@ class App extends enhance.Base {
|
|||||||
this.navigate = useNavigate();
|
this.navigate = useNavigate();
|
||||||
this.location = useLocation();
|
this.location = useLocation();
|
||||||
useEffect(() => {
|
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.flush()
|
||||||
}, [this.location]);
|
}, [this.location]);
|
||||||
};
|
};
|
||||||
@@ -156,46 +163,57 @@ class App extends enhance.Base {
|
|||||||
height: '100vh'
|
height: '100vh'
|
||||||
}}>
|
}}>
|
||||||
|
|
||||||
<Menu
|
<div style={{
|
||||||
style={{}}
|
display: 'flex',
|
||||||
selectedKeys={[this.selected_key]}
|
alignItems: 'center',
|
||||||
onClick={this.onClick}
|
flexShrink: 0,
|
||||||
mode="horizontal"
|
borderBottom: '1px solid #f0f0f0'
|
||||||
// 核心修改:使用items属性配置所有菜单项,替代原有Menu.Item子组件
|
}}>
|
||||||
items={[
|
<Menu
|
||||||
// 1. 原有动态菜单列表(menuItems映射)
|
style={{
|
||||||
...this.menuItems.map(item => ({
|
borderBottom: 0,
|
||||||
|
flex: '0 0 auto'
|
||||||
|
}}
|
||||||
|
selectedKeys={[this.selected_key]}
|
||||||
|
onClick={this.onClick}
|
||||||
|
mode="horizontal"
|
||||||
|
items={this.menuItems.map(item => ({
|
||||||
key: item.key,
|
key: item.key,
|
||||||
icon: item.icon,
|
icon: item.icon,
|
||||||
label: item.label
|
label: item.label
|
||||||
})),
|
}))}
|
||||||
// 2. 原有版本号固定菜单项(右侧对齐、禁用、自定义样式)
|
/>
|
||||||
{
|
<div style={{
|
||||||
key: "version",
|
marginLeft: 'auto',
|
||||||
label: (
|
minWidth: 0,
|
||||||
<span style={{
|
overflow: 'hidden',
|
||||||
color: '#666', // 字体颜色
|
textOverflow: 'ellipsis',
|
||||||
fontWeight: 'bold', // 字体加粗
|
whiteSpace: 'nowrap',
|
||||||
padding: '4px 8px', // 内边距
|
color: '#666',
|
||||||
}}>
|
fontWeight: 'bold',
|
||||||
{`开机时间:${this.formatTimestamp(this.start_device_time)} 版本:${this.version}`}
|
padding: '0 20px 0 8px'
|
||||||
</span>
|
}}>
|
||||||
),
|
{`开机时间:${this.formatTimestamp(this.start_device_time)} 版本:${this.version}`}
|
||||||
disabled: true,
|
</div>
|
||||||
style: {
|
</div>
|
||||||
marginLeft: 'auto',
|
|
||||||
paddingRight: '20px',
|
|
||||||
cursor: 'none',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
path="/map"
|
path="/map"
|
||||||
element={(
|
element={(
|
||||||
<div style={{ width: '100%', height: '100%', position: 'relative'}}>
|
<div style={{ width: '100%', height: '100%', position: 'relative'}}>
|
||||||
<this.leaflet_map.x />
|
<React.Suspense fallback={<div style={{padding: 20}}>地图加载中...</div>}>
|
||||||
|
<Cesium_Map_View initialSceneMode="2d" />
|
||||||
|
</React.Suspense>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/ui/map"
|
||||||
|
element={(
|
||||||
|
<div style={{ width: '100%', height: '100%', position: 'relative'}}>
|
||||||
|
<React.Suspense fallback={<div style={{padding: 20}}>地图加载中...</div>}>
|
||||||
|
<Cesium_Map_View initialSceneMode="2d" />
|
||||||
|
</React.Suspense>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -205,6 +223,32 @@ class App extends enhance.Base {
|
|||||||
<this.setting.x />
|
<this.setting.x />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="/ui/settings"
|
||||||
|
element={(
|
||||||
|
<this.setting.x />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/map3d"
|
||||||
|
element={(
|
||||||
|
<div style={{ width: '100%', height: '100%', position: 'relative'}}>
|
||||||
|
<React.Suspense fallback={<div style={{padding: 20}}>3D地图加载中...</div>}>
|
||||||
|
<Cesium_Map_View initialSceneMode="3d" />
|
||||||
|
</React.Suspense>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/ui/map3d"
|
||||||
|
element={(
|
||||||
|
<div style={{ width: '100%', height: '100%', position: 'relative'}}>
|
||||||
|
<React.Suspense fallback={<div style={{padding: 20}}>3D地图加载中...</div>}>
|
||||||
|
<Cesium_Map_View initialSceneMode="3d" />
|
||||||
|
</React.Suspense>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/aircraftlist"
|
path="/aircraftlist"
|
||||||
element={(
|
element={(
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export class Data_Source extends enhance.Base {
|
|||||||
lat: number = 0;
|
lat: number = 0;
|
||||||
lon: number = 0;
|
lon: number = 0;
|
||||||
alt: number = 0;
|
alt: number = 0;
|
||||||
|
base_station_has_valid_position: boolean = false;
|
||||||
update_form_gps: boolean = false;
|
update_form_gps: boolean = false;
|
||||||
aircraft_pixel_size: number = 20;
|
aircraft_pixel_size: number = 20;
|
||||||
ignore_msg_time: boolean = false;
|
ignore_msg_time: boolean = false;
|
||||||
@@ -88,21 +89,27 @@ export class Data_Source extends enhance.Base {
|
|||||||
|
|
||||||
let d = response.data;
|
let d = response.data;
|
||||||
this.base_station.set_popup(this.key, d);
|
this.base_station.set_popup(this.key, d);
|
||||||
|
const gps_has_valid_position = Boolean(d.has_valid_position ?? d["GPS有效定位"]);
|
||||||
if (this.update_form_gps) {
|
if (this.update_form_gps) {
|
||||||
this.lat = d.latitude;
|
this.base_station_has_valid_position = gps_has_valid_position;
|
||||||
this.lon = d.longitude;
|
if (gps_has_valid_position) {
|
||||||
let dss : Data_Source_Show = app.leaflet_map.data_source_show;
|
this.lat = d.latitude;
|
||||||
if(dss.mounted) {
|
this.lon = d.longitude;
|
||||||
|
}
|
||||||
|
let dss : Data_Source_Show | null = app.leaflet_map?.data_source_show ?? null;
|
||||||
|
if(dss?.mounted) {
|
||||||
dss.flush();
|
dss.flush();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.base_station_has_valid_position = Number.isFinite(this.lat) && Number.isFinite(this.lon) && (this.lat !== 0 || this.lon !== 0);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("refresh base station location failed:", err);
|
console.error("refresh base station location failed:", err);
|
||||||
} finally {
|
} finally {
|
||||||
this.base_station.set_pos(this.lat, this.lon);
|
this.base_station.set_pos(this.lat, this.lon, this.base_station_has_valid_position);
|
||||||
let ds = app.get_Data_Source(this.key)
|
let ds = app.get_Data_Source(this.key)
|
||||||
ds.refresh_base_station();
|
ds?.refresh_base_station();
|
||||||
if(this.first){
|
if(this.first && ds){
|
||||||
ds.aircraft_pixel_size = this.aircraft_pixel_size
|
ds.aircraft_pixel_size = this.aircraft_pixel_size
|
||||||
ds.color = this.color
|
ds.color = this.color
|
||||||
this.first = false;
|
this.first = false;
|
||||||
@@ -129,11 +136,13 @@ export class Data_Source extends enhance.Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
refresh_base_station() {
|
refresh_base_station() {
|
||||||
|
const map = app.leaflet_map?.map;
|
||||||
|
if (!map) return;
|
||||||
if (this.enable) {
|
if (this.enable) {
|
||||||
if (this.base_station_show) {
|
if (this.base_station_show && this.base_station_has_valid_position) {
|
||||||
this.base_station.set_color(darkenColor(this.color, 0.5))
|
this.base_station.set_color(darkenColor(this.color, 0.5))
|
||||||
this.base_station.show(app.leaflet_map.map);
|
this.base_station.show(map);
|
||||||
this.base_station.set_pos(this.lat, this.lon);
|
this.base_station.set_pos(this.lat, this.lon, this.base_station_has_valid_position);
|
||||||
this.base_station.on_mount()
|
this.base_station.on_mount()
|
||||||
} else {
|
} else {
|
||||||
this.base_station.hide();
|
this.base_station.hide();
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export class Data_Source_Show extends enhance.Base {
|
|||||||
<InputNumber addonBefore="纬度" min={-90} max={90} placeholder="请输入维度"
|
<InputNumber addonBefore="纬度" min={-90} max={90} placeholder="请输入维度"
|
||||||
value={ds.lat}
|
value={ds.lat}
|
||||||
onChange={(value: number | null) => {
|
onChange={(value: number | null) => {
|
||||||
if (value) {
|
if (value !== null) {
|
||||||
ds.lat = value
|
ds.lat = value
|
||||||
this.flush();
|
this.flush();
|
||||||
}
|
}
|
||||||
@@ -116,7 +116,7 @@ export class Data_Source_Show extends enhance.Base {
|
|||||||
placeholder="请输入经度"
|
placeholder="请输入经度"
|
||||||
value={ds.lon}
|
value={ds.lon}
|
||||||
onChange={(value: number | null) => {
|
onChange={(value: number | null) => {
|
||||||
if (value) {
|
if (value !== null) {
|
||||||
ds.lon = value
|
ds.lon = value
|
||||||
this.flush();
|
this.flush();
|
||||||
}
|
}
|
||||||
@@ -125,7 +125,7 @@ export class Data_Source_Show extends enhance.Base {
|
|||||||
<InputNumber addonBefore="高度" min={0} max={65535} placeholder="请输入高度"
|
<InputNumber addonBefore="高度" min={0} max={65535} placeholder="请输入高度"
|
||||||
value={ds.alt}
|
value={ds.alt}
|
||||||
onChange={(value: number | null) => {
|
onChange={(value: number | null) => {
|
||||||
if (value) {
|
if (value !== null) {
|
||||||
ds.alt = value
|
ds.alt = value
|
||||||
this.flush();
|
this.flush();
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@ export class Data_Source_Show extends enhance.Base {
|
|||||||
placeholder="请输入显示大小"
|
placeholder="请输入显示大小"
|
||||||
value={ds.aircraft_pixel_size}
|
value={ds.aircraft_pixel_size}
|
||||||
onChange={(value: number | null) => {
|
onChange={(value: number | null) => {
|
||||||
if (value) {
|
if (value !== null) {
|
||||||
ds.aircraft_pixel_size = value;
|
ds.aircraft_pixel_size = value;
|
||||||
|
|
||||||
// console.log("ds.aircraft_pixel_size", ds.aircraft_pixel_size)
|
// console.log("ds.aircraft_pixel_size", ds.aircraft_pixel_size)
|
||||||
|
|||||||
+55
-141
@@ -5,7 +5,7 @@ import {baseURL, G, StateHolder} from "../Global.js";
|
|||||||
import {lightenColor} from "../Global.tsx";
|
import {lightenColor} from "../Global.tsx";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import {Data_Source} from "../Data_Source/Data_Source.tsx";
|
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#marker
|
||||||
// https://leafletjs.cn/reference.html#circlemarker
|
// 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 {
|
export class Aircraft {
|
||||||
pos_marker: Aircraft_Marker = new Aircraft_Marker([0.0, 0.0])
|
pos_marker: Aircraft_Marker = new Aircraft_Marker([0.0, 0.0])
|
||||||
icao: string
|
icao: string
|
||||||
@@ -211,6 +196,7 @@ export class Aircraft {
|
|||||||
track_path: Aircraft_Track_Path = new Aircraft_Track_Path();
|
track_path: Aircraft_Track_Path = new Aircraft_Track_Path();
|
||||||
track_point_list: Aircraft_Track_Point[] = []
|
track_point_list: Aircraft_Track_Point[] = []
|
||||||
data_list: any[] = [];
|
data_list: any[] = [];
|
||||||
|
data_model: Aircraft_Model;
|
||||||
status = new StateHolder<Aircraft_Status>(Aircraft_Status.Need_Init);
|
status = new StateHolder<Aircraft_Status>(Aircraft_Status.Need_Init);
|
||||||
angle = new StateHolder<number>(0);
|
angle = new StateHolder<number>(0);
|
||||||
|
|
||||||
@@ -408,6 +394,7 @@ export class Aircraft {
|
|||||||
|
|
||||||
constructor(icao: string, ds: Data_Source) {
|
constructor(icao: string, ds: Data_Source) {
|
||||||
this.icao = icao
|
this.icao = icao
|
||||||
|
this.data_model = new Aircraft_Model(ds.key, icao)
|
||||||
// this.pos_marker.bindPopup("没有数据也不能删除,否则不弹出!");
|
// this.pos_marker.bindPopup("没有数据也不能删除,否则不弹出!");
|
||||||
// this.track_path.setColor(lightenColor(this.color));
|
// this.track_path.setColor(lightenColor(this.color));
|
||||||
this.data_source = ds;
|
this.data_source = ds;
|
||||||
@@ -439,7 +426,7 @@ export class Aircraft {
|
|||||||
if (list.length >= 2) {
|
if (list.length >= 2) {
|
||||||
let second_last_data = list[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 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);
|
this.angle.set(angle);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.pos_marker.setRotationAngle(this.angle);
|
this.pos_marker.setRotationAngle(this.angle);
|
||||||
@@ -450,78 +437,14 @@ export class Aircraft {
|
|||||||
|
|
||||||
|
|
||||||
refresh_from_change_list(item) {
|
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_pos(item.latitude, item.longitude, item.altitude);
|
||||||
this.set_angle(item.track)
|
this.set_angle(this.data_model.heading)
|
||||||
|
this.status.set(this.data_model.status)
|
||||||
|
|
||||||
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.refresh();
|
this.refresh();
|
||||||
|
this.call_sign = this.data_model.call_sign
|
||||||
// === 4. 弹窗内容(带状态) ===
|
this.fly_status = this.data_model.fly_status
|
||||||
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 +
|
|
||||||
// "<br>航班号: " + item.call_sign +
|
|
||||||
// "<br>状态: " + statusText,
|
|
||||||
// maxWidth: 300,
|
|
||||||
// minWidth: 50,
|
|
||||||
// autoPan: true,
|
|
||||||
// autoPanPadding: [5, 5],
|
|
||||||
// closeButton: true,
|
|
||||||
// closeOnClick: true,
|
|
||||||
// className: "custom-popup"
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// this.pos_marker.bindPopup(markerPopup);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set_pos(lat: number, lon: number, alt : number) {
|
set_pos(lat: number, lon: number, alt : number) {
|
||||||
@@ -529,11 +452,11 @@ export class Aircraft {
|
|||||||
this.pos = pos;
|
this.pos = pos;
|
||||||
let old_pos = this.pos_marker.getLatLng();
|
let old_pos = this.pos_marker.getLatLng();
|
||||||
if (pos.lat != old_pos.lat || pos.lng != old_pos.lng) {
|
if (pos.lat != old_pos.lat || pos.lng != old_pos.lng) {
|
||||||
let angle = getAngle(old_pos, pos);
|
let angle = get_aircraft_angle(old_pos, pos);
|
||||||
this.set_angle(angle)
|
this.set_angle(angle)
|
||||||
this.pos_marker.setLatLng(pos);
|
this.pos_marker.setLatLng(pos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/*{
|
/*{
|
||||||
"icao": "780FBD",
|
"icao": "780FBD",
|
||||||
@@ -562,58 +485,49 @@ export class Aircraft {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}*/
|
}*/
|
||||||
append_path() {
|
async load_track_data() {
|
||||||
let map = app.leaflet_map.map;
|
const res = await axios.post(`${baseURL}/get_aircraft_track_list`, {
|
||||||
axios.post(`${baseURL}/get_aircraft_track_list`, {
|
|
||||||
data_source_key: this.data_source.key,
|
data_source_key: this.data_source.key,
|
||||||
icao: this.icao,
|
icao: this.icao,
|
||||||
last_size: this.last_size
|
last_size: this.last_size
|
||||||
}).then(res => {
|
})
|
||||||
if(res.data){
|
if (!res.data) {
|
||||||
let list = res.data.list;
|
return []
|
||||||
let end_seq = res.data.end_seq;
|
}
|
||||||
let sta_seq = res.data.sta_seq;
|
let list = res.data.list;
|
||||||
let size = res.data.size;
|
let end_seq = res.data.end_seq;
|
||||||
let old_seq = this.last_size;
|
let sta_seq = res.data.sta_seq;
|
||||||
// end_seq 34
|
let old_seq = this.last_size;
|
||||||
// sta_seq 34
|
if (sta_seq != old_seq) {
|
||||||
// size 0
|
return []
|
||||||
// list.size: 0
|
}
|
||||||
// this.data_list.length: 34
|
this.last_size = end_seq;
|
||||||
|
this.data_model.append_track_points(list)
|
||||||
|
this.data_list.push(...list);
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
// 简单判断 不是那次请求过来的 直接舍弃
|
append_path() {
|
||||||
if(sta_seq != old_seq){
|
let map = app.leaflet_map.map;
|
||||||
return;
|
this.load_track_data().then(list => {
|
||||||
}
|
list.forEach((data, i) => {
|
||||||
this.last_size = end_seq;
|
let point = new Aircraft_Track_Point(data);
|
||||||
|
point.setColor(lightenColor(this.data_source.color));
|
||||||
|
if (map) {
|
||||||
|
|
||||||
|
|
||||||
// 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));
|
|
||||||
point.addTo(map);
|
point.addTo(map);
|
||||||
this.track_point_list.push(point)
|
}
|
||||||
this.track_path.addLatLng(point.getLatLng());
|
this.track_point_list.push(point)
|
||||||
});
|
this.track_path.addLatLng(point.getLatLng());
|
||||||
this.track_point_list.forEach((data, i) => {
|
});
|
||||||
|
this.track_point_list.forEach((data, i) => {
|
||||||
|
if (map) {
|
||||||
data.addTo(map)
|
data.addTo(map)
|
||||||
});
|
}
|
||||||
this.data_list.push(...list);
|
});
|
||||||
|
if (map) {
|
||||||
this.track_path.addTo(map)
|
this.track_path.addTo(map)
|
||||||
this.refresh_pos_by_track();
|
|
||||||
}
|
}
|
||||||
|
this.refresh_pos_by_track();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -633,18 +547,18 @@ export class Aircraft {
|
|||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
|
const map = app.leaflet_map?.map;
|
||||||
if (this.data_source.aircraft_show) {
|
if (this.data_source.aircraft_show) {
|
||||||
//console.log("aircraft refresh show")
|
//console.log("aircraft refresh show")
|
||||||
if (app.leaflet_map){
|
if (map){
|
||||||
// app.leaflet_map.map.removeLayer(this.pos_marker);
|
// app.leaflet_map.map.removeLayer(this.pos_marker);
|
||||||
if(app.leaflet_map.map){
|
this.pos_marker.addTo(map);
|
||||||
this.pos_marker.addTo(app.leaflet_map.map);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//console.log("aircraft refresh hide")
|
//console.log("aircraft refresh hide")
|
||||||
app.leaflet_map.map.removeLayer(this.pos_marker);
|
if (map) {
|
||||||
|
map.removeLayer(this.pos_marker);
|
||||||
|
}
|
||||||
this.hide_path();
|
this.hide_path();
|
||||||
}
|
}
|
||||||
if(this.need_refresh()){
|
if(this.need_refresh()){
|
||||||
|
|||||||
@@ -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<string, any>
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import {Data_Source} from "../Data_Source/Data_Source.tsx";
|
|||||||
|
|
||||||
export class Base_Station {
|
export class Base_Station {
|
||||||
pos: L.LatLng | null = null
|
pos: L.LatLng | null = null
|
||||||
|
has_valid_position: boolean = false
|
||||||
max_plane_pos: L.LatLng | null = null
|
max_plane_pos: L.LatLng | null = null
|
||||||
circle: L.Circle = new L.Circle([0, 0], {
|
circle: L.Circle = new L.Circle([0, 0], {
|
||||||
color: 'blue',
|
color: 'blue',
|
||||||
@@ -141,7 +142,14 @@ export class Base_Station {
|
|||||||
this.show_max_distance.remove();
|
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.pos = new L.LatLng(lat, lon);
|
||||||
this.that.setLatLng(this.pos);
|
this.that.setLatLng(this.pos);
|
||||||
this.circle.setLatLng(this.pos)
|
this.circle.setLatLng(this.pos)
|
||||||
|
|||||||
@@ -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<string, Aircraft_Entity_Record> = new Map();
|
||||||
|
base_station_entity_map: Map<string, Base_Station_Entity_Record> = 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<Cesium_Graphics_Config>;
|
||||||
|
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<string>();
|
||||||
|
const alive_base_stations = new Set<string>();
|
||||||
|
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<string>) {
|
||||||
|
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("<br>");
|
||||||
|
}
|
||||||
|
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("<br>");
|
||||||
|
}
|
||||||
|
track_point_description(point: Aircraft_Track_Point_Model) {
|
||||||
|
const extra = Object.entries(point.properties).map(([key, value]) => `${key}: ${value}`).join("<br>");
|
||||||
|
return [
|
||||||
|
`时间: ${point.timestamp || ""}`,
|
||||||
|
`纬度: ${point.lat.toFixed(6)}`,
|
||||||
|
`经度: ${point.lon.toFixed(6)}`,
|
||||||
|
`高度: ${point.alt}`,
|
||||||
|
extra
|
||||||
|
].filter(Boolean).join("<br>");
|
||||||
|
}
|
||||||
|
render_performance_panel() {
|
||||||
|
if (!this.graphics_config.debugShowFramesPerSecond) return null;
|
||||||
|
const info = this.performance_info;
|
||||||
|
return (
|
||||||
|
<div style={{position: "absolute", left: 12, top: 92, zIndex: 2, minWidth: 170, maxWidth: 260, padding: "8px 10px", borderRadius: 4, background: "rgba(30, 30, 30, 0.72)", color: "#f6f6f6", fontSize: 12, lineHeight: 1.45, pointerEvents: "none"}}>
|
||||||
|
<div style={{fontWeight: 700, color: "#fff000"}}>{`FPS ${info.fps}`}</div>
|
||||||
|
<div>{`Frame ${info.frame_ms.toFixed(1)} ms`}</div>
|
||||||
|
<div>{`CPU估算 ${info.cpu_pressure}%`}</div>
|
||||||
|
<div>{`JS堆 ${info.js_heap_mb === null ? "N/A" : `${info.js_heap_mb.toFixed(1)} MB`}`}</div>
|
||||||
|
<div>{`Tiles ${info.tile_state || "N/A"}`}</div>
|
||||||
|
<div style={{color: "#ff8a33", wordBreak: "break-word"}}>{`GPU ${info.gpu_renderer || "N/A"}`}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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 (
|
||||||
|
<div style={{width: "100%", height: "100%", position: "relative"}}>
|
||||||
|
<AircraftInfo></AircraftInfo>
|
||||||
|
<DataSourceShow></DataSourceShow>
|
||||||
|
<div id={this.container_id} style={{width: "100%", height: "100%"}}/>
|
||||||
|
<div style={{position: "absolute", left: 64, top: 16, zIndex: 2, display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap", maxWidth: "calc(100% - 180px)"}}>
|
||||||
|
<Select
|
||||||
|
style={{width: 180}}
|
||||||
|
value={this.current_imagery_key}
|
||||||
|
options={imagery_options}
|
||||||
|
onChange={(key) => this.change_imagery_source(key)}
|
||||||
|
/>
|
||||||
|
<Segmented
|
||||||
|
value={this.scene_mode}
|
||||||
|
options={[
|
||||||
|
{label: "2D", value: "2d"},
|
||||||
|
{label: "2.5D", value: "2.5d"},
|
||||||
|
{label: "3D", value: "3d"}
|
||||||
|
]}
|
||||||
|
onChange={(value) => this.change_scene_mode(value as Scene_Mode_Key)}
|
||||||
|
/>
|
||||||
|
<Button type="primary" icon={<SaveOutlined />} onClick={() => this.save_current_map_view()}>确定</Button>
|
||||||
|
<Space.Compact>
|
||||||
|
<Tooltip title="定位选中飞机">
|
||||||
|
<Button icon={<AimOutlined />} onClick={() => this.locate_selected()} disabled={!this.selected_aircraft}/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="跟随选中飞机">
|
||||||
|
<Button icon={<EyeOutlined />} onClick={() => this.follow_selected()} disabled={!this.selected_aircraft}/>
|
||||||
|
</Tooltip>
|
||||||
|
</Space.Compact>
|
||||||
|
</div>
|
||||||
|
{this.render_performance_panel()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type Cesium_Map_View_Props = {
|
||||||
|
initialSceneMode?: Scene_Mode_Key
|
||||||
|
}
|
||||||
|
export function Cesium_Map_View({initialSceneMode = "3d"}: Cesium_Map_View_Props) {
|
||||||
|
const map_ref = React.useRef<Cesium_Map | null>(null);
|
||||||
|
if (!map_ref.current) {
|
||||||
|
map_ref.current = new Cesium_Map(initialSceneMode);
|
||||||
|
}
|
||||||
|
const Map_Component = map_ref.current.x;
|
||||||
|
return <Map_Component />;
|
||||||
|
}
|
||||||
|
export default Cesium_Map_View;
|
||||||
@@ -0,0 +1,310 @@
|
|||||||
|
import React from "react";
|
||||||
|
import {Button, InputNumber, message, Slider, Space, Switch, Tooltip} from "antd";
|
||||||
|
import {QuestionCircleOutlined} from "@ant-design/icons";
|
||||||
|
import axios from "axios";
|
||||||
|
import enhance from "../core/enhance.tsx";
|
||||||
|
import {col_style, row_style, setting_style} from "../Global.tsx";
|
||||||
|
|
||||||
|
export type Cesium_Graphics_Config = {
|
||||||
|
debugShowFramesPerSecond: boolean
|
||||||
|
targetFrameRate: number
|
||||||
|
useBrowserRecommendedResolution: boolean
|
||||||
|
resolutionScale: number
|
||||||
|
msaaSamples: number
|
||||||
|
fxaa: boolean
|
||||||
|
shadows: boolean
|
||||||
|
enableLighting: boolean
|
||||||
|
maximumScreenSpaceError: number
|
||||||
|
terrainEnabledIn3D: boolean
|
||||||
|
terrainExaggeration: number
|
||||||
|
terrainLimitMaximumLevel: boolean
|
||||||
|
terrainMaximumLevel: number
|
||||||
|
terrainCacheTiles: number
|
||||||
|
}
|
||||||
|
export const default_cesium_graphics_config: Cesium_Graphics_Config = {
|
||||||
|
debugShowFramesPerSecond: true,
|
||||||
|
targetFrameRate: 30,
|
||||||
|
useBrowserRecommendedResolution: true,
|
||||||
|
resolutionScale: 0.7,
|
||||||
|
msaaSamples: 1,
|
||||||
|
fxaa: false,
|
||||||
|
shadows: false,
|
||||||
|
enableLighting: false,
|
||||||
|
maximumScreenSpaceError: 4,
|
||||||
|
terrainEnabledIn3D: false,
|
||||||
|
terrainExaggeration: 1.0,
|
||||||
|
terrainLimitMaximumLevel: true,
|
||||||
|
terrainMaximumLevel: 15,
|
||||||
|
terrainCacheTiles: 128
|
||||||
|
};
|
||||||
|
export const cesium_graphics_config_event = "ecap_cesium_graphics_config_changed";
|
||||||
|
type Number_Config_Key = "targetFrameRate" | "resolutionScale" | "msaaSamples" | "maximumScreenSpaceError" | "terrainExaggeration" | "terrainMaximumLevel" | "terrainCacheTiles"
|
||||||
|
type Boolean_Config_Key = Exclude<keyof Cesium_Graphics_Config, Number_Config_Key>
|
||||||
|
const graphics_presets: Record<"low" | "medium" | "high", Cesium_Graphics_Config> = {
|
||||||
|
low: {...default_cesium_graphics_config},
|
||||||
|
medium: {
|
||||||
|
debugShowFramesPerSecond: true,
|
||||||
|
targetFrameRate: 45,
|
||||||
|
useBrowserRecommendedResolution: true,
|
||||||
|
resolutionScale: 0.85,
|
||||||
|
msaaSamples: 1,
|
||||||
|
fxaa: true,
|
||||||
|
shadows: false,
|
||||||
|
enableLighting: false,
|
||||||
|
maximumScreenSpaceError: 3,
|
||||||
|
terrainEnabledIn3D: true,
|
||||||
|
terrainExaggeration: 1.0,
|
||||||
|
terrainLimitMaximumLevel: true,
|
||||||
|
terrainMaximumLevel: 15,
|
||||||
|
terrainCacheTiles: 128
|
||||||
|
},
|
||||||
|
high: {
|
||||||
|
debugShowFramesPerSecond: true,
|
||||||
|
targetFrameRate: 60,
|
||||||
|
useBrowserRecommendedResolution: false,
|
||||||
|
resolutionScale: 1.0,
|
||||||
|
msaaSamples: 4,
|
||||||
|
fxaa: true,
|
||||||
|
shadows: false,
|
||||||
|
enableLighting: true,
|
||||||
|
maximumScreenSpaceError: 2,
|
||||||
|
terrainEnabledIn3D: true,
|
||||||
|
terrainExaggeration: 1.0,
|
||||||
|
terrainLimitMaximumLevel: true,
|
||||||
|
terrainMaximumLevel: 17,
|
||||||
|
terrainCacheTiles: 256
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const graphics_help: Record<keyof Cesium_Graphics_Config, string[]> = {
|
||||||
|
debugShowFramesPerSecond: ["显示 FPS、每帧耗时和诊断信息。", "只用于判断卡顿,基本不影响画质。"],
|
||||||
|
targetFrameRate: ["限制 Cesium 渲染循环的目标帧率。", "越高越流畅,GPU 占用越高;集显建议 30。"],
|
||||||
|
useBrowserRecommendedResolution: ["开启后按 CSS 像素渲染,忽略高 DPI 物理像素倍率。", "高 DPI 屏幕上通常能显著降低集显压力。"],
|
||||||
|
resolutionScale: ["调整 WebGL 内部渲染分辨率。", "0.7 约等于渲染 49% 像素;越低越快但更模糊。"],
|
||||||
|
msaaSamples: ["几何边缘多重采样抗锯齿。", "1 表示关闭;数值越高边缘越平滑,GPU 开销越大。"],
|
||||||
|
fxaa: ["屏幕空间后处理抗锯齿。", "开销通常低于 MSAA,但可能让文字和细线变软。"],
|
||||||
|
shadows: ["控制模型、Primitive 和地形阴影。", "画面更真实,但对大量模型和地形开销明显。"],
|
||||||
|
enableLighting: ["根据场景光源给地球表面做明暗。", "有昼夜和山体受光效果,性能优先时建议关闭。"],
|
||||||
|
maximumScreenSpaceError: ["控制地球瓦片 LOD 的屏幕误差。", "数值越大越快但地面更粗糙;4 是集显平衡档。"],
|
||||||
|
terrainEnabledIn3D: ["3D 模式下加载 Terrarium 真实高程地形。", "会增加网络、解码、CPU 和 GPU 压力;2D/2.5D 默认不加载。"],
|
||||||
|
terrainExaggeration: ["控制地形垂直方向的视觉夸张倍率。", "1.0 为真实高度;0.0 压平地形;只改变视觉起伏,不增加高程精度。"],
|
||||||
|
terrainLimitMaximumLevel: ["限制 Terrarium 地形继续请求更深层级。", "只限制高程层级,减少无意义子瓦片请求。"],
|
||||||
|
terrainMaximumLevel: ["地形允许加载的最高瓦片层级。", "只有启用地形且限制层级时生效;越高越精细也越耗资源。"],
|
||||||
|
terrainCacheTiles: ["内存中保留的已解码地形瓦片数量。", "越大回看越快但占用更多内存;过小会增加重复请求和解码。"]
|
||||||
|
};
|
||||||
|
type Cesium_Graphics_Config_Json = {
|
||||||
|
debug_show_frames_per_second: boolean
|
||||||
|
target_frame_rate: number
|
||||||
|
use_browser_recommended_resolution: boolean
|
||||||
|
resolution_scale: number
|
||||||
|
msaa_samples: number
|
||||||
|
fxaa: boolean
|
||||||
|
shadows: boolean
|
||||||
|
enable_lighting: boolean
|
||||||
|
maximum_screen_space_error: number
|
||||||
|
terrain_enabled_in_3d: boolean
|
||||||
|
terrain_exaggeration: number
|
||||||
|
terrain_limit_maximum_level: boolean
|
||||||
|
terrain_maximum_level: number
|
||||||
|
terrain_cache_tiles: number
|
||||||
|
}
|
||||||
|
function number_value(value: unknown, fallback: number, min: number, max: number): number {
|
||||||
|
const next = Number(value);
|
||||||
|
if (!Number.isFinite(next)) return fallback;
|
||||||
|
return Math.min(max, Math.max(min, next));
|
||||||
|
}
|
||||||
|
function boolean_value(value: unknown, fallback: boolean): boolean {
|
||||||
|
return typeof value === "boolean" ? value : fallback;
|
||||||
|
}
|
||||||
|
export function normalize_cesium_graphics_config(data: Partial<Cesium_Graphics_Config>): Cesium_Graphics_Config {
|
||||||
|
return {
|
||||||
|
debugShowFramesPerSecond: boolean_value(data.debugShowFramesPerSecond, default_cesium_graphics_config.debugShowFramesPerSecond),
|
||||||
|
targetFrameRate: number_value(data.targetFrameRate, default_cesium_graphics_config.targetFrameRate, 1, 120),
|
||||||
|
useBrowserRecommendedResolution: boolean_value(data.useBrowserRecommendedResolution, default_cesium_graphics_config.useBrowserRecommendedResolution),
|
||||||
|
resolutionScale: number_value(data.resolutionScale, default_cesium_graphics_config.resolutionScale, 0.2, 1.5),
|
||||||
|
msaaSamples: number_value(data.msaaSamples, default_cesium_graphics_config.msaaSamples, 1, 8),
|
||||||
|
fxaa: boolean_value(data.fxaa, default_cesium_graphics_config.fxaa),
|
||||||
|
shadows: boolean_value(data.shadows, default_cesium_graphics_config.shadows),
|
||||||
|
enableLighting: boolean_value(data.enableLighting, default_cesium_graphics_config.enableLighting),
|
||||||
|
maximumScreenSpaceError: number_value(data.maximumScreenSpaceError, default_cesium_graphics_config.maximumScreenSpaceError, 1, 16),
|
||||||
|
terrainEnabledIn3D: boolean_value(data.terrainEnabledIn3D, default_cesium_graphics_config.terrainEnabledIn3D),
|
||||||
|
terrainExaggeration: number_value(data.terrainExaggeration, default_cesium_graphics_config.terrainExaggeration, 0, 5),
|
||||||
|
terrainLimitMaximumLevel: boolean_value(data.terrainLimitMaximumLevel, default_cesium_graphics_config.terrainLimitMaximumLevel),
|
||||||
|
terrainMaximumLevel: number_value(data.terrainMaximumLevel, default_cesium_graphics_config.terrainMaximumLevel, 0, 24),
|
||||||
|
terrainCacheTiles: Math.round(number_value(data.terrainCacheTiles, default_cesium_graphics_config.terrainCacheTiles, 16, 2048))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function from_server_config(data: Partial<Cesium_Graphics_Config_Json>): Cesium_Graphics_Config {
|
||||||
|
return normalize_cesium_graphics_config({
|
||||||
|
debugShowFramesPerSecond: data.debug_show_frames_per_second,
|
||||||
|
targetFrameRate: data.target_frame_rate,
|
||||||
|
useBrowserRecommendedResolution: data.use_browser_recommended_resolution,
|
||||||
|
resolutionScale: data.resolution_scale,
|
||||||
|
msaaSamples: data.msaa_samples,
|
||||||
|
fxaa: data.fxaa,
|
||||||
|
shadows: data.shadows,
|
||||||
|
enableLighting: data.enable_lighting,
|
||||||
|
maximumScreenSpaceError: data.maximum_screen_space_error,
|
||||||
|
terrainEnabledIn3D: data.terrain_enabled_in_3d,
|
||||||
|
terrainExaggeration: data.terrain_exaggeration,
|
||||||
|
terrainLimitMaximumLevel: data.terrain_limit_maximum_level,
|
||||||
|
terrainMaximumLevel: data.terrain_maximum_level,
|
||||||
|
terrainCacheTiles: data.terrain_cache_tiles
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function to_server_config(config: Cesium_Graphics_Config): Cesium_Graphics_Config_Json {
|
||||||
|
const normalized = normalize_cesium_graphics_config(config);
|
||||||
|
return {
|
||||||
|
debug_show_frames_per_second: normalized.debugShowFramesPerSecond,
|
||||||
|
target_frame_rate: normalized.targetFrameRate,
|
||||||
|
use_browser_recommended_resolution: normalized.useBrowserRecommendedResolution,
|
||||||
|
resolution_scale: normalized.resolutionScale,
|
||||||
|
msaa_samples: normalized.msaaSamples,
|
||||||
|
fxaa: normalized.fxaa,
|
||||||
|
shadows: normalized.shadows,
|
||||||
|
enable_lighting: normalized.enableLighting,
|
||||||
|
maximum_screen_space_error: normalized.maximumScreenSpaceError,
|
||||||
|
terrain_enabled_in_3d: normalized.terrainEnabledIn3D,
|
||||||
|
terrain_exaggeration: normalized.terrainExaggeration,
|
||||||
|
terrain_limit_maximum_level: normalized.terrainLimitMaximumLevel,
|
||||||
|
terrain_maximum_level: normalized.terrainMaximumLevel,
|
||||||
|
terrain_cache_tiles: normalized.terrainCacheTiles
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export async function load_cesium_graphics_config(): Promise<Cesium_Graphics_Config> {
|
||||||
|
const response = await axios.get<Cesium_Graphics_Config_Json>("/map/graphics");
|
||||||
|
return from_server_config(response.data);
|
||||||
|
}
|
||||||
|
export async function save_cesium_graphics_config(config: Cesium_Graphics_Config): Promise<Cesium_Graphics_Config> {
|
||||||
|
const response = await axios.post<Cesium_Graphics_Config_Json>("/map/graphics", to_server_config(config));
|
||||||
|
const normalized = from_server_config(response.data);
|
||||||
|
emit_cesium_graphics_config(normalized);
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
export function emit_cesium_graphics_config(config: Cesium_Graphics_Config) {
|
||||||
|
const normalized = normalize_cesium_graphics_config(config);
|
||||||
|
window.dispatchEvent(new CustomEvent<Cesium_Graphics_Config>(cesium_graphics_config_event, {detail: normalized}));
|
||||||
|
}
|
||||||
|
export class Cesium_Render_Settings extends enhance.Base {
|
||||||
|
config: Cesium_Graphics_Config = {...default_cesium_graphics_config};
|
||||||
|
async on_mount() {
|
||||||
|
this.config = await load_cesium_graphics_config();
|
||||||
|
this.flush();
|
||||||
|
}
|
||||||
|
set_number(key: Number_Config_Key, value: number | null) {
|
||||||
|
if (value === null) return;
|
||||||
|
this.config = normalize_cesium_graphics_config({...this.config, [key]: value});
|
||||||
|
emit_cesium_graphics_config(this.config);
|
||||||
|
this.flush();
|
||||||
|
}
|
||||||
|
set_boolean(key: Boolean_Config_Key, value: boolean) {
|
||||||
|
this.config = normalize_cesium_graphics_config({...this.config, [key]: value});
|
||||||
|
emit_cesium_graphics_config(this.config);
|
||||||
|
this.flush();
|
||||||
|
}
|
||||||
|
reset() {
|
||||||
|
this.config = {...default_cesium_graphics_config};
|
||||||
|
emit_cesium_graphics_config(this.config);
|
||||||
|
this.flush();
|
||||||
|
}
|
||||||
|
apply_preset(key: keyof typeof graphics_presets) {
|
||||||
|
this.config = {...graphics_presets[key]};
|
||||||
|
emit_cesium_graphics_config(this.config);
|
||||||
|
this.flush();
|
||||||
|
}
|
||||||
|
async save() {
|
||||||
|
this.config = await save_cesium_graphics_config(this.config);
|
||||||
|
message.success("Cesium画质设置已保存");
|
||||||
|
this.flush();
|
||||||
|
}
|
||||||
|
render_label(label: string, key: keyof Cesium_Graphics_Config) {
|
||||||
|
return (
|
||||||
|
<Space size={4}>
|
||||||
|
<span>{label}</span>
|
||||||
|
<Tooltip placement="right" title={<div style={{maxWidth: 360}}>{graphics_help[key].map((line) => <div key={line}>{line}</div>)}</div>}>
|
||||||
|
<QuestionCircleOutlined style={{color: "#1677ff"}} />
|
||||||
|
</Tooltip>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
render_switch(label: string, key: Boolean_Config_Key) {
|
||||||
|
return (
|
||||||
|
<Space style={{display: "flex", justifyContent: "space-between", width: 280}}>
|
||||||
|
{this.render_label(label, key)}
|
||||||
|
<Switch checked={this.config[key]} onChange={(checked) => this.set_boolean(key, checked)} />
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
render_number(label: string, key: Number_Config_Key, min: number, max: number, step: number) {
|
||||||
|
return (
|
||||||
|
<InputNumber
|
||||||
|
addonBefore={this.render_label(label, key)}
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
step={step}
|
||||||
|
value={this.config[key]}
|
||||||
|
style={{width: 280}}
|
||||||
|
onChange={(value) => this.set_number(key, value)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
render_terrain_exaggeration() {
|
||||||
|
const disabled = !this.config.terrainEnabledIn3D;
|
||||||
|
return (
|
||||||
|
<Space direction="vertical" size={4} style={{width: 280}}>
|
||||||
|
{this.render_label("地形起伏倍率", "terrainExaggeration")}
|
||||||
|
<Space style={{width: 280}}>
|
||||||
|
<Slider
|
||||||
|
min={0}
|
||||||
|
max={5}
|
||||||
|
step={0.1}
|
||||||
|
disabled={disabled}
|
||||||
|
value={this.config.terrainExaggeration}
|
||||||
|
style={{width: 170}}
|
||||||
|
onChange={(value) => this.set_number("terrainExaggeration", value)}
|
||||||
|
/>
|
||||||
|
<InputNumber
|
||||||
|
min={0}
|
||||||
|
max={5}
|
||||||
|
step={0.1}
|
||||||
|
disabled={disabled}
|
||||||
|
value={this.config.terrainExaggeration}
|
||||||
|
style={{width: 90}}
|
||||||
|
onChange={(value) => this.set_number("terrainExaggeration", value)}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
render(props: any) {
|
||||||
|
return (
|
||||||
|
<div style={setting_style}>
|
||||||
|
<h2 style={row_style}>Cesium画质设置</h2>
|
||||||
|
<Space direction="vertical" size={8} style={col_style}>
|
||||||
|
<Space>
|
||||||
|
<Button onClick={() => this.apply_preset("low")}>低画质</Button>
|
||||||
|
<Button onClick={() => this.apply_preset("medium")}>中画质</Button>
|
||||||
|
<Button onClick={() => this.apply_preset("high")}>高画质</Button>
|
||||||
|
</Space>
|
||||||
|
{this.render_switch("显示FPS", "debugShowFramesPerSecond")}
|
||||||
|
{this.render_number("目标帧率", "targetFrameRate", 1, 120, 1)}
|
||||||
|
{this.render_switch("浏览器推荐分辨率", "useBrowserRecommendedResolution")}
|
||||||
|
{this.render_number("分辨率缩放", "resolutionScale", 0.2, 1.5, 0.1)}
|
||||||
|
{this.render_number("MSAA采样", "msaaSamples", 1, 8, 1)}
|
||||||
|
{this.render_switch("FXAA", "fxaa")}
|
||||||
|
{this.render_switch("阴影", "shadows")}
|
||||||
|
{this.render_switch("地球光照", "enableLighting")}
|
||||||
|
{this.render_number("地球细节误差", "maximumScreenSpaceError", 1, 16, 1)}
|
||||||
|
{this.render_switch("3D启用地形", "terrainEnabledIn3D")}
|
||||||
|
{this.render_terrain_exaggeration()}
|
||||||
|
{this.render_switch("限制地形层级", "terrainLimitMaximumLevel")}
|
||||||
|
{this.render_number("地形最大层级", "terrainMaximumLevel", 0, 24, 1)}
|
||||||
|
{this.render_number("地形缓存瓦片数", "terrainCacheTiles", 16, 2048, 16)}
|
||||||
|
<Space>
|
||||||
|
<Button onClick={() => this.reset()}>恢复默认画质</Button>
|
||||||
|
<Button type="primary" onClick={() => this.save()}>确定</Button>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
-7
@@ -21,6 +21,7 @@ import {Data_Source_Show} from "../Data_Source/Data_Source_Show.tsx";
|
|||||||
import {app} from "../App.tsx";
|
import {app} from "../App.tsx";
|
||||||
//import {Base_Station} from "./Base_Station.tsx";
|
//import {Base_Station} from "./Base_Station.tsx";
|
||||||
import {Data_Source} from "../Data_Source/Data_Source.tsx";
|
import {Data_Source} from "../Data_Source/Data_Source.tsx";
|
||||||
|
import {load_map_resources} from "./Map_Resources.tsx";
|
||||||
|
|
||||||
|
|
||||||
export class POS {
|
export class POS {
|
||||||
@@ -38,6 +39,7 @@ export class Leaflet_Map extends enhance.Base {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
aircraft_info_show: Aircraft_Info_Show = new Aircraft_Info_Show(this);
|
aircraft_info_show: Aircraft_Info_Show = new Aircraft_Info_Show(this);
|
||||||
data_source_config: Data_Source_Config = null
|
data_source_config: Data_Source_Config = null
|
||||||
|
tile_layer: L.TileLayer | null = null
|
||||||
private savedCenter: L.LatLng | null = null;
|
private savedCenter: L.LatLng | null = null;
|
||||||
private savedZoom: number | null = null;
|
private savedZoom: number | null = null;
|
||||||
intervalId: number | null = null;
|
intervalId: number | null = null;
|
||||||
@@ -89,6 +91,7 @@ export class Leaflet_Map extends enhance.Base {
|
|||||||
on_mount: () => void = () => {
|
on_mount: () => void = () => {
|
||||||
console.log("地图加载");
|
console.log("地图加载");
|
||||||
this.map = this.load_map();
|
this.map = this.load_map();
|
||||||
|
this.load_imagery_layer();
|
||||||
this.map.on("click", this.clear_active_aircraft_by_map_click);
|
this.map.on("click", this.clear_active_aircraft_by_map_click);
|
||||||
this.map.whenReady(() => {
|
this.map.whenReady(() => {
|
||||||
|
|
||||||
@@ -138,6 +141,7 @@ export class Leaflet_Map extends enhance.Base {
|
|||||||
this.savedZoom = this.map.getZoom();
|
this.savedZoom = this.map.getZoom();
|
||||||
this.map.remove()
|
this.map.remove()
|
||||||
}
|
}
|
||||||
|
this.tile_layer = null
|
||||||
let map = this.map;
|
let map = this.map;
|
||||||
map.eachLayer(function(layer) {
|
map.eachLayer(function(layer) {
|
||||||
map.removeLayer(layer);
|
map.removeLayer(layer);
|
||||||
@@ -283,13 +287,6 @@ export class Leaflet_Map extends enhance.Base {
|
|||||||
// preferCanvas: true, // 可选
|
// preferCanvas: true, // 可选
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2) 加载瓦片(等价 TileLayer)
|
|
||||||
const tile = L.tileLayer(`http://${host}/tiles/{z}/{x}/{y}.png`, {
|
|
||||||
minNativeZoom: 3,
|
|
||||||
maxNativeZoom: 9,
|
|
||||||
});
|
|
||||||
tile.addTo(map);
|
|
||||||
|
|
||||||
// 3) 等价 MapContent: fitBounds + on_mount
|
// 3) 等价 MapContent: fitBounds + on_mount
|
||||||
// INIT_EXTENT 必须是 Leaflet 接受的 bounds 格式:
|
// INIT_EXTENT 必须是 Leaflet 接受的 bounds 格式:
|
||||||
// [[southLat, westLng], [northLat, eastLng]]
|
// [[southLat, westLng], [northLat, eastLng]]
|
||||||
@@ -299,5 +296,22 @@ export class Leaflet_Map extends enhance.Base {
|
|||||||
});
|
});
|
||||||
return map;
|
return map;
|
||||||
};
|
};
|
||||||
|
load_imagery_layer = async () => {
|
||||||
|
const resources = await load_map_resources();
|
||||||
|
if (!this.map) return;
|
||||||
|
if (this.tile_layer) {
|
||||||
|
this.map.removeLayer(this.tile_layer);
|
||||||
|
}
|
||||||
|
const imagery = resources.imagery;
|
||||||
|
this.tile_layer = L.tileLayer(imagery.url, {
|
||||||
|
minZoom: imagery.minimum_level,
|
||||||
|
maxZoom: imagery.maximum_level,
|
||||||
|
minNativeZoom: imagery.minimum_level,
|
||||||
|
maxNativeZoom: imagery.maximum_level,
|
||||||
|
tileSize: imagery.tile_size,
|
||||||
|
tms: imagery.y_axis === "tms",
|
||||||
|
});
|
||||||
|
this.tile_layer.addTo(this.map);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
export type Map_Tile_Y_Axis = "xyz" | "tms"
|
||||||
|
export type Map_Tile_Projection = "web_mercator"
|
||||||
|
export type Map_Tile_Encoding = "terrarium" | string
|
||||||
|
export type Map_Tile_Metadata = {
|
||||||
|
url: string
|
||||||
|
minimum_level: number
|
||||||
|
maximum_level: number
|
||||||
|
tile_size: number
|
||||||
|
projection: Map_Tile_Projection
|
||||||
|
y_axis: Map_Tile_Y_Axis
|
||||||
|
encoding?: Map_Tile_Encoding
|
||||||
|
credit?: string
|
||||||
|
}
|
||||||
|
export type Map_Imagery_Source_Metadata = {
|
||||||
|
key: string
|
||||||
|
name: string
|
||||||
|
resource: Map_Tile_Metadata
|
||||||
|
}
|
||||||
|
export type Map_Resources_Metadata = {
|
||||||
|
current_imagery_key: string
|
||||||
|
imagery_sources: Map_Imagery_Source_Metadata[]
|
||||||
|
imagery: Map_Tile_Metadata
|
||||||
|
google_imagery?: Map_Tile_Metadata
|
||||||
|
terrain: Map_Tile_Metadata
|
||||||
|
}
|
||||||
|
export async function load_map_resources(): Promise<Map_Resources_Metadata> {
|
||||||
|
const response = await axios.get<Map_Resources_Metadata>("/map/resources")
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
export function url_template_for_y_axis(resource: Map_Tile_Metadata): string {
|
||||||
|
if (resource.y_axis === "tms") {
|
||||||
|
return resource.url.replace("{y}", "{reverseY}")
|
||||||
|
}
|
||||||
|
return resource.url
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
export type Scene_Mode_Key = "2d" | "2.5d" | "3d"
|
||||||
|
export type Map_Camera_View = {
|
||||||
|
longitude: number
|
||||||
|
latitude: number
|
||||||
|
height: number
|
||||||
|
heading: number
|
||||||
|
pitch: number
|
||||||
|
roll: number
|
||||||
|
}
|
||||||
|
export type Map_View_Config = {
|
||||||
|
current_imagery_key: string
|
||||||
|
scene_mode: Scene_Mode_Key
|
||||||
|
camera: Map_Camera_View
|
||||||
|
}
|
||||||
|
export const default_map_camera_view: Map_Camera_View = {
|
||||||
|
longitude: 121.27,
|
||||||
|
latitude: 37.41,
|
||||||
|
height: 300000,
|
||||||
|
heading: 0,
|
||||||
|
pitch: -90,
|
||||||
|
roll: 0
|
||||||
|
};
|
||||||
|
export const default_map_view_config: Map_View_Config = {
|
||||||
|
current_imagery_key: "imagery",
|
||||||
|
scene_mode: "3d",
|
||||||
|
camera: {...default_map_camera_view}
|
||||||
|
};
|
||||||
|
function number_value(value: unknown, fallback: number): number {
|
||||||
|
const next = Number(value);
|
||||||
|
return Number.isFinite(next) ? next : fallback;
|
||||||
|
}
|
||||||
|
function scene_mode_value(value: unknown, fallback: Scene_Mode_Key): Scene_Mode_Key {
|
||||||
|
return value === "2d" || value === "2.5d" || value === "3d" ? value : fallback;
|
||||||
|
}
|
||||||
|
function normalize_camera(data: Partial<Map_Camera_View> | undefined): Map_Camera_View {
|
||||||
|
return {
|
||||||
|
longitude: number_value(data?.longitude, default_map_camera_view.longitude),
|
||||||
|
latitude: number_value(data?.latitude, default_map_camera_view.latitude),
|
||||||
|
height: number_value(data?.height, default_map_camera_view.height),
|
||||||
|
heading: number_value(data?.heading, default_map_camera_view.heading),
|
||||||
|
pitch: number_value(data?.pitch, default_map_camera_view.pitch),
|
||||||
|
roll: number_value(data?.roll, default_map_camera_view.roll)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function normalize_map_view_config(data: Partial<Map_View_Config>): Map_View_Config {
|
||||||
|
return {
|
||||||
|
current_imagery_key: typeof data.current_imagery_key === "string" ? data.current_imagery_key : default_map_view_config.current_imagery_key,
|
||||||
|
scene_mode: scene_mode_value(data.scene_mode, default_map_view_config.scene_mode),
|
||||||
|
camera: normalize_camera(data.camera)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export async function load_map_view_config(): Promise<Map_View_Config> {
|
||||||
|
const response = await axios.get<Map_View_Config>("/map/view");
|
||||||
|
return normalize_map_view_config(response.data);
|
||||||
|
}
|
||||||
|
export async function save_map_view_config(config: Map_View_Config): Promise<Map_View_Config> {
|
||||||
|
const normalized = normalize_map_view_config(config);
|
||||||
|
const response = await axios.post<Map_View_Config>("/map/view", normalized);
|
||||||
|
return normalize_map_view_config(response.data);
|
||||||
|
}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
import * as Cesium from "cesium";
|
||||||
|
import type {Map_Tile_Metadata} from "./Map_Resources.tsx";
|
||||||
|
|
||||||
|
type Cached_Height_Tile = {
|
||||||
|
promise: Promise<Float32Array | undefined>
|
||||||
|
}
|
||||||
|
type Terrarium_Terrain_Provider_Options = {
|
||||||
|
maximum_level?: number
|
||||||
|
max_cached_height_tiles?: number
|
||||||
|
}
|
||||||
|
type Pending_Worker_Request = {
|
||||||
|
resolve: (value: Float32Array | undefined) => void
|
||||||
|
reject: (reason?: any) => void
|
||||||
|
}
|
||||||
|
type Worker_Response = {
|
||||||
|
id: number
|
||||||
|
buffer?: ArrayBuffer
|
||||||
|
missing?: boolean
|
||||||
|
aborted?: boolean
|
||||||
|
error?: string
|
||||||
|
}
|
||||||
|
export class Terrarium_Terrain_Provider {
|
||||||
|
provider: Cesium.TerrainProvider
|
||||||
|
private resource: Map_Tile_Metadata
|
||||||
|
private height_cache: Map<string, Cached_Height_Tile> = new Map()
|
||||||
|
private worker: Worker
|
||||||
|
private pending_worker_requests: Map<number, Pending_Worker_Request> = new Map()
|
||||||
|
private next_worker_request_id = 1
|
||||||
|
private maximum_level?: number
|
||||||
|
private max_cached_height_tiles: number
|
||||||
|
private destroyed = false
|
||||||
|
constructor(resource: Map_Tile_Metadata, options: Terrarium_Terrain_Provider_Options = {}) {
|
||||||
|
if (resource.projection !== "web_mercator") {
|
||||||
|
throw new Error(`Unsupported terrain projection: ${resource.projection}`)
|
||||||
|
}
|
||||||
|
if (resource.encoding !== "terrarium") {
|
||||||
|
throw new Error(`Unsupported terrain encoding: ${resource.encoding}`)
|
||||||
|
}
|
||||||
|
this.resource = resource
|
||||||
|
this.maximum_level = options.maximum_level
|
||||||
|
this.max_cached_height_tiles = options.max_cached_height_tiles || 128
|
||||||
|
this.worker = new Worker(new URL("./Terrarium_Terrain_Worker.ts", import.meta.url), {type: "module"})
|
||||||
|
this.worker.onmessage = (event: MessageEvent<Worker_Response>) => this.handle_worker_response(event.data)
|
||||||
|
this.worker.onerror = (event) => this.reject_all_worker_requests(new Error(event.message))
|
||||||
|
const provider = new Cesium.CustomHeightmapTerrainProvider({
|
||||||
|
width: resource.tile_size,
|
||||||
|
height: resource.tile_size,
|
||||||
|
tilingScheme: new Cesium.WebMercatorTilingScheme(),
|
||||||
|
callback: () => new Float32Array(resource.tile_size * resource.tile_size)
|
||||||
|
})
|
||||||
|
;(provider as any).requestTileGeometry = (x: number, y: number, level: number, request?: Cesium.Request) => {
|
||||||
|
return this.request_tile_geometry(x, y, level, request)
|
||||||
|
}
|
||||||
|
;(provider as any).getTileDataAvailable = (x: number, y: number, level: number) => {
|
||||||
|
return this.get_tile_data_available(x, y, level)
|
||||||
|
}
|
||||||
|
this.provider = provider as unknown as Cesium.TerrainProvider
|
||||||
|
}
|
||||||
|
destroy() {
|
||||||
|
this.destroyed = true
|
||||||
|
this.resolve_all_worker_requests()
|
||||||
|
this.worker.terminate()
|
||||||
|
this.height_cache.clear()
|
||||||
|
}
|
||||||
|
private request_tile_geometry(x: number, y: number, level: number, request?: Cesium.Request): Promise<Cesium.TerrainData | undefined> | undefined {
|
||||||
|
if (this.destroyed || !this.is_level_available(level)) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return this.load_height_tile(x, y, level, request).then(heights => {
|
||||||
|
if (!heights || this.destroyed) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return new Cesium.HeightmapTerrainData({
|
||||||
|
buffer: new Float32Array(heights),
|
||||||
|
width: this.resource.tile_size,
|
||||||
|
height: this.resource.tile_size
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
private load_height_tile(x: number, y: number, level: number, request?: Cesium.Request): Promise<Float32Array | undefined> {
|
||||||
|
const key = `${level}/${x}/${y}`
|
||||||
|
const cached = this.height_cache.get(key)
|
||||||
|
if (cached) {
|
||||||
|
return cached.promise
|
||||||
|
}
|
||||||
|
const promise = this.decode_height_tile_in_worker(key, x, y, level, request).catch(error => {
|
||||||
|
this.height_cache.delete(key)
|
||||||
|
throw error
|
||||||
|
})
|
||||||
|
this.height_cache.set(key, {promise})
|
||||||
|
this.trim_cache()
|
||||||
|
return promise
|
||||||
|
}
|
||||||
|
private trim_cache() {
|
||||||
|
while (this.height_cache.size > this.max_cached_height_tiles) {
|
||||||
|
const first = this.height_cache.keys().next().value
|
||||||
|
if (first === undefined) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
this.height_cache.delete(first)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private decode_height_tile_in_worker(key: string, x: number, y: number, level: number, request?: Cesium.Request): Promise<Float32Array | undefined> {
|
||||||
|
const id = this.next_worker_request_id++
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.pending_worker_requests.set(id, {resolve, reject})
|
||||||
|
if (request) {
|
||||||
|
request.cancelFunction = () => {
|
||||||
|
const pending = this.pending_worker_requests.get(id)
|
||||||
|
if (!pending) return
|
||||||
|
this.pending_worker_requests.delete(id)
|
||||||
|
this.height_cache.delete(key)
|
||||||
|
this.worker.postMessage({type: "cancel", id})
|
||||||
|
pending.resolve(undefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.worker.postMessage({type: "decode", id, url: this.tile_url(x, y, level), tile_size: this.resource.tile_size})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
private handle_worker_response(response: Worker_Response) {
|
||||||
|
const pending = this.pending_worker_requests.get(response.id)
|
||||||
|
if (!pending) return
|
||||||
|
this.pending_worker_requests.delete(response.id)
|
||||||
|
if (response.error) {
|
||||||
|
pending.reject(new Error(response.error))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (response.missing || response.aborted || !response.buffer) {
|
||||||
|
pending.resolve(undefined)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pending.resolve(new Float32Array(response.buffer))
|
||||||
|
}
|
||||||
|
private resolve_all_worker_requests() {
|
||||||
|
for (const pending of this.pending_worker_requests.values()) {
|
||||||
|
pending.resolve(undefined)
|
||||||
|
}
|
||||||
|
this.pending_worker_requests.clear()
|
||||||
|
}
|
||||||
|
private reject_all_worker_requests(error: Error) {
|
||||||
|
for (const pending of this.pending_worker_requests.values()) {
|
||||||
|
pending.reject(error)
|
||||||
|
}
|
||||||
|
this.pending_worker_requests.clear()
|
||||||
|
}
|
||||||
|
private get_tile_data_available(x: number, y: number, level: number): boolean | undefined {
|
||||||
|
if (level < this.resource.minimum_level) return false
|
||||||
|
if (this.maximum_level !== undefined) return level <= this.maximum_level
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
private is_level_available(level: number): boolean {
|
||||||
|
if (level < this.resource.minimum_level) return false
|
||||||
|
if (this.maximum_level === undefined) return true
|
||||||
|
return level <= this.maximum_level
|
||||||
|
}
|
||||||
|
private tile_url(x: number, y: number, level: number): string {
|
||||||
|
const tile_y = this.resource.y_axis === "tms" ? Math.pow(2, level) - 1 - y : y
|
||||||
|
return this.resource.url
|
||||||
|
.replace("{z}", String(level))
|
||||||
|
.replace("{x}", String(x))
|
||||||
|
.replace("{y}", String(tile_y))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
type Decode_Message = {
|
||||||
|
type: "decode"
|
||||||
|
id: number
|
||||||
|
url: string
|
||||||
|
tile_size: number
|
||||||
|
}
|
||||||
|
type Cancel_Message = {
|
||||||
|
type: "cancel"
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
type Worker_Message = Decode_Message | Cancel_Message
|
||||||
|
type Worker_Context = {
|
||||||
|
onmessage: ((event: MessageEvent<Worker_Message>) => void) | null
|
||||||
|
postMessage: (message: any, transfer?: ArrayBuffer[]) => void
|
||||||
|
}
|
||||||
|
const worker_context = self as unknown as Worker_Context;
|
||||||
|
const controllers: Map<number, AbortController> = new Map();
|
||||||
|
worker_context.onmessage = (event: MessageEvent<Worker_Message>) => {
|
||||||
|
const message = event.data;
|
||||||
|
if (message.type === "cancel") {
|
||||||
|
controllers.get(message.id)?.abort();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
decode(message);
|
||||||
|
};
|
||||||
|
async function decode(message: Decode_Message) {
|
||||||
|
const controller = new AbortController();
|
||||||
|
controllers.set(message.id, controller);
|
||||||
|
try {
|
||||||
|
const response = await fetch(message.url, {signal: controller.signal, cache: "force-cache"});
|
||||||
|
if (response.status === 404) {
|
||||||
|
worker_context.postMessage({id: message.id, missing: true});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`terrain tile request failed: ${response.status}`);
|
||||||
|
}
|
||||||
|
const heights = await decode_terrarium_png(await response.blob(), message.tile_size);
|
||||||
|
const buffer = heights.buffer as ArrayBuffer;
|
||||||
|
worker_context.postMessage({id: message.id, buffer}, [buffer]);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (controller.signal.aborted) {
|
||||||
|
worker_context.postMessage({id: message.id, aborted: true});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
worker_context.postMessage({id: message.id, error: error instanceof Error ? error.message : String(error)});
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
controllers.delete(message.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function decode_terrarium_png(blob: Blob, tile_size: number): Promise<Float32Array> {
|
||||||
|
const bitmap = await createImageBitmap(blob);
|
||||||
|
try {
|
||||||
|
if (typeof OffscreenCanvas === "undefined") {
|
||||||
|
throw new Error("OffscreenCanvas is not available");
|
||||||
|
}
|
||||||
|
const canvas = new OffscreenCanvas(tile_size, tile_size);
|
||||||
|
const context = canvas.getContext("2d", {willReadFrequently: true});
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("worker canvas 2d context is not available");
|
||||||
|
}
|
||||||
|
context.drawImage(bitmap, 0, 0, tile_size, tile_size);
|
||||||
|
const image = context.getImageData(0, 0, tile_size, tile_size);
|
||||||
|
const heights = new Float32Array(tile_size * tile_size);
|
||||||
|
for (let i = 0, j = 0; i < heights.length; i++, j += 4) {
|
||||||
|
heights[i] = image.data[j] * 256 + image.data[j + 1] + image.data[j + 2] / 256 - 32768;
|
||||||
|
}
|
||||||
|
return heights;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
bitmap.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export {};
|
||||||
+5
-1
@@ -15,6 +15,7 @@ import { Source_Feed_Relation_Config } from "./Source_Feed_Relation/Source_Feed_
|
|||||||
import { Input_Port_Number } from "./A_Global.tsx";
|
import { Input_Port_Number } from "./A_Global.tsx";
|
||||||
import { PersistentScroll } from "./PersistentScroll.tsx";
|
import { PersistentScroll } from "./PersistentScroll.tsx";
|
||||||
import { External_Resources_Manager } from "./External_Resources_Manager/External_Resources_Manager.tsx";
|
import { External_Resources_Manager } from "./External_Resources_Manager/External_Resources_Manager.tsx";
|
||||||
|
import { Cesium_Render_Settings } from "./Map/Cesium_Render_Settings.tsx";
|
||||||
|
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
|
|
||||||
@@ -511,6 +512,7 @@ export class Settings extends enhance.Base {
|
|||||||
source_feed_relation_config: Source_Feed_Relation_Config | null = null;
|
source_feed_relation_config: Source_Feed_Relation_Config | null = null;
|
||||||
config_Import_Export_Upgrade: Online_Upgrade | null = null;
|
config_Import_Export_Upgrade: Online_Upgrade | null = null;
|
||||||
external_resources_manager: External_Resources_Manager | null = null;
|
external_resources_manager: External_Resources_Manager | null = null;
|
||||||
|
cesium_render_settings: Cesium_Render_Settings | null = null;
|
||||||
|
|
||||||
isRebooting: boolean = false;
|
isRebooting: boolean = false;
|
||||||
|
|
||||||
@@ -530,11 +532,12 @@ export class Settings extends enhance.Base {
|
|||||||
this.source_feed_relation_config = new Source_Feed_Relation_Config();
|
this.source_feed_relation_config = new Source_Feed_Relation_Config();
|
||||||
this.config_Import_Export_Upgrade = new Online_Upgrade();
|
this.config_Import_Export_Upgrade = new Online_Upgrade();
|
||||||
this.external_resources_manager = new External_Resources_Manager();
|
this.external_resources_manager = new External_Resources_Manager();
|
||||||
|
this.cesium_render_settings = new Cesium_Render_Settings();
|
||||||
};
|
};
|
||||||
|
|
||||||
render(props: any) {
|
render(props: any) {
|
||||||
// 第三步:渲染前校验子组件是否初始化完成
|
// 第三步:渲染前校验子组件是否初始化完成
|
||||||
if (!this.data_feed_config || !this.console || !this.device_config || !this.data_source_config || !this.mode_s_config || !this.source_feed_relation_config || !this.config_Import_Export_Upgrade || !this.external_resources_manager) {
|
if (!this.data_feed_config || !this.console || !this.device_config || !this.data_source_config || !this.mode_s_config || !this.source_feed_relation_config || !this.config_Import_Export_Upgrade || !this.external_resources_manager || !this.cesium_render_settings) {
|
||||||
return <div style={{ textAlign: 'center', padding: '20px' }}>设置组件初始化中...</div>;
|
return <div style={{ textAlign: 'center', padding: '20px' }}>设置组件初始化中...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -553,6 +556,7 @@ export class Settings extends enhance.Base {
|
|||||||
<this.source_feed_relation_config.x></this.source_feed_relation_config.x>
|
<this.source_feed_relation_config.x></this.source_feed_relation_config.x>
|
||||||
<this.external_resources_manager.x></this.external_resources_manager.x>
|
<this.external_resources_manager.x></this.external_resources_manager.x>
|
||||||
<this.config_Import_Export_Upgrade.x></this.config_Import_Export_Upgrade.x>
|
<this.config_Import_Export_Upgrade.x></this.config_Import_Export_Upgrade.x>
|
||||||
|
<this.cesium_render_settings.x></this.cesium_render_settings.x>
|
||||||
|
|
||||||
<br></br>
|
<br></br>
|
||||||
<div style={setting_style}>
|
<div style={setting_style}>
|
||||||
|
|||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
declare module "*.css";
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"checkJs": false,
|
"checkJs": false,
|
||||||
|
"ignoreDeprecations": "6.0",
|
||||||
"noEmit": true, // 或 "emitDeclarationOnly": true
|
"noEmit": true, // 或 "emitDeclarationOnly": true
|
||||||
"outDir": "./dist" // 避免文件覆盖
|
"outDir": "./dist" // 避免文件覆盖
|
||||||
},
|
},
|
||||||
|
|||||||
+115
-14
@@ -1,16 +1,129 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||||
|
const cesiumSource = path.join(__dirname, 'node_modules', 'cesium', 'Build', 'Cesium')
|
||||||
|
const modelSource = path.join(__dirname, 'model')
|
||||||
|
function copyDir(source, target) {
|
||||||
|
fs.mkdirSync(target, { recursive: true })
|
||||||
|
for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
|
||||||
|
const sourcePath = path.join(source, entry.name)
|
||||||
|
const targetPath = path.join(target, entry.name)
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
copyDir(sourcePath, targetPath)
|
||||||
|
} else {
|
||||||
|
fs.copyFileSync(sourcePath, targetPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function cesiumAssetsPlugin() {
|
||||||
|
let outDir = path.join(__dirname, 'wwwroot')
|
||||||
|
const serveStatic = (root, req, res, next) => {
|
||||||
|
const urlPath = decodeURIComponent((req.url || '').split('?')[0]).replace(/^\/+/, '')
|
||||||
|
const filePath = path.normalize(path.join(root, urlPath))
|
||||||
|
if (!filePath.startsWith(root) || !fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
||||||
|
next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const contentType = filePath.endsWith('.js') ? 'application/javascript' :
|
||||||
|
filePath.endsWith('.css') ? 'text/css' :
|
||||||
|
filePath.endsWith('.json') ? 'application/json' :
|
||||||
|
filePath.endsWith('.glb') ? 'model/gltf-binary' :
|
||||||
|
'application/octet-stream'
|
||||||
|
res.setHeader('Content-Type', contentType)
|
||||||
|
fs.createReadStream(filePath).pipe(res)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: 'ecap-cesium-assets',
|
||||||
|
configResolved(config) {
|
||||||
|
outDir = path.isAbsolute(config.build.outDir) ? config.build.outDir : path.join(config.root, config.build.outDir)
|
||||||
|
},
|
||||||
|
configureServer(server) {
|
||||||
|
server.middlewares.use('/ui/cesium', (req, res, next) => {
|
||||||
|
serveStatic(cesiumSource, req, res, next)
|
||||||
|
})
|
||||||
|
server.middlewares.use('/ui/model', (req, res, next) => {
|
||||||
|
serveStatic(modelSource, req, res, next)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
closeBundle() {
|
||||||
|
copyDir(cesiumSource, path.join(outDir, 'cesium'))
|
||||||
|
copyDir(modelSource, path.join(outDir, 'model'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const spaAliasPaths = new Set([
|
||||||
|
'/map',
|
||||||
|
'/map3d',
|
||||||
|
'/dsp',
|
||||||
|
'/aircraftlist',
|
||||||
|
'/settings',
|
||||||
|
'/ui/map',
|
||||||
|
'/ui/map3d',
|
||||||
|
'/ui/dsp',
|
||||||
|
'/ui/aircraftlist',
|
||||||
|
'/ui/settings'
|
||||||
|
])
|
||||||
|
function spaAliasesPlugin() {
|
||||||
|
return {
|
||||||
|
name: 'ecap-spa-aliases',
|
||||||
|
configureServer(server) {
|
||||||
|
server.middlewares.use(async (req, res, next) => {
|
||||||
|
const urlPath = decodeURIComponent((req.url || '').split('?')[0])
|
||||||
|
if (!spaAliasPaths.has(urlPath)) {
|
||||||
|
next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const indexPath = path.join(__dirname, 'index.html')
|
||||||
|
let html = fs.readFileSync(indexPath, 'utf-8')
|
||||||
|
html = await server.transformIndexHtml('/ui/index.html', html)
|
||||||
|
res.statusCode = 200
|
||||||
|
res.setHeader('Content-Type', 'text/html')
|
||||||
|
res.end(html)
|
||||||
|
} catch (error) {
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
base: '/ui/',
|
base: '/ui/',
|
||||||
publicDir: 'public', // 默认就是 public
|
publicDir: 'public', // 默认就是 public
|
||||||
plugins: [react()],
|
define: {
|
||||||
|
CESIUM_BASE_URL: JSON.stringify('/ui/cesium/')
|
||||||
|
},
|
||||||
|
plugins: [react(), cesiumAssetsPlugin(), spaAliasesPlugin()],
|
||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
|
'/map/imagery': {
|
||||||
|
target: 'http://127.0.0.1',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
'/map/terrain': {
|
||||||
|
target: 'http://127.0.0.1',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
'/map/resources': {
|
||||||
|
target: 'http://127.0.0.1',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
'/map/graphics': {
|
||||||
|
target: 'http://127.0.0.1',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
'/map/view': {
|
||||||
|
target: 'http://127.0.0.1',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
'/tiles': {
|
'/tiles': {
|
||||||
target: 'http://127.0.0.1',
|
target: 'http://127.0.0.1',
|
||||||
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://127.0.0.1',
|
target: 'http://127.0.0.1',
|
||||||
@@ -24,18 +137,6 @@ export default defineConfig({
|
|||||||
'/aircraftlist.json': {
|
'/aircraftlist.json': {
|
||||||
target: 'http://127.0.0.1'
|
target: 'http://127.0.0.1'
|
||||||
},
|
},
|
||||||
'/map': {
|
|
||||||
target: 'http://localhost:5173/ui/index.html'
|
|
||||||
},
|
|
||||||
'/dsp': {
|
|
||||||
target: 'http://localhost:5173/ui/index.html'
|
|
||||||
},
|
|
||||||
'/aircraftlist': {
|
|
||||||
target: 'http://localhost:5173/ui/index.html'
|
|
||||||
},
|
|
||||||
'/settings': {
|
|
||||||
target: 'http://localhost:5173/ui/index.html'
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
fs:{
|
fs:{
|
||||||
strict: false
|
strict: false
|
||||||
|
|||||||
Reference in New Issue
Block a user