├── .nvmrc ├── src ├── index.ts └── packages │ ├── index.ts │ ├── event │ └── index.ts │ ├── tiles │ ├── LayerTilesRenderer.ts │ └── index.ts │ └── utils │ └── GPSUtil.js ├── types └── global.d.ts ├── .eslintignore ├── .npmrc ├── scripts ├── .eslintrc.js ├── publish.sh └── publish-beta.sh ├── test ├── hutong │ ├── NoLod_0.cmpt │ ├── tileset.json │ └── scenetree.json ├── testConvert.js └── index.html ├── pnpm-workspace.yaml ├── lint-staged.config.js ├── .prettierignore ├── CHANGELOG.md ├── .prettierrc.js ├── .gitignore ├── tsconfig.json ├── LICENSE ├── rollup.config.js ├── rollup.umd.config.js ├── package.json ├── .eslintrc.js └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | v16 2 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './packages' 2 | -------------------------------------------------------------------------------- /types/global.d.ts: -------------------------------------------------------------------------------- 1 | declare let AMap: any 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | !.* 4 | -------------------------------------------------------------------------------- /src/packages/index.ts: -------------------------------------------------------------------------------- 1 | export * from './tiles' 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist = true 2 | @fj:registry=https://npm.fjdynamics.com/repository/npm-hosted/ 3 | -------------------------------------------------------------------------------- /scripts/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'no-console': 'off', 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /test/hutong/NoLod_0.cmpt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangyanggu/layer-3dtiles/HEAD/test/hutong/NoLod_0.cmpt -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | # 2 | packages: 3 | - 'src/**' 4 | - '!packages/__mocks__' 5 | - '!**/__tests__/**' 6 | -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'src/**/*.{js,ts,vue}': ['eslint --fix --ext .js,.ts,.vue'] 3 | } 4 | -------------------------------------------------------------------------------- /scripts/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | pnpm run build 6 | 7 | cd .. 8 | 9 | npm publish --access public 10 | cd - 11 | 12 | echo "Publish completed" 13 | -------------------------------------------------------------------------------- /scripts/publish-beta.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | pnpm run build 6 | 7 | cd .. 8 | 9 | npm publish --tag=beta --access public 10 | cd - 11 | 12 | echo "Publish completed" 13 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.sh 2 | node_modules 3 | *.md 4 | *.woff 5 | *.ttf 6 | .vscode 7 | .idea 8 | dist 9 | /public 10 | .husky 11 | .local 12 | /bin 13 | Dockerfile 14 | .eslintrc.js 15 | lint-staged.config.js 16 | postcss.config.js 17 | .prettierrc.js 18 | .commitlintrc.js 19 | tailwind.config.js 20 | config 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.0.8 2 | * 增加autoPosition事件,用于自动设置中心点后通过translate进行微调位置 3 | 4 | ### 0.0.7 5 | * 增加region包围盒支持 6 | * 增加自动获取模型中心点功能 7 | 8 | ### 0.0.6 9 | * 适配3d-tiles-renderer新版本 10 | 11 | ### 0.0.5 12 | * 调整依赖 13 | 14 | ### 0.0.4 15 | * 解决部分打包工具不支持index-es.js格式 16 | 17 | ### 0.0.3 18 | * 解决打包问题 19 | 20 | ### 0.0.1 21 | 初始版本,完成3dtiles模型加载 22 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | endOfLine: 'auto', // 允许代码结尾是回车或者换行,\n\r 3 | printWidth: 120, // 换行字符串阈值 4 | htmlWhitespaceSensitivity: "ignore", 5 | tabWidth: 2, // 水平缩进的空格数 6 | useTabs: false, 7 | semi: true, // 句末是否加分号 8 | vueIndentScriptAndStyle: true, 9 | singleQuote: true, // 用单引号 10 | trailingComma: 'none', // 最后一个对象元素加逗号 11 | bracketSpacing: true, // 对象,数组加空格 12 | bracketSameLine: true, // > 是否另起一行 13 | arrowParens: 'always' // (x) => {} 是否要有小括号 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build and Release Folders 2 | bin-debug/ 3 | bin-release/ 4 | [Oo]bj/ 5 | [Bb]in/ 6 | 7 | # Other files and folders 8 | .settings/ 9 | 10 | # Executables 11 | *.swf 12 | *.air 13 | *.ipa 14 | *.apk 15 | 16 | # Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties` 17 | # should NOT be excluded as they contain compiler settings and other important 18 | # information for Eclipse / Flash Builder. 19 | 20 | dist 21 | node_modules 22 | .idea 23 | *.log 24 | .temp 25 | .cache 26 | dist.zip 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "strict": true, 5 | "module": "ES6", 6 | "target": "ES2018", 7 | "noImplicitAny": false, 8 | "declaration": true, 9 | "outDir": "dist", 10 | "moduleResolution": "Node", 11 | "esModuleInterop": true, 12 | "jsx": "preserve", 13 | "sourceMap": true, 14 | "lib": ["ES2018", "DOM"], 15 | "allowSyntheticDefaultImports": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "resolveJsonModule": true 18 | }, 19 | "include": [ 20 | "src", 21 | "types" 22 | ], 23 | "exclude": ["node_modules", "**/__test?__", "**/dist"] 24 | } 25 | -------------------------------------------------------------------------------- /test/hutong/tileset.json: -------------------------------------------------------------------------------- 1 | {"asset":{"generatetool":"cesiumlab2@www.cesiumlab.com/model2tiles","version":"1.0"},"extras":{"scenetree":"scenetree.json"},"geometricError":277.772193757584,"properties":null,"refine":"REPLACE","root":{"boundingVolume":{"box":[1.28056854009628e-09,0.00126921967603266,50.9999999995343,138.886096878792,0,0,0,89.5272091499064,0,0,0,50.0000000144355]},"children":[{"boundingVolume":{"box":[1.74468150362372e-05,0.000771780032664537,51.001090479549,138.883413264412,0,0,0,89.5276713818312,0,0,0,50.0010483968072]},"content":{"uri":"NoLod_0.cmpt"},"geometricError":0.0,"refine":"REPLACE"}],"geometricError":277.772193757584,"transform":[-0.895721264132815,-0.444616033202033,0.0,0.0,0.285247488449276,-0.574658181137602,0.76707355917474,0.0,-0.341053203054438,0.687084098106856,0.641559159247997,0.0,-2178286.80089891,4388365.82858541,4070169.81846538,1.0]}} 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 amap 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/testConvert.js: -------------------------------------------------------------------------------- 1 | 2 | //const result = XYZ2LLA(6378136.999105044, -18.033653101931456, -104.96120694052074) 3 | 4 | 5 | function convertToWGS84(centerX, centerY, centerZ) { 6 | const a = 6378137; // 长半轴 7 | const b = 6356752.3142; // 短半轴 8 | // 定义椭球体参数 9 | const f = (a - b) / a; // 扁率 10 | const e = Math.sqrt(2 * f - f * f); // 第一偏心率 11 | const e2 = e * e; // 第二偏心率 12 | 13 | // 计算球心坐标转换为WGS84坐标的公式 14 | const p = Math.sqrt(centerX * centerX + centerY * centerY); 15 | const theta = Math.atan2(centerZ * a, p * b); 16 | const sinTheta = Math.sin(theta); 17 | const cosTheta = Math.cos(theta); 18 | const latitude = Math.atan2(centerZ + e2 * b * sinTheta * sinTheta * sinTheta, p - e2 * a * cosTheta * cosTheta * cosTheta); 19 | const longitude = Math.atan2(centerY, centerX); 20 | const h = p / Math.cos(latitude) - a / Math.sqrt(1 - e2 * Math.sin(latitude) * Math.sin(latitude)); 21 | 22 | // 将弧度转换为度 23 | const latitudeDeg = latitude * 180 / Math.PI; 24 | const longitudeDeg = longitude * 180 / Math.PI; 25 | 26 | // 返回WGS84坐标 27 | return { 28 | latitude: latitudeDeg, 29 | longitude: longitudeDeg, 30 | height: h 31 | }; 32 | } 33 | 34 | // 示例使用 35 | const centerX = -2307081.927348412; // 球心X坐标 36 | const centerY = 5418677.784665749; // 球心Y坐标 37 | const centerZ = 2440717.2996277967; // 球心Z坐标 38 | 39 | 40 | const wgs84 = convertToWGS84(centerX, centerY, centerZ); 41 | console.log(wgs84); 42 | -------------------------------------------------------------------------------- /src/packages/event/index.ts: -------------------------------------------------------------------------------- 1 | interface ListenerItem{ 2 | callback: Callback 3 | isOnce?: boolean 4 | } 5 | interface Listeners { 6 | [key: string]: ListenerItem[] 7 | } 8 | type Callback = (e: T) => void 9 | abstract class BaseEvent { 10 | private _listeners: Listeners = {} 11 | 12 | /** 13 | * 绑定事件 14 | * @param name 事件名称 15 | * @param cb 事件回调 16 | * @param isOnce 是否只执行一次 17 | */ 18 | on(name:string, cb: Callback, isOnce?: boolean):void{ 19 | if(this._listeners[name]){ 20 | this._listeners[name].push({ 21 | callback: cb, 22 | isOnce 23 | }) 24 | }else{ 25 | this._listeners[name] = [{ 26 | callback: cb, 27 | isOnce 28 | }] 29 | } 30 | } 31 | off(name:string, cb: Callback){ 32 | if(!cb){ 33 | throw new Error('取消事件时需要传入原回调函数') 34 | } 35 | const listeners = this._listeners[name] 36 | if(listeners && listeners.length > 0){ 37 | for(let i=0;i 0){ 49 | for(let i=0;i标签引入时,才能通过name访问到export的内容。 40 | } 41 | ], 42 | // 监听的文件 43 | watch: { 44 | exclude: 'node_modules/**' 45 | }, 46 | // 不参与打包 47 | plugins: [ 48 | typescript({ 49 | tsconfig: 'tsconfig.json' 50 | }), 51 | nodeResolve({ 52 | extensions: ['.mjs', '.js', '.json', '.ts'], 53 | }), 54 | commonjs(), 55 | esbuild({ 56 | // All options are optional 57 | include: /\.[jt]sx?$/, // default, inferred from `loaders` option 58 | exclude: /node_modules\/three/, // default 59 | sourceMap: true, // default 60 | minify: isProd(), 61 | target: 'es2017', // default, or 'es20XX', 'esnext' 62 | jsx: 'transform', // default, or 'preserve' 63 | // Like @rollup/plugin-replace 64 | tsconfig: 'tsconfig.json', // default 65 | // Add extra loaders 66 | loaders: { 67 | // Add .json files support 68 | // require @rollup/plugin-commonjs 69 | '.json': 'json', 70 | }, 71 | }), 72 | // 热更新 73 | !isProd() && livereload({ 74 | watch: ['dist', 'test'], 75 | verbose: false 76 | }), 77 | // 开发模式开启静态服务器 78 | !isProd() && serve({ 79 | open: true, 80 | contentBase: ['dist', 'test', '../'], 81 | openPage: '/index.html' 82 | }) 83 | ] 84 | } 85 | -------------------------------------------------------------------------------- /src/packages/tiles/LayerTilesRenderer.ts: -------------------------------------------------------------------------------- 1 | import { TilesRenderer } from '3d-tiles-renderer' 2 | import {Matrix4, Vector3, Quaternion} from "three"; 3 | export class LayerTilesRenderer extends TilesRenderer { 4 | 5 | preprocessNode( tile, tileSetDir, parentTile ) { 6 | 7 | if ( tile.transform ) { 8 | 9 | if ( ! parentTile ) { 10 | const matrix = new Matrix4().fromArray( tile.transform ); 11 | const position = new Vector3(); 12 | const scale = new Vector3(); 13 | const quaternion = new Quaternion(); 14 | matrix.decompose(position, quaternion, scale); 15 | quaternion.setFromAxisAngle( new Vector3( 0, 0, 1 ), 0 ); 16 | matrix.compose(position, quaternion, scale); 17 | tile.transform = matrix.toArray(); 18 | 19 | } 20 | 21 | } 22 | 23 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 24 | // @ts-ignore 25 | super.preprocessNode( tile, tileSetDir, parentTile ); 26 | 27 | /*if ( 'region' in tile.boundingVolume && ! parentTile) { 28 | console.log('tile.cached.transform: ', tile.cached.transform) 29 | const matrix = new Matrix4().fromArray( tile.cached.transform ); 30 | matrix.makeRotationAxis( new Vector3( 0, 0, 1 ), 0 ); 31 | tile.cached.transform = matrix.toArray(); 32 | 33 | }*/ 34 | 35 | } 36 | 37 | /*dispose() { 38 | 39 | super.dispose(); 40 | const _this = this as any 41 | _this.lruCache.itemList.forEach( tile => { 42 | _this.disposeTile( tile ); 43 | } ); 44 | _this.lruCache.itemSet.clear(); 45 | _this.lruCache.itemList = []; 46 | _this.lruCache.callbacks.clear(); 47 | _this.lruCache = null; 48 | _this.visibleTiles.clear(); 49 | _this.activeTiles.clear(); 50 | _this.downloadQueue.callbacks.clear(); 51 | _this.downloadQueue.items = []; 52 | _this.downloadQueue = null; 53 | _this.parseQueue.callbacks.clear(); 54 | _this.parseQueue.items = []; 55 | _this.parseQueue = null; 56 | this.clearGroup( this.group ); 57 | _this.tileSets = {}; 58 | _this.cameraMap.clear(); 59 | _this.cameras = []; 60 | _this.cameraInfo = []; 61 | _this.group = null; 62 | 63 | } 64 | 65 | clearGroup( group ) { 66 | group.traverse( ( item ) => { 67 | 68 | if ( item.isMesh ) { 69 | 70 | item.geometry.dispose(); 71 | item.material.dispose(); 72 | if ( item.material.texture && item.material.texture.dispose ) { 73 | 74 | item.material.texture.dispose(); 75 | 76 | } 77 | 78 | } 79 | delete item.featureTable; 80 | delete item.batchTable; 81 | 82 | } ); 83 | delete group.tilesRenderer; 84 | group.remove( ...group.children ); 85 | 86 | }*/ 87 | } 88 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vuemap/layer-3dtiles", 3 | "version": "0.0.8", 4 | "description": "高德地图3DTiles加载插件", 5 | "keywords": [ 6 | "amap", 7 | "threejs", 8 | "3dtiles", 9 | "高德地图" 10 | ], 11 | "exports": { 12 | ".": { 13 | "types": "./dist/index.d.ts", 14 | "require": "./dist/index.cjs", 15 | "import": "./dist/index.mjs" 16 | }, 17 | "./*": "./*" 18 | }, 19 | "main": "dist/index.cjs", 20 | "module": "dist/index.mjs", 21 | "unpkg": "dist/index.js", 22 | "jsdelivr": "dist/index.js", 23 | "packageManager": "pnpm@7.9.4", 24 | "workspaces": [ 25 | "src/*" 26 | ], 27 | "engines": { 28 | "node": ">= 16" 29 | }, 30 | "scripts": { 31 | "build": "npm run clean:dist && cross-env NODE_ENV=production rollup -c rollup.config.js && cross-env NODE_ENV=production rollup -c rollup.umd.config.js", 32 | "dev": "npm run clean:dist && cross-env NODE_ENV=development rollup -c rollup.umd.config.js -w", 33 | "clean:dist": "rimraf dist", 34 | "lint": "eslint --fix --ext .js,.vue src/", 35 | "prettier": "prettier --write src/", 36 | "prepublishOnly": "npm run build" 37 | }, 38 | "browserslist": [ 39 | "> 1%", 40 | "not ie 11", 41 | "not op_mini all" 42 | ], 43 | "repository": "https://github.com/yangyanggu/layer-3dtiles", 44 | "author": "guyangyang <407042815@qq.com>", 45 | "license": "MIT", 46 | "bugs": { 47 | "url": "https://github.com/yangyanggu/layer-3dtiles/issues" 48 | }, 49 | "homepage": "https://github.com/yangyanggu/layer-3dtiles", 50 | "dependencies": { 51 | "3d-tiles-renderer": "0.3.20", 52 | "three": "0.143.0", 53 | "lodash-es": "^4.17.21" 54 | }, 55 | "devDependencies": { 56 | "@pnpm/logger": "4.0.0", 57 | "@pnpm/types": "8.5.0", 58 | "@rollup/plugin-commonjs": "^22.0.2", 59 | "@rollup/plugin-node-resolve": "^13.3.0", 60 | "@rollup/plugin-replace": "4.0.0", 61 | "@rollup/plugin-typescript": "^8.3.0", 62 | "@typescript-eslint/eslint-plugin": "5.35.1", 63 | "@typescript-eslint/parser": "5.35.1", 64 | "@types/three": "0.143.0", 65 | "cross-env": "^7.0.3", 66 | "esbuild": "^0.15.5", 67 | "eslint": "8.22.0", 68 | "eslint-config-prettier": "8.3.0", 69 | "eslint-define-config": "1.6.0", 70 | "eslint-plugin-import": "2.25.3", 71 | "eslint-plugin-prettier": "^4.2.1", 72 | "fast-glob": "3.2.7", 73 | "fs-extra": "10.0.0", 74 | "gulp": "4.0.2", 75 | "lint-staged": "^12.4.0", 76 | "prettier": "^2.7.1", 77 | "rimraf": "3.0.2", 78 | "rollup": "^2.78.1", 79 | "rollup-plugin-esbuild": "4.9.3", 80 | "rollup-plugin-filesize": "9.1.2", 81 | "rollup-plugin-serve": "^2.0.1", 82 | "rollup-plugin-livereload": "^2.0.5", 83 | "typescript": "^4.7.4", 84 | "magic-string": "^0.25.7" 85 | }, 86 | "types": "dist/index.d.ts", 87 | "files": [ 88 | "dist", 89 | "src" 90 | ], 91 | "peerDependencies": { 92 | "@vuemap/three-layer": ">=0.0.2" 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires,no-undef 2 | const { defineConfig } = require('eslint-define-config') 3 | 4 | // eslint-disable-next-line no-undef 5 | module.exports = defineConfig({ 6 | root: true, 7 | parserOptions: { 8 | parser: '@typescript-eslint/parser', 9 | sourceType: 'module', 10 | ecmaFeatures: { 11 | jsx: true, 12 | tsx: true, 13 | }, 14 | }, 15 | env: { 16 | browser: true, 17 | node: false, 18 | }, 19 | globals: { 20 | jest: 'readonly', 21 | "AMap": true, 22 | "particlesJS": true, 23 | "expect": true, 24 | "sinon": true, 25 | "Loca": true 26 | }, 27 | plugins: ['@typescript-eslint', 'import'], 28 | extends: [ 29 | 'eslint:recommended', 30 | 'plugin:@typescript-eslint/recommended' 31 | ], 32 | overrides: [ 33 | { 34 | files: ['*.ts', '*.vue'], 35 | rules: { 36 | 'no-undef': 'off', 37 | }, 38 | }, 39 | { 40 | files: ['**/__tests__/**', '**/gulpfile.ts'], 41 | rules: { 42 | 'no-console': 'off', 43 | }, 44 | }, 45 | ], 46 | rules: { 47 | // js/ts 48 | 'no-console': ['warn', { allow: ['error'] }], 49 | 'no-restricted-syntax': ['error', 'LabeledStatement', 'WithStatement'], 50 | camelcase: ['error', { properties: 'never' }], 51 | 52 | 'no-var': 'error', 53 | 'no-empty': ['error', { allowEmptyCatch: true }], 54 | 'no-void': 'error', 55 | 'prefer-const': [ 56 | 'warn', 57 | { destructuring: 'all', ignoreReadBeforeAssign: true }, 58 | ], 59 | 'prefer-template': 'error', 60 | 'object-shorthand': [ 61 | 'error', 62 | 'always', 63 | { ignoreConstructors: false, avoidQuotes: true }, 64 | ], 65 | 'block-scoped-var': 'error', 66 | 'no-constant-condition': ['error', { checkLoops: false }], 67 | 68 | '@typescript-eslint/explicit-module-boundary-types': 'off', 69 | '@typescript-eslint/no-explicit-any': 'off', 70 | '@typescript-eslint/no-non-null-assertion': 'off', 71 | '@typescript-eslint/no-non-null-asserted-optional-chain': 'off', 72 | '@typescript-eslint/consistent-type-imports': [ 73 | 'error', 74 | { disallowTypeAnnotations: false }, 75 | ], 76 | '@typescript-eslint/no-this-alias': ['off'], 77 | 78 | // vue 79 | 'vue/no-v-html': 'off', 80 | 'vue/require-default-prop': 'off', 81 | 'vue/require-explicit-emits': 'off', 82 | 'vue/multi-word-component-names': 'off', 83 | 84 | // import 85 | 'import/first': 'error', 86 | 'import/no-duplicates': 'error', 87 | 'import/order': [ 88 | 'error', 89 | { 90 | groups: [ 91 | 'builtin', 92 | 'external', 93 | 'internal', 94 | 'parent', 95 | 'sibling', 96 | 'index', 97 | 'object', 98 | 'type', 99 | ], 100 | 101 | pathGroupsExcludedImportTypes: ['type'], 102 | }, 103 | ], 104 | }, 105 | }) 106 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 测试 7 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 | 35 | 36 | 37 | 38 |
39 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /src/packages/utils/GPSUtil.js: -------------------------------------------------------------------------------- 1 | const pi = 3.1415926535897932384626; 2 | const x_pi = 3.14159265358979324 * 3000.0 / 180.0; 3 | const a = 6378245.0; 4 | const ee = 0.00669342162296594323; 5 | 6 | function transformLat(x, y) { 7 | let ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y 8 | + 0.2 * Math.sqrt(Math.abs(x)); 9 | ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0; 10 | ret += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0; 11 | ret += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0; 12 | return ret; 13 | } 14 | 15 | function transformLon(x, y) { 16 | let ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 17 | * Math.sqrt(Math.abs(x)); 18 | ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0; 19 | ret += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0; 20 | ret += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0 21 | * pi)) * 2.0 / 3.0; 22 | return ret; 23 | } 24 | 25 | function transform(lon, lat) { 26 | if (outOfChina(lon, lat)) { 27 | return new [lon, lat]; 28 | } 29 | let dLat = transformLat(lon - 105.0, lat - 35.0); 30 | let dLon = transformLon(lon - 105.0, lat - 35.0); 31 | let radLat = lat / 180.0 * pi; 32 | let magic = Math.sin(radLat); 33 | magic = 1 - ee * magic * magic; 34 | let sqrtMagic = Math.sqrt(magic); 35 | dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi); 36 | dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi); 37 | let mgLat = lat + dLat; 38 | let mgLon = lon + dLon; 39 | return [mgLon, mgLat]; 40 | } 41 | 42 | function outOfChina(lon, lat) { 43 | if (lon < 72.004 || lon > 137.8347) 44 | return true; 45 | if (lat < 0.8293 || lat > 55.8271) 46 | return true; 47 | return false; 48 | } 49 | 50 | /** 51 | * 84 to 火星坐标系 (GCJ-02) World Geodetic System ==> Mars Geodetic System 52 | * 53 | * @param lat 54 | * @param lon 55 | * @return 56 | */ 57 | function gps84_To_Gcj02(lon, lat) { 58 | if (outOfChina(lon, lat)) { 59 | return [lon, lat]; 60 | } 61 | let dLat = transformLat(lon - 105.0, lat - 35.0); 62 | let dLon = transformLon(lon - 105.0, lat - 35.0); 63 | let radLat = lat / 180.0 * pi; 64 | let magic = Math.sin(radLat); 65 | magic = 1 - ee * magic * magic; 66 | let sqrtMagic = Math.sqrt(magic); 67 | dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi); 68 | dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi); 69 | let mgLat = retain6(lat + dLat); 70 | let mgLon = retain6(lon + dLon); 71 | return [mgLon, mgLat]; 72 | } 73 | 74 | /** 75 | * * 火星坐标系 (GCJ-02) to 84 * * @param lon * @param lat * @return 76 | * */ 77 | function gcj02_To_Gps84(lon, lat) { 78 | let gps = transform(lon, lat); 79 | let mgLon = lon * 2 - gps[0]; 80 | let mgLat = lat * 2 - gps[1]; 81 | return [mgLon, mgLat]; 82 | } 83 | 84 | /** 85 | * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换算法 将 GCJ-02 坐标转换成 BD-09 坐标 86 | * 87 | * @param lat 88 | * @param lon 89 | */ 90 | function gcj02_To_Bd09(lon, lat) { 91 | let x = lon, y = lat; 92 | let z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * x_pi); 93 | let theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * x_pi); 94 | let mgLon = z * Math.cos(theta) + 0.0065; 95 | let mgLat = z * Math.sin(theta) + 0.006; 96 | return [mgLon, mgLat]; 97 | } 98 | 99 | /** 100 | * * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换算法 * * 将 BD-09 坐标转换成GCJ-02 坐标 * * @param 101 | * bd_lat * @param bd_lon * @return 102 | */ 103 | function bd09_To_Gcj02(lon, lat) { 104 | let x = lon - 0.0065, y = lat - 0.006; 105 | let z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_pi); 106 | let theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_pi); 107 | let mgLon = z * Math.cos(theta); 108 | let mgLat = z * Math.sin(theta); 109 | return [mgLon, mgLat]; 110 | } 111 | 112 | /**将gps84转为bd09 113 | * @param lat 114 | * @param lon 115 | * @return 116 | */ 117 | function gps84_To_bd09(lon, lat) { 118 | let gcj02 = gps84_To_Gcj02(lon, lat); 119 | let bd09 = gcj02_To_Bd09(gcj02[0], gcj02[1]); 120 | return bd09; 121 | } 122 | 123 | function bd09_To_gps84(lon, lat) { 124 | let gcj02 = bd09_To_Gcj02(lon, lat); 125 | let gps84 = gcj02_To_Gps84(gcj02[0], gcj02[1]); 126 | //保留小数点后六位 127 | gps84[0] = retain6(gps84[0]); 128 | gps84[1] = retain6(gps84[1]); 129 | return gps84; 130 | } 131 | 132 | /**保留小数点后六位 133 | * @param num 134 | * @return 135 | */ 136 | function retain6(num) { 137 | return parseFloat(num.toFixed(6)); 138 | } 139 | 140 | function convertToWGS84(centerX, centerY, centerZ) { 141 | const a = 6378137; // 长半轴 142 | const b = 6356752.3142; // 短半轴 143 | // 定义椭球体参数 144 | const f = (a - b) / a; // 扁率 145 | const e = Math.sqrt(2 * f - f * f); // 第一偏心率 146 | const e2 = e * e; // 第二偏心率 147 | 148 | // 计算球心坐标转换为WGS84坐标的公式 149 | const p = Math.sqrt(centerX * centerX + centerY * centerY); 150 | const theta = Math.atan2(centerZ * a, p * b); 151 | const sinTheta = Math.sin(theta); 152 | const cosTheta = Math.cos(theta); 153 | const latitude = Math.atan2(centerZ + e2 * b * sinTheta * sinTheta * sinTheta, p - e2 * a * cosTheta * cosTheta * cosTheta); 154 | const longitude = Math.atan2(centerY, centerX); 155 | const h = p / Math.cos(latitude) - a / Math.sqrt(1 - e2 * Math.sin(latitude) * Math.sin(latitude)); 156 | 157 | // 将弧度转换为度 158 | const latitudeDeg = latitude * 180 / Math.PI; 159 | const longitudeDeg = longitude * 180 / Math.PI; 160 | 161 | // 返回WGS84坐标 162 | return { 163 | latitude: latitudeDeg, 164 | longitude: longitudeDeg, 165 | height: h 166 | }; 167 | } 168 | 169 | export { 170 | gps84_To_Gcj02, 171 | gcj02_To_Gps84, 172 | gps84_To_bd09, 173 | bd09_To_gps84, 174 | gcj02_To_Bd09, 175 | convertToWGS84 176 | }; 177 | -------------------------------------------------------------------------------- /test/hutong/scenetree.json: -------------------------------------------------------------------------------- 1 | {"scenes":[{"children":[{"id":"0","name":"","sphere":[-2178330.81791446,4388347.72927855,4070245.29940303,147.654209259349],"type":"element"},{"id":"1","name":"","sphere":[-2178366.54819685,4388327.80195228,4070247.64583843,160.98925455451],"type":"element"},{"id":"2","name":"","sphere":[-2178372.98505219,4388386.72257653,4070181.12316649,168.090161767772],"type":"element"},{"id":"3","name":"","sphere":[-2178428.76023489,4388369.41396771,4070170.00824871,139.04023105245],"type":"element"},{"id":"4","name":"","sphere":[-2178278.97999338,4388398.46029416,4070218.52597073,115.875315229196],"type":"element"},{"id":"5","name":"","sphere":[-2178248.25874665,4388418.17087496,4070213.74791606,130.189294614613],"type":"element"},{"id":"6","name":"","sphere":[-2178227.91184346,4388454.17622751,4070186.00339522,148.022612175129],"type":"element"},{"id":"7","name":"","sphere":[-2178402.85612101,4388383.79981436,4070168.37295212,128.894987606613],"type":"element"},{"id":"8","name":"","sphere":[-2178168.60323059,4388432.78419528,4070240.44040373,123.212713208829],"type":"element"},{"id":"9","name":"","sphere":[-2178286.41626357,4388370.82318332,4070244.17084073,118.713194028568],"type":"element"},{"id":"10","name":"","sphere":[-2178294.31100264,4388381.11216743,4070228.95512163,118.829729891228],"type":"element"},{"id":"11","name":"","sphere":[-2178416.15162753,4388381.89991917,4070163.33926942,110.865356165357],"type":"element"},{"id":"12","name":"","sphere":[-2178272.81850232,4388411.19326961,4070208.1649275,124.684813978533],"type":"element"},{"id":"13","name":"","sphere":[-2178187.95738167,4388405.21247669,4070259.68034944,110.292649583497],"type":"element"},{"id":"14","name":"","sphere":[-2178331.95592965,4388390.24494993,4070199.16240262,147.3312542719],"type":"element"},{"id":"15","name":"","sphere":[-2178250.32808852,4388377.4035347,4070256.30761161,115.832789820028],"type":"element"},{"id":"16","name":"","sphere":[-2178168.76586543,4388417.69661407,4070256.51203195,119.195103530949],"type":"element"},{"id":"17","name":"","sphere":[-2178355.19769152,4388419.99868666,4070154.94136959,125.629168847988],"type":"element"},{"id":"18","name":"","sphere":[-2178341.05858808,4388326.47352193,4070262.61872932,117.745269278814],"type":"element"},{"id":"19","name":"","sphere":[-2178397.86525725,4388360.35109158,4070196.13876743,118.99886580659],"type":"element"},{"id":"20","name":"","sphere":[-2178305.42245523,4388405.20893184,4070197.24175669,121.127768466069],"type":"element"},{"id":"21","name":"","sphere":[-2178308.43971284,4388338.35071979,4070267.23921387,114.049245678964],"type":"element"},{"id":"22","name":"","sphere":[-2178428.50106485,4388352.14171249,4070188.64370272,109.648751318997],"type":"element"},{"id":"23","name":"","sphere":[-2178269.15194299,4388375.9968734,4070247.80761661,115.227223022517],"type":"element"},{"id":"24","name":"","sphere":[-2178168.05894316,4388408.44452623,4070266.79633526,126.765798491016],"type":"element"},{"id":"25","name":"","sphere":[-2178291.2210609,4388376.04110841,4070236.02853692,116.76905402985],"type":"element"},{"id":"26","name":"","sphere":[-2178249.3236884,4388372.87985748,4070261.68611316,116.005021580344],"type":"element"},{"id":"27","name":"","sphere":[-2178353.37898002,4388402.57441726,4070174.56919992,137.866653319871],"type":"element"},{"id":"28","name":"","sphere":[-2178401.18742633,4388351.80284149,4070203.52736969,118.12967984781],"type":"element"},{"id":"29","name":"","sphere":[-2178301.03632054,4388386.11238488,4070220.02498181,117.182310422502],"type":"element"},{"id":"30","name":"","sphere":[-2178263.82429093,4388392.46742751,4070233.00074794,134.142772552412],"type":"element"},{"id":"31","name":"","sphere":[-2178422.95708572,4388344.41592997,4070199.86501851,117.051258672937],"type":"element"},{"id":"32","name":"","sphere":[-2178399.10101105,4388346.66709585,4070210.1366204,110.767378941556],"type":"element"},{"id":"33","name":"","sphere":[-2178283.29106094,4388419.89638327,4070193.27701417,116.784015008103],"type":"element"},{"id":"34","name":"","sphere":[-2178280.9140185,4388357.03626669,4070261.86071809,122.701067072631],"type":"element"},{"id":"35","name":"","sphere":[-2178204.81722525,4388401.05112695,4070255.17484746,122.474931251164],"type":"element"},{"id":"36","name":"","sphere":[-2178324.14768537,4388445.10576405,4070144.55894625,149.684715117648],"type":"element"},{"id":"37","name":"","sphere":[-2178299.22755075,4388350.81282375,4070258.79036652,128.507989975658],"type":"element"},{"id":"38","name":"","sphere":[-2178260.60237639,4388408.10329593,4070217.96810666,117.655073469513],"type":"element"},{"id":"39","name":"","sphere":[-2178180.12973991,4388413.99266301,4070254.43811826,109.454573387751],"type":"element"},{"id":"40","name":"","sphere":[-2178268.73143572,4388421.98981767,4070198.77481496,112.50103262221],"type":"element"},{"id":"41","name":"","sphere":[-2178408.2181874,4388319.34035782,4070234.55514864,143.456544637493],"type":"element"},{"id":"42","name":"","sphere":[-2178255.13178125,4388383.18671359,4070247.56063736,120.115149856851],"type":"element"},{"id":"43","name":"","sphere":[-2178411.43769866,4388373.56728402,4070174.76929071,111.423349715256],"type":"element"},{"id":"44","name":"","sphere":[-2178207.39927564,4388419.32154956,4070234.23574394,159.882177464473],"type":"element"},{"id":"45","name":"","sphere":[-2178185.0169162,4388450.79387377,4070212.42776494,129.084945753991],"type":"element"},{"id":"46","name":"","sphere":[-2178310.33446818,4388369.31329174,4070233.07316179,136.095991795956],"type":"element"},{"id":"47","name":"","sphere":[-2178280.94646565,4388415.0479016,4070199.71595778,118.105687188315],"type":"element"},{"id":"48","name":"","sphere":[-2178264.69421169,4388364.96804842,4070261.98846863,114.47539494613],"type":"element"},{"id":"49","name":"","sphere":[-2178239.02504607,4388382.27832784,4070257.09543774,111.618444100296],"type":"element"},{"id":"50","name":"","sphere":[-2178361.85906153,4388429.17777314,4070141.56936999,115.190559009688],"type":"element"},{"id":"51","name":"","sphere":[-2178359.13765343,4388367.76944462,4070208.78242062,123.453086561387],"type":"element"},{"id":"52","name":"","sphere":[-2178302.81209574,4388418.32639597,4070184.58103035,113.337919949134],"type":"element"},{"id":"53","name":"","sphere":[-2178421.49866791,4388353.93973884,4070190.44082406,113.819845646335],"type":"element"},{"id":"54","name":"","sphere":[-2178297.65420659,4388397.15563772,4070209.99609352,120.848841917863],"type":"element"},{"id":"55","name":"","sphere":[-2178319.34645111,4388424.09604718,4070169.61218082,119.624150855101],"type":"element"},{"id":"56","name":"","sphere":[-2178234.51596827,4388375.38660059,4070266.87297392,111.475322507327],"type":"element"},{"id":"57","name":"","sphere":[-2178413.91235936,4388377.80242577,4070168.91800936,111.423356151856],"type":"element"},{"id":"58","name":"","sphere":[-2178342.85776948,4388408.63999891,4070173.66638178,137.064550884377],"type":"element"},{"id":"59","name":"","sphere":[-2178291.34387218,4388360.0664621,4070253.07115586,116.715629058657],"type":"element"},{"id":"60","name":"","sphere":[-2178422.2952465,4388360.12134269,4070183.39714555,119.243267925891],"type":"element"},{"id":"61","name":"","sphere":[-2178225.25872053,4388390.77757732,4070255.31111966,120.498038444107],"type":"element"},{"id":"63","name":"","sphere":[-2178239.22190314,4388401.95532439,4070235.91782718,118.313439002785],"type":"element"},{"id":"64","name":"","sphere":[-2178209.89225322,4388388.70638142,4070265.69764547,121.090081092719],"type":"element"},{"id":"65","name":"","sphere":[-2178356.18538749,4388320.6188662,4070260.84721265,125.338999740286],"type":"element"},{"id":"66","name":"","sphere":[-2178305.25002082,4388429.29593519,4070171.53707373,135.401212835919],"type":"element"},{"id":"67","name":"","sphere":[-2178369.45473137,4388375.47602522,4070195.04433723,126.381251822677],"type":"element"},{"id":"68","name":"","sphere":[-2178352.61496465,4388354.11946024,4070226.8684673,155.168330222436],"type":"element"},{"id":"69","name":"","sphere":[-2178402.3836126,4388369.93404564,4070183.47381934,124.468633068014],"type":"element"},{"id":"70","name":"","sphere":[-2178246.17285716,4388432.43162578,4070199.58392622,128.400395287266],"type":"element"},{"id":"71","name":"","sphere":[-2178197.94927265,4388457.13502885,4070198.76202682,127.595934748898],"type":"element"},{"id":"72","name":"","sphere":[-2178221.7890628,4388445.20667732,4070198.86423938,117.03888730744],"type":"element"},{"id":"73","name":"","sphere":[-2178279.33695893,4388436.96131302,4070177.10304479,132.937190713912],"type":"element"},{"id":"74","name":"","sphere":[-2178293.85214599,4388456.91477573,4070148.01689601,120.199980620671],"type":"element"},{"id":"75","name":"","sphere":[-2178267.50849737,4388474.3965681,4070143.29835359,111.681803957598],"type":"element"},{"id":"76","name":"","sphere":[-2178284.8520533,4388470.53566262,4070138.2136043,139.104473588343],"type":"element"},{"id":"77","name":"","sphere":[-2178261.84496915,4388466.58301495,4070154.6773158,111.099354979435],"type":"element"},{"id":"78","name":"","sphere":[-2178265.10332533,4388450.05853472,4070170.64275521,132.452591831639],"type":"element"},{"id":"79","name":"","sphere":[-2178239.39656029,4388489.82542594,4070141.71842437,149.154006611696],"type":"element"},{"id":"80","name":"","sphere":[-2178254.17702734,4388470.60780057,4070154.44310636,119.572044753601],"type":"element"},{"id":"81","name":"","sphere":[-2178233.49756122,4388481.4606473,4070153.81283533,117.328841412013],"type":"element"},{"id":"82","name":"","sphere":[-2178241.73861975,4388464.89654072,4070167.17201542,132.862459834366],"type":"element"},{"id":"83","name":"","sphere":[-2178212.94118681,4388480.02128162,4070166.28197372,142.251512125093],"type":"element"},{"id":"84","name":"","sphere":[-2178216.58526472,4388471.24594392,4070173.74297496,111.245566530752],"type":"element"},{"id":"85","name":"","sphere":[-2178196.95937141,4388481.54785653,4070173.14251615,111.427032143716],"type":"element"},{"id":"86","name":"","sphere":[-2178236.15517842,4388460.90812146,4070174.41157163,119.655598357969],"type":"element"},{"id":"87","name":"","sphere":[-2178221.41756683,4388466.9508205,4070175.77431434,154.447787464897],"type":"element"}],"id":"aeea8c4314ed45c2b2c2c612cbc6deec","name":"buildings","sphere":[-2178304.19688321,4388400.87075673,4070202.53949579,489.113713952552],"type":"Group"}]} 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @vuemap/layer-3dtiles 2 | [![npm (tag)](https://img.shields.io/npm/v/@vuemap/layer-3dtiles)](https://www.npmjs.org/package/@vuemap/layer-3dtiles) 3 | [![NPM downloads](http://img.shields.io/npm/dm/@vuemap/layer-3dtiles.svg)](https://npmjs.org/package/@vuemap/layer-3dtiles) 4 | ![JS gzip size](http://img.badgesize.io/https://unpkg.com/@vuemap/layer-3dtiles/dist/index.js?compression=gzip&label=gzip%20size:%20JS) 5 | [![NPM](https://img.shields.io/npm/l/@vuemap/layer-3dtiles)](https://github.com/AMap-Web/layer-3dtiles) 6 | [![star](https://badgen.net/github/stars/amap-web/layer-3dtiles)](https://github.com/AMap-Web/layer-3dtiles) 7 | 8 | ### 简介 9 | 本项目为高德地图的3DTilesLayer图层插件,依赖`@vuemap/three-layer`插件,因此如果使用npm安装时需要安装`@vuemap/three-layer` 10 | 11 | > 当前坐标只支持box、region, 不支持sphere 12 | > 从0.0.7版本开始可以初始化可以不传position,不传时将默认从3dtiles数据中获取中心点和海拔 13 | 14 | ### 示例 15 | [codepen示例](https://codepen.io/yangyanggu/pen/BaxGLVZ) 16 | 17 | ### 模型导出时注意事项 18 | * 当使用shp文件生成3dtiles时,参考坐标系需要根据shp文件的坐标系来设定,正常shp文件使用EPSG:4326坐标系,也就是WGS84 19 | * 只支持box、region包围盒 20 | 21 | ### 开发注意事项 22 | * 当加载3dtiles,需要关闭浏览器的开发者工具,不然在销毁3dtiles图层时会有部分显存无法释放 23 | 24 | ### 加载方式 25 | 当前项目支持CDN加载和npm加载两种方式。 26 | 27 | #### CDN加载 28 | CDN加载需要先加载高德地图JS、threejs的库和`@vuemap/three-layer`,代码如下 29 | ```js 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ``` 43 | 44 | #### npm加载 45 | npm加载可以直接使用安装库 46 | ```shell 47 | npm install @vuemap/layer-3dtiles @vuemap/three-layer 48 | ``` 49 | 50 | ### 使用示例 51 | 52 | #### CDN方式 53 | ```js 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 89 | ``` 90 | 91 | #### npm方式 92 | ```js 93 | import {AmbientLight} from 'three' 94 | import {ThreeLayer} from '@vuemap/three-layer' 95 | import {Layer3DTiles} from '@vuemap/layer-3dtiles' 96 | const map = new AMap.Map('app', { 97 | center: [120,31], 98 | zoom: 14, 99 | viewMode: '3D', 100 | pitch: 35 101 | }) 102 | const layer = new ThreeLayer(map) 103 | layer.on('complete', () => { 104 | const light = new AmbientLight('#ffffff', 1); 105 | layer.add(light); 106 | const tiles = new Layer3DTiles(layer, { 107 | url: './hutong/tileset.json', 108 | position: [116.405242513021,39.909402940539] 109 | }) 110 | tiles.setRotation({ 111 | x:0, 112 | y:0, 113 | z:0 114 | }) 115 | tiles.setTranslate({x:15,y:15,z:0}) 116 | tiles.on('click',(e) => { 117 | console.log('click: ', e) 118 | }) 119 | console.log('layer: ', layer) 120 | console.log('tiles: ', tiles) 121 | }) 122 | ``` 123 | 124 | ### API文档说明 125 | 126 | #### Layer3DTiles图层说明 127 | 3dtiles图层类,提供了3dtils加载和常用事件功能
128 | `` new AMap.Layer3DTiles(layer: AMap.ThreeLayer, options: Layer3DTilesOptions) ``
129 | ###### 参数说明 130 | layer: ThreeLayer实例对象
131 | options: Layer3DTiles初始化参数,参数内容如下: 132 | 133 | | 属性名 | 属性类型 | 属性描述 | 134 | |------------------|-----------------------------------------|------------------------------------------------------------------------------------------------------| 135 | | url | String | 模型加载地址 | 136 | | position | [number,number] | 3dtiles加载的经纬度位置,0.0.7版本开始可以不用传,默认从3dtiles数据中读取 | 137 | | scale | Number,{x:Number, y: Number, z: Number} | 设置缩放比例 | 138 | | rotation | {x:Number, y: Number, z: Number} | 旋转模型 | 139 | | translate | {x:Number, y: Number, z: Number} | 模型偏移设置 | 140 | | dracoDecoderPath | String | DRACOLoader 的decoder路径,默认使用CDN路径,默认:https://cdn.jsdelivr.net/npm/three@0.143/examples/js/libs/draco/ | 141 | | fetchOptions | Object | 使用fetch下载文件的参数 | 142 | | mouseEvent | Boolean | 是否开启事件,默认false | 143 | | debug | Boolean | 是否开启debug,开启后将会在页面最顶部显示当前模型处理情况, 默认 false | 144 | | autoFocus | Boolean | 加载后是否自动将地图中心点移动到模型中心,仅在不传position时生效,0.0.7支持 | 145 | | configLoader | (loader: GLTFLoader) => void | 配置loader,用于添加draco等扩展 ,0.0.7支持 | 146 | 147 | ###### 成员函数 148 | 149 | | 函数名 | 入参 | 返回值 | 描述 | 150 | |------------------|-----------------------------------------------|--------------------|--------------------------------------------------------------------------------------------------| 151 | | setScale | Number,{x:Number, y: Number, z: Number} | 无 | 设置缩放比例 | 152 | | setPosition | [Number,Number] (经纬度) | 无 | 设置模型位置 | 153 | | setRotation | {x:Number, y: Number, z: Number} | 无 | 旋转模型 | 154 | | setTranslate | {x:Number, y: Number, z: Number} | 无 | 模型偏移设置 | 155 | | getGroup | 无 | Group | 获取3dtiles的Group对象 | 156 | | getTilesRenderer | 无 | TilesRenderer | 获取3dtile渲染的对象,该对象为`3d-tiles-renderer`的对象,[文档地址](https://github.com/NASA-AMMOS/3DTilesRendererJS) | 157 | | refresh | 无 | 无 | 刷新图层 | 158 | | show | 无 | 无 | 显示模型 | 159 | | hide | 无 | 无 | 隐藏模型 | 160 | | destroy | 无 | 无 | 销毁模型 | 161 | 162 | ###### 事件列表 163 | 164 | | 事件名 | 参数 | 描述 | 165 | |--------------|------------------------------------------|---------------------------------------------------------------| 166 | | loadTileSet | TileSet | tileSet加载成功后触发 | 167 | | loadModel | {scene, tile} | 加载模型后触发 | 168 | | disposeModel | {scene, tile} | 销毁模型后触发 | 169 | | click | null, {object: Object3D,batchData: Object} | 点击事件,可能会出现null值,object为点击的模型,batchData为模型所在的扩展数据,通过读取父节点获取 | 170 | | mousemove | null, {object: Object3D,batchData: Object} | 鼠标移动事件,可能会出现null值,object为滑动到的模型,batchData为模型所在的扩展数据,通过读取父节点获取 | 171 | | rightClick | null, {object: Object3D,batchData: Object} | 右击事件,可能会出现null值,object为点击的模型,batchData为模型所在的扩展数据,通过读取父节点获取 | 172 | | autoPosition | null | 自动设置position后触发,只有在初始化插件时不传入position,并且3dtiles中能够计算出中心点时生效 | 173 | -------------------------------------------------------------------------------- /src/packages/tiles/index.ts: -------------------------------------------------------------------------------- 1 | import {GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader.js"; 2 | import {DRACOLoader} from "three/examples/jsm/loaders/DRACOLoader.js"; 3 | import {Vector2, Raycaster, Group, Box3, Vector3} from "three"; 4 | import {bind} from 'lodash-es'; 5 | import BaseEvent from '../event' 6 | import { LayerTilesRenderer } from './LayerTilesRenderer' 7 | import {gps84_To_Gcj02, convertToWGS84} from '../utils/GPSUtil' 8 | 9 | interface Vec { 10 | x: number 11 | y: number 12 | z: number 13 | } 14 | 15 | interface Options { 16 | url: string //模型下载地址 17 | position?: number[] // 模型位置,经纬度 18 | rotation?: Vec // 旋转角度,一般不需要设置 19 | translate?: Vec // 偏移位置 20 | scale?: number | Vec // 模型缩放比例 21 | dracoDecoderPath?: string // DRACOLoader 的decoder路径,默认使用CDN路径 22 | fetchOptions?: any // 使用fetch下载文件的参数 23 | mouseEvent?: boolean // 是否开启鼠标事件,包含点击、移动、双击、右击 24 | debug?: boolean // 是否开启debug模式,开启后将会再最上面显示当前模型加载情况 25 | autoFocus?: boolean // 加载后是否自动将地图中心点移动到模型中心,仅在不传position时生效 26 | configLoader?: (loader: GLTFLoader) => void // 配置loader,用于添加draco等扩展 27 | } 28 | 29 | class Layer3DTiles extends BaseEvent{ 30 | layer: any // threejs的图层对象 31 | animationFrame = -1; //gltf动画 32 | tilesRenderer: LayerTilesRenderer 33 | group: any 34 | statsContainer?: HTMLDivElement 35 | mouse: Vector2 36 | raycaster?: Raycaster 37 | clickMapFn: any 38 | mousemoveMapFn: any 39 | rightClickMapFn: any 40 | parentGroup: Group 41 | position?: number[] 42 | hasResetCenter = false 43 | options: Options 44 | 45 | constructor(layer: any, options: Options) { 46 | super(); 47 | this.options = options; 48 | this.mouse = new Vector2() 49 | this.layer = layer; 50 | const tilesRenderer = new LayerTilesRenderer( options.url ); 51 | tilesRenderer.setCamera( this.layer.getCamera() ); 52 | tilesRenderer.setResolutionFromRenderer( this.layer.getCamera(), this.layer.getRender() ); 53 | const fetchOptions = options.fetchOptions || {} 54 | const gltfLoader = new GLTFLoader(tilesRenderer.manager) 55 | if ( fetchOptions.credentials === 'include' && fetchOptions.mode === 'cors' ) { 56 | gltfLoader.setCrossOrigin( 'use-credentials' ); 57 | } 58 | 59 | if ( 'credentials' in fetchOptions ) { 60 | gltfLoader.setWithCredentials( fetchOptions.credentials === 'include' ); 61 | } 62 | 63 | if ( fetchOptions.headers ) { 64 | gltfLoader.setRequestHeader( fetchOptions.headers ); 65 | } 66 | const dRACOLoader = new DRACOLoader() 67 | const dracoDecodePath = options.dracoDecoderPath || 'https://cdn.jsdelivr.net/npm/three@0.143/examples/js/libs/draco/' 68 | dRACOLoader.setDecoderPath(dracoDecodePath) 69 | gltfLoader.setDRACOLoader(dRACOLoader) 70 | if (options.configLoader){ 71 | options.configLoader(gltfLoader) 72 | } 73 | tilesRenderer.manager.addHandler(/\.gltf$/i, gltfLoader) 74 | tilesRenderer.onLoadTileSet = (tileSet) => { 75 | this.emit('loadTileSet', tileSet) 76 | } 77 | tilesRenderer.onLoadModel = (scene, tile) => { 78 | this.emit('loadModel', { 79 | scene, 80 | tile 81 | }) 82 | } 83 | tilesRenderer.onDisposeModel = (scene, tile) => { 84 | this.emit('disposeModel', { 85 | scene, 86 | tile 87 | }) 88 | } 89 | tilesRenderer.downloadQueue.maxJobs = 6 90 | tilesRenderer.parseQueue.maxJobs = 6 91 | const contentGroup = new Group() 92 | this.parentGroup = new Group() 93 | this.parentGroup.add(tilesRenderer.group) 94 | contentGroup.add(this.parentGroup) 95 | this.group = contentGroup 96 | this.layer.add( this.group ); 97 | this.tilesRenderer = tilesRenderer 98 | if(options.position){ 99 | this.setPosition(options.position); 100 | } 101 | if(options.rotation){ 102 | this.setRotation(options.rotation); 103 | } 104 | if(options.translate){ 105 | this.setTranslate(options.translate); 106 | } 107 | if(options.scale){ 108 | this.setScale(options.scale); 109 | } 110 | this.animate() 111 | if(options.debug){ 112 | const statsContainer = document.createElement( 'div' ); 113 | statsContainer.style.position = 'absolute'; 114 | statsContainer.style.top = '0px'; 115 | statsContainer.style.left = '0px'; 116 | statsContainer.style.color = 'white'; 117 | statsContainer.style.width = '100%'; 118 | statsContainer.style.textAlign = 'center'; 119 | statsContainer.style.padding = '5px'; 120 | statsContainer.style.pointerEvents = 'none'; 121 | statsContainer.style.lineHeight = '1.5em'; 122 | document.body.appendChild( statsContainer ); 123 | this.statsContainer = statsContainer 124 | } 125 | this.bindEvents(options.mouseEvent) 126 | } 127 | 128 | bindEvents(mouseEvent?:boolean){ 129 | if(mouseEvent){ 130 | this.raycaster = new Raycaster(); 131 | (this.raycaster as any).firstHitOnly = true; 132 | const map = this.layer.getMap() 133 | this.clickMapFn = bind(this.clickMap, this) 134 | map.on('click', this.clickMapFn) 135 | this.mousemoveMapFn = bind(this.mousemoveMap, this) 136 | map.on('mousemove', this.mousemoveMapFn) 137 | this.rightClickMapFn = bind(this.rightClickMap, this) 138 | map.on('rightclick', this.rightClickMapFn) 139 | } 140 | } 141 | unbindEvents(){ 142 | const map = this.layer.getMap() 143 | if(this.clickMapFn){ 144 | map.off('click', this.clickMapFn) 145 | this.clickMapFn = null 146 | } 147 | if(this.mousemoveMapFn){ 148 | map.off('mousemove', this.mousemoveMapFn) 149 | this.mousemoveMapFn = null 150 | } 151 | if(this.rightClickMapFn){ 152 | map.off('rightclick', this.rightClickMapFn) 153 | this.rightClickMapFn = null 154 | } 155 | if(this.tilesRenderer){ 156 | this.tilesRenderer.onLoadTileSet = null; 157 | this.tilesRenderer.onLoadModel = null; 158 | this.tilesRenderer.onDisposeModel = null; 159 | } 160 | } 161 | clickMap(e){ 162 | const result = this._intersectGltf(e) 163 | this.emit('click', result) 164 | } 165 | 166 | mousemoveMap(e){ 167 | const result = this._intersectGltf(e) 168 | this.emit('mousemove', result) 169 | } 170 | 171 | rightClickMap(e){ 172 | const result = this._intersectGltf(e) 173 | this.emit('rightClick', result) 174 | } 175 | 176 | _intersectGltf(e) { 177 | const client = this.layer.getMap().getContainer(); 178 | // 通过鼠标点击位置,计算出 raycaster 所需点的位置,以屏幕为中心点,范围 -1 到 1 179 | const bounds = client.getBoundingClientRect(); 180 | const mouse = this.mouse; 181 | // window.pageYOffset 鼠标滚动的距离 182 | // clientTop 一个元素顶部边框的宽度 183 | mouse.x = e.originEvent.clientX - bounds.x; 184 | mouse.y = e.originEvent.clientY - bounds.y; 185 | mouse.x = ( mouse.x / bounds.width ) * 2 - 1; 186 | mouse.y = - ( mouse.y / bounds.height ) * 2 + 1; 187 | const camera = this.layer.getCamera(); 188 | this.raycaster?.setFromCamera(mouse, camera); 189 | const intersects = this.raycaster?.intersectObject(this.group, true); 190 | if(intersects?.length){ 191 | const obj = intersects[0].object 192 | const batchData = {} 193 | const batchTable = this.getBatchTable(obj) 194 | if(batchTable){ 195 | const keys = batchTable.getKeys() 196 | keys.forEach( v => { 197 | batchData[v] = batchTable.getData(v) 198 | }) 199 | } 200 | return { 201 | object: obj, 202 | batchData 203 | } 204 | } 205 | return null 206 | } 207 | 208 | getBatchTable(selectedMesh){ 209 | if(!selectedMesh){ 210 | return null 211 | } 212 | if(selectedMesh.batchTable){ 213 | return selectedMesh.batchTable 214 | } 215 | return this.getBatchTable(selectedMesh.parent) 216 | } 217 | 218 | setPosition(position) { 219 | const positionConvert = this.layer.convertLngLat(position); 220 | this.group.position.setX(positionConvert[0]); 221 | this.group.position.setY(positionConvert[1]); 222 | this.refresh(); 223 | this.position = position; 224 | } 225 | 226 | setRotation(rotation: Vec) { 227 | if (rotation) { 228 | const x = Math.PI / 180 * (rotation.x || 0); 229 | const y = Math.PI / 180 * (rotation.y || 0); 230 | const z = Math.PI / 180 * (rotation.z || 0); 231 | this.group.rotation.set(x, y, z); 232 | this.refresh(); 233 | } 234 | } 235 | 236 | setTranslate(translate: Vec){ 237 | if(translate){ 238 | this.group.translateX(translate.x) 239 | this.group.translateY(translate.y) 240 | this.group.translateZ(translate.z) 241 | this.refresh() 242 | } 243 | } 244 | 245 | setScale(scale: number | Vec) { 246 | let scaleVec: Vec; 247 | if (typeof scale === 'number') { 248 | scaleVec = { 249 | x: scale, 250 | y: scale, 251 | z: scale 252 | }; 253 | } else { 254 | scaleVec = scale; 255 | } 256 | this.group.scale.set(scaleVec.x, scaleVec.y, scaleVec.z); 257 | this.refresh(); 258 | } 259 | 260 | refresh() { 261 | this.layer.update(); 262 | } 263 | 264 | show() { 265 | this.group.visible = true; 266 | this.refresh(); 267 | } 268 | 269 | hide() { 270 | this.group.visible = false; 271 | this.refresh(); 272 | } 273 | 274 | animate() { 275 | this.animationFrame = requestAnimationFrame(() => { 276 | this.update(); 277 | this.animate(); 278 | if(!this.hasResetCenter && this.tilesRenderer.root){ 279 | const box = new Box3() 280 | // 重置region的中心点 281 | if ( this.tilesRenderer.root && (this.tilesRenderer.root as any).boundingVolume.region ) { 282 | this.tilesRenderer.getOrientedBounds( box, this.parentGroup.matrix ); 283 | this.parentGroup.matrix.decompose( this.parentGroup.position, this.parentGroup.quaternion, this.parentGroup.scale ); 284 | this.parentGroup.position.set( 0, 0, 0 ); 285 | this.parentGroup.quaternion.invert(); 286 | this.parentGroup.scale.set( 1, 1, 1 ); 287 | } 288 | this.group.updateMatrixWorld( false ); 289 | // 获取中心点,将3dtiles加载的group的中心点重置,变成默认为0 0 0 290 | if ( this.tilesRenderer.getBounds( box ) ) { 291 | // 重置整体的坐标,转化为高德需要的投影坐标 292 | this.resetPosition(box); 293 | box.getCenter( this.tilesRenderer.group.position ); 294 | this.tilesRenderer.group.position.multiplyScalar( - 1 ); 295 | this.hasResetCenter = true; 296 | } 297 | } 298 | }); 299 | } 300 | 301 | resetPosition(box: Box3){ 302 | if(!this.position){ 303 | // 从3dtiles盒模型中获取中心点坐标 304 | const center = new Vector3(); 305 | box.getCenter(center); 306 | // 将中心点坐标转化为经纬度和海拔 307 | const result = convertToWGS84(center.x, center.y, center.z); 308 | // console.log('result: ', result) 309 | const lnglat = gps84_To_Gcj02(result.longitude, result.latitude); 310 | if(this.options.autoFocus){ 311 | this.layer.getMap().setCenter(lnglat) 312 | } 313 | this.setPosition(lnglat); 314 | /*this.setTranslate({ 315 | x:0, 316 | y:0, 317 | z: result.height 318 | })*/ 319 | this.emit('autoPosition') 320 | } 321 | } 322 | 323 | update(){ 324 | this.layer.getCamera().updateMatrixWorld(); 325 | this.tilesRenderer?.update(); 326 | this.layer.update() 327 | if(this.statsContainer){ 328 | const tiles = this.tilesRenderer as any; 329 | this.statsContainer.innerHTML = `正在下载: ${ tiles.stats.downloading } 正在编译: ${ tiles.stats.parsing } 已显示: ${ tiles.group.children.length }`; 330 | } 331 | } 332 | 333 | getGroup(){ 334 | return this.group 335 | } 336 | 337 | getTilesRenderer(){ 338 | return this.tilesRenderer 339 | } 340 | 341 | destroy() { 342 | cancelAnimationFrame(this.animationFrame) 343 | this.unbindEvents() 344 | this.layer.remove(this.group) 345 | this.tilesRenderer?.dispose() 346 | this.group = null 347 | // this.tilesRenderer = undefined 348 | this.layer = null 349 | if(this.statsContainer){ 350 | this.statsContainer.remove() 351 | this.statsContainer = undefined 352 | } 353 | /*if (this.object) { 354 | clearGroup(this.object); 355 | this.object = null; 356 | this.layer = null; 357 | }*/ 358 | } 359 | 360 | } 361 | 362 | export {Layer3DTiles} 363 | --------------------------------------------------------------------------------