Map.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005
  1. import * as THREE from "../../../../libs/three.js/build/three.module.js";
  2. import math from '../../utils/math.js'
  3. import browser from '../../utils/browser.js'
  4. let texLoader = new THREE.TextureLoader()
  5. texLoader.crossOrigin = "anonymous"
  6. let createErrorMaterial = function() {
  7. var t = new THREE.MeshBasicMaterial({
  8. transparent: !0,
  9. depthWrite: !1,
  10. depthTest: !0,
  11. opacity: 1,
  12. side: THREE.DoubleSide
  13. });
  14. return t.color = new THREE.Color(3355443),
  15. t
  16. }
  17. let tempVector = new THREE.Vector3, //sharedata
  18. face1 = new THREE.Face3(0,1,2),
  19. face2 = new THREE.Face3(2,3,0),
  20. errorMaterial = createErrorMaterial(),
  21. uv00 = new THREE.Vector2(0,0),
  22. uv01 = new THREE.Vector2(0,1),
  23. uv10 = new THREE.Vector2(1,0),
  24. uv11 = new THREE.Vector2(1,1),
  25. face1UV = [uv00, uv10, uv11],
  26. face2UV = [uv11, uv01, uv00]
  27. const HALF_WORLD_SIZE = 21e6 //略大于半个周长(mapSizeM/2)
  28. const MAX_VERTICAL_DIST = 2
  29. const MAX_VERTICAL_DIST_TO_BEST = 1
  30. function defineLocalProj(locationLonLat){
  31. proj4.defs("LOCAL_MAP", "+proj=tmerc +ellps=WGS84 +lon_0=" + locationLonLat[0].toPrecision(15) + " +lat_0=" + locationLonLat[1].toPrecision(15));
  32. }
  33. const getSid = (function(){
  34. let sid = 0
  35. return function(){
  36. return sid++
  37. }
  38. })()
  39. //高德坐标拾取工具 : https://lbs.amap.com/tools/picker
  40. export class MapLayer extends THREE.EventDispatcher{ // 包括了 MapLayerBase SceneLayer
  41. constructor(viewer_, viewport){
  42. super()
  43. this.sceneGroup = new THREE.Object3D;
  44. this.sceneGroup.name = "MapLayer"
  45. this.waitQueue = [] //等待加载的
  46. this.loadingInProgress = 0
  47. this.maps = []
  48. this.frustum = new THREE.Frustum
  49. this.frustumMatrix = new THREE.Matrix4
  50. this.tileColor = new THREE.Color(16777215)
  51. this.viewport = viewport
  52. this.changeViewer(viewer_)
  53. //添加地图
  54. var map = new TiledMapOpenStreetMap(this, this.tileColor )
  55. this.addMap(map)
  56. //map.setEnable(false)
  57. this.sceneGroup.addEventListener('isVisible',()=>{
  58. this.viewer.mapChanged = true
  59. })
  60. }
  61. addMapEntity(data, datasetId){
  62. if(!data || !data[0]){
  63. Potree.Log('平面图无数据',{font:'red'})
  64. return
  65. }
  66. data[0].datasetId = datasetId
  67. var floorplan = new TiledMapFromEntity(this, this.tileColor, data[0] )//[0]?
  68. if(floorplan){
  69. floorplan.name += "_"+ datasetId
  70. this.addMap(floorplan)
  71. floorplan.updateProjection()
  72. floorplan.updateObjectGroup()
  73. let visible = false
  74. if(datasetId in Potree.settings.floorplanEnables){
  75. visible = Potree.settings.floorplanEnables[datasetId]
  76. }else{
  77. visible = Potree.settings.floorplanEnable
  78. }
  79. if(visible){
  80. this.needUpdate = true
  81. }else{
  82. floorplan.setEnable(false)
  83. }
  84. this.dispatchEvent({type:'floorplanLoaded', floorplan})
  85. }
  86. return floorplan
  87. }
  88. getFloorplan(datasetId){
  89. return this.maps.find(e=>e.name == 'floorplan'+"_"+ datasetId )
  90. }
  91. addMap(t){
  92. this.maps.push(t)
  93. //this.view.invalidateScene()
  94. this.needUpdate = true
  95. this.viewer.mapChanged = true
  96. }
  97. removeMap(t){
  98. var e = this.maps.indexOf(t);
  99. if(e >= 0){
  100. t.removeFromSceneGroup(this.sceneGroup)
  101. this.maps.splice(e, 1)
  102. }
  103. /* this.view.invalidateScene() */
  104. this.needUpdate = true
  105. this.viewer.mapChanged = true
  106. }
  107. changeViewer(viewer_){//add
  108. this.viewer = viewer_
  109. }
  110. initProjection(){
  111. this.maps.forEach(map=>{
  112. map.updateProjection()
  113. map.updateObjectGroup()
  114. })
  115. }
  116. visibilityChanged(){
  117. if (!this.visible)
  118. for (var t = 0, e = this.maps; t < e.length; t++){
  119. e[t].removeFromSceneGroup(this.sceneGroup)
  120. }
  121. }
  122. update(){
  123. this.needUpdate = false
  124. if(this.disabled || !this.maps.find(e=>!e.disabled) || !this.maps.find(e=>e.objectGroup.visible) )return //add
  125. this.viewer.mapChanged = true
  126. var e, n, i, r, o;
  127. this.updateTimer = void 0,
  128. e = this.viewport.camera,
  129. n = e.projectionMatrix.clone()
  130. let expandRatio = 1.3
  131. n.elements[0] /= expandRatio
  132. n.elements[5] /= expandRatio // 为了缓存吗,使边界处也提前加载,扩大显示区域
  133. this.frustumMatrix.multiplyMatrices(n, e.matrixWorldInverse),
  134. this.frustum.setFromProjectionMatrix(this.frustumMatrix),
  135. this.frustum.planes[4].setComponents(0, 0, 0, 0),
  136. this.frustum.planes[5].setComponents(0, 0, 0, 0),
  137. i = !0
  138. //console.log('-------------update-----------')
  139. for (r = 0; r < this.maps.length; r++){
  140. var map = this.maps[r]
  141. i = map.update(this.frustum, this.sceneGroup) && i;
  142. }
  143. return [2, i]
  144. }
  145. updateProjection(){
  146. for (var t = 0, e = this.maps; t < e.length; t++){
  147. var n = e[t];
  148. n.clearProjection(),
  149. n.updateObjectGroup()
  150. }
  151. }
  152. }
  153. export class TiledMapBase extends THREE.EventDispatcher{
  154. constructor( name, mapLayer, tileColor, projection){
  155. super();
  156. this.name = name
  157. this.mapLayer = mapLayer,
  158. this.tileColor = tileColor,
  159. this.bias = 0
  160. this.zIndex = -1
  161. this.objectGroup = new THREE.Object3D;
  162. this.objectGroup.name = name
  163. this.objectGroupAdded = !1,
  164. this.baseTile = new MapTile(this, this.objectGroup, this.tileColor, null, '0'),
  165. this.isTileVisibleBox = new THREE.Box3,
  166. this.isTileVisibleVec = new THREE.Vector3
  167. this.projection = projection
  168. this._zoomLevel = 0;//1-20
  169. this.objectGroup.addEventListener('isVisible',()=>{
  170. this.mapLayer.viewer.mapChanged = true
  171. })
  172. this.computeCount = 0
  173. this.maxLoading = 3
  174. this.loadFailCount = 0
  175. this.loadingInProgress = 0
  176. }
  177. get zoomLevel(){
  178. return this._zoomLevel
  179. }
  180. set zoomLevel(zoomLevel){
  181. if(this._zoomLevel != zoomLevel){
  182. this._zoomLevel = zoomLevel
  183. this.dispatchEvent({type:'zoomLevelChange',zoomLevel})
  184. //if(this.name == 'map')console.log(zoomLevel,viewer.mapViewer.camera.zoom)
  185. }
  186. }
  187. updateObjectGroup(){
  188. this.position && this.objectGroup.position.copy(this.position),
  189. this.quaternion && this.objectGroup.quaternion.copy(this.quaternion),
  190. this.objectGroup.updateMatrixWorld(!0)
  191. }
  192. updateProjection(){
  193. if(!this.transformMapToLocal){
  194. this.transformMapToLocal = proj4(this.projection, "LOCAL_MAP")
  195. }
  196. }
  197. clearProjection(){
  198. this.transformMapToLocal = void 0
  199. this.projection !== 'LOCAL_MAP' && this.baseTile.remove()
  200. }
  201. setEnable(enable){//add
  202. if(!this.disabled == enable)return
  203. if(enable){
  204. //console.log('setEnable',true)
  205. }
  206. this.disabled = !enable
  207. Potree.Utils.updateVisible(this.objectGroup, 'setEnable', enable)
  208. if(!enable){
  209. this.baseTile.remove()
  210. }else{
  211. this.mapLayer.needUpdate = true
  212. }
  213. }
  214. update(e, n){
  215. this.computeCount = 0
  216. var unavailable = (this.disabled || !this.objectGroup.visible)//地图即使不显示也要获得zoomlevel
  217. if(this.name != 'map' && unavailable)return
  218. this.updateProjection()
  219. if(!this.transformMapToLocal)return
  220. if (!this.isTileVisible(new THREE.Vector3(0,0,0), this.mapSizeM, e))
  221. return this.removeFromSceneGroup(n), !0;
  222. let viewport = this.mapLayer.viewport
  223. var i = new THREE.Vector3(-.5 * this.mapSizeM,0,0);
  224. i.applyMatrix4(this.objectGroup.matrixWorld),
  225. i.project(viewport.camera);
  226. var o = new THREE.Vector3(.5 * this.mapSizeM,0,0);
  227. o.applyMatrix4(this.objectGroup.matrixWorld),
  228. o.project(viewport.camera);
  229. var a = viewport.resolution.x
  230. , s = viewport.resolution.y
  231. if (a <= 0 || s <= 0 || isNaN(i.x) || isNaN(o.x)) return !1;
  232. i.sub(o),
  233. i.x *= a / 2,
  234. i.y *= s / 2;
  235. let scale
  236. if(this.name == 'map'){
  237. //add 高纬度的因倾斜而造成tile较小,所以放大些,否则会造成显示的tile过多而卡
  238. let lonlat = viewer.transform.lonlatToLocal.inverse(viewport.camera.position.clone())
  239. let cos = Math.cos(THREE.Math.degToRad(lonlat.y)); //越小就在纬度上越高,tile表现越小
  240. //为什么lonlat.y会超出90?
  241. /* if(lonlat.y>90){
  242. console.log('lonlat.y>90',lonlat.y)
  243. } */
  244. cos = THREE.Math.clamp(cos, 0,1)
  245. let lonShift = Math.abs(viewer.mapViewer.camera.position.x / this.mapSizeM * 16 ) //越大就在经度离中心越远,tile表现越大 。
  246. lonShift = THREE.Math.clamp(lonShift, 0, Math.PI)
  247. lonShift = (1 - Math.sin( 1/2 * lonShift + Math.PI/2 )) * Math.PI // 0-Math.PI sin增速向上
  248. scale = 0.5 * cos * (1+lonShift) + 0.5 * Math.pow(cos, lonShift)
  249. }else{
  250. scale = 1
  251. }
  252. var c = this.tileSizePx / i.length() / scale //多除以一个scale缩放因子,scale越大level越小
  253. , level = Math.ceil(-Math.log(c) / Math.log(2) - this.bias);
  254. level = Math.max(level, 0)
  255. level = Math.min(level, void 0 === this.maxDepth ? 1 / 0 : this.maxDepth)
  256. this.zoomLevel = level//add
  257. /* if(isNaN(this.zoomLevel )){
  258. console.log(level, cos , scale , lonlat )
  259. } */
  260. if(!unavailable){
  261. this.addToSceneGroup(n)
  262. return this.baseTile.update(this, e, level, this.mapSizeM, 0, 0, "")
  263. }
  264. }
  265. isTileVisible(e, n, i){
  266. if (n > HALF_WORLD_SIZE) return !0;
  267. var r = .5 * n;
  268. //简单版:
  269. this.transformMapToLocal.forward(e) //e转化为local
  270. this.isTileVisibleBox.makeEmpty()
  271. this.isTileVisibleVec.set(e.x - r, e.y - r, e.z).applyMatrix4(this.objectGroup.matrixWorld)
  272. this.isTileVisibleBox.expandByPoint(this.isTileVisibleVec)
  273. this.isTileVisibleVec.set(e.x - r, e.y + r, e.z).applyMatrix4(this.objectGroup.matrixWorld)
  274. this.isTileVisibleBox.expandByPoint(this.isTileVisibleVec)
  275. this.isTileVisibleVec.set(e.x + r, e.y - r, e.z).applyMatrix4(this.objectGroup.matrixWorld)
  276. this.isTileVisibleBox.expandByPoint(this.isTileVisibleVec)
  277. this.isTileVisibleVec.set(e.x + r, e.y + r, e.z).applyMatrix4(this.objectGroup.matrixWorld)
  278. this.isTileVisibleBox.expandByPoint(this.isTileVisibleVec)
  279. //仿造createMesh写的准确版,但会因为大的tile非矩形,而视口是矩形,若视口刚好在tile的曲线边缘外却识别为可见,就会创建冗余tile。 但上面那个简单版在zoomlevel低的时候地球边缘容易有识别不到的tile,造成黑色三角形。
  280. //容易出现奇怪的mesh
  281. /* this.isTileVisibleBox.makeEmpty()
  282. this.isTileVisibleVec.set(e.x - r, e.y - r, e.z)
  283. this.transformMapToLocal.forward(this.isTileVisibleVec)
  284. this.isTileVisibleVec.applyMatrix4(this.objectGroup.matrixWorld)
  285. this.isTileVisibleBox.expandByPoint(this.isTileVisibleVec)
  286. this.isTileVisibleVec.set(e.x - r, e.y + r, e.z)
  287. this.transformMapToLocal.forward(this.isTileVisibleVec)
  288. this.isTileVisibleVec.applyMatrix4(this.objectGroup.matrixWorld)
  289. this.isTileVisibleBox.expandByPoint(this.isTileVisibleVec)
  290. this.isTileVisibleVec.set(e.x + r, e.y - r, e.z)
  291. this.transformMapToLocal.forward(this.isTileVisibleVec)
  292. this.isTileVisibleVec.applyMatrix4(this.objectGroup.matrixWorld)
  293. this.isTileVisibleBox.expandByPoint(this.isTileVisibleVec)
  294. this.isTileVisibleVec.set(e.x + r, e.y + r, e.z)
  295. this.transformMapToLocal.forward(this.isTileVisibleVec)
  296. this.isTileVisibleVec.applyMatrix4(this.objectGroup.matrixWorld)
  297. this.isTileVisibleBox.expandByPoint(this.isTileVisibleVec) */
  298. return i.intersectsBox(this.isTileVisibleBox)
  299. }
  300. addToSceneGroup(t){
  301. this.objectGroupAdded || (t.add(this.objectGroup),
  302. this.objectGroupAdded = !0)
  303. }
  304. removeFromSceneGroup(t){
  305. this.baseTile.remove(),
  306. this.objectGroupAdded && (t.remove(this.objectGroup),
  307. this.objectGroupAdded = !1)
  308. }
  309. }
  310. const loadDone = (tile, success)=>{
  311. tile.map.mapLayer.loadingInProgress--
  312. tile.map.loadingInProgress--
  313. //console.log('loaddone', tile.name, 'loadingInProgress',tile.map.mapLayer.loadingInProgress, Date.now())
  314. tile.loading = false
  315. let next = tile.map.mapLayer.waitQueue[0]
  316. if(next){
  317. addLoadTile(next)
  318. }else{
  319. if(tile.map.mapLayer.loadingInProgress == 0){//注意这时候不一定就加载完了,300ms后可能还会有新的tile加载
  320. //console.log('loadDone All ?', Date.now())
  321. tile.map.mapLayer.dispatchEvent('loadDone')
  322. }
  323. }
  324. tile.mesh && (tile.mesh.material.needsUpdate = true)
  325. }
  326. function addLoadTile(tile){
  327. /* if(tile.texURL && tile.texURL.includes('testdata') ){
  328. console.error('addLoadTile', tile.texURL.split('map_tiles/')[1] )
  329. } */
  330. if(tile.map.loadingInProgress < tile.map.maxLoading){
  331. if(!tile.mesh)return; //有时候会遇到这种情况, 为什么没有被cancelLoad呢?
  332. tile.map.mapLayer.loadingInProgress++
  333. tile.map.loadingInProgress ++
  334. tile.map.mapLayer.dispatchEvent('startLoad')
  335. //console.log('addLoad', 'loadingInProgress',tile.map.mapLayer.loadingInProgress, Date.now())
  336. //tile.texURL && tile.texURL.includes('testdata') && console.log('startloadTile ', tile.texURL.split('map_tiles/')[1] )
  337. tile.loading = true
  338. let index = tile.map.mapLayer.waitQueue.indexOf(tile);
  339. index > -1 && tile.map.mapLayer.waitQueue.splice(index,1)
  340. let tex = tile.mesh.material.map = texLoader.load(tile.texURL, (tex)=>{ //如果一直加载不了会影响其他的加载,如google地图没有vpn会使全景图一直加载不了
  341. if(tile.mesh){//如果还要显示的话
  342. tile.textureLoaded = true
  343. tile.mesh.material.opacity = 1
  344. tile.map.mapLayer.viewer.mapChanged = true
  345. tile.map.mapLayer.needUpdate = true //表示还要继续update(以removeChildren)
  346. if(tile.map instanceof TiledMapOpenStreetMap){
  347. tile.map.maxLoading = browser.isMobile() ? 5 : 10;
  348. }
  349. }else{
  350. //tile.texURL && tile.texURL.includes('testdata') && console.log('loadDone and dispose', tile.texURL.split('map_tiles/')[1] )
  351. tex.dispose()
  352. }
  353. loadDone(tile, true)
  354. } , void 0, (()=>{//error
  355. tile.textureLoaded = !0
  356. if(tile.mesh){
  357. tile.mesh.material.dispose()
  358. tile.mesh.material = errorMaterial
  359. tile.map.mapLayer.viewer.mapChanged = true
  360. }
  361. loadDone(tile, false)
  362. tile.map.loadFailCount ++ ;
  363. if(tile.map instanceof TiledMapOpenStreetMap && Potree.settings.mapCompany == 'google' && tile.map.loadFailCount > 3){//极有可能没有vpn为了防止影响到其他资源加载,减少加载的个数
  364. tile.map.maxLoading = 2;
  365. }
  366. }))
  367. tex.anisotropy = 0
  368. tex.generateMipmaps = !1
  369. tex.minFilter = THREE.LinearFilter
  370. tex.magFilter = THREE.LinearFilter
  371. }else{
  372. tile.map.mapLayer.waitQueue.includes(tile) || tile.map.mapLayer.waitQueue.push(tile)
  373. }
  374. }
  375. function cancelLoad(tile,log){//如果等待加载,但还没开始加载,取消加载
  376. if(!tile.loading){
  377. let index = tile.map.mapLayer.waitQueue.indexOf(tile);
  378. index > -1 && tile.map.mapLayer.waitQueue.splice(index,1)
  379. //index > -1 && tile.texURL && tile.texURL.includes('testdata') && console.log('cancelLoad', tile.texURL.split('map_tiles/')[1]/* , (log && waitQueue.indexOf(tile)>-1) ? log:'' , tile.loading */ )
  380. }
  381. }
  382. export class MapTile{
  383. constructor(map, e, n, parent, name){
  384. this.map = map;
  385. this.name = name;
  386. this.parent = parent;
  387. this.objectGroup = e,
  388. this.tileColor = n,
  389. this.meshAdded = !1,
  390. this.textureLoaded = !1,
  391. this.children = []
  392. this.id = getSid();
  393. }
  394. update(e, n, i, r, o, a, s){
  395. return !!this.doesNotContainTilesToBeDisplayed(e) || (0 === i ? this.updateTile(e, r, o, a) : this.updateSubTiles(e, n, i, r, o, a, s))
  396. }
  397. doesNotContainTilesToBeDisplayed(t){
  398. return t.tilePresenceMap && t.tilePresenceMap.empty
  399. }
  400. updateTile(t, e, n, i){ //真正显示mesh的是这一层,最高level
  401. //if(this.map.name.includes('floorplan'))console.log('updateTile',this.name)
  402. if(!this.mesh){
  403. this.createTileObject(t, e, n, i)
  404. }
  405. if(!this.meshAdded){
  406. this.objectGroup.add(this.mesh)
  407. this.meshAdded = !0
  408. }
  409. if(this.textureLoaded){//贴图加载完就不需要子集了
  410. this.removeChildren()
  411. }else{
  412. this.cancelChildren() //add 停止加载子集
  413. }
  414. return this.textureLoaded
  415. }
  416. updateSubTiles(entity, n, level, o, a, s, c){
  417. //if(entity.name.includes('floorplan'))console.log('updateSubTiles',this.name) //名字越长代表level越高
  418. for (var childrenLoaded = !0, u = [-.25 * o, .25 * o, -.25 * o, .25 * o], d = [.25 * o, .25 * o, -.25 * o, -.25 * o], p = 0; p < 4; ++p){
  419. var h = c + p.toString(10);
  420. //一级(512):0 1 2 3分别为左上、右上、左下、右下。二级(1024)就是把一级的每一块分裂,如00 01 02 03分别是0的左上、右上、左下、右下……
  421. if (!entity.tilePresenceMap || entity.tilePresenceMap[h]){
  422. //去掉判断,直接显示
  423. var f = a + u[p]
  424. , m = s + d[p];
  425. tempVector.set(f, m, 0);
  426. this.map.computeCount ++
  427. //console.log(this.map.computeCount, this.name, 'level:',level)
  428. if (entity.isTileVisible(tempVector, .5 * o, n)){
  429. this.children[p] || (this.children[p] = new MapTile(this.map, this.objectGroup,this.tileColor, this, this.name+p ))
  430. //childrenLoaded = childrenLoaded && this.children[p].update(entity, n, level - 1, .5 * o, f, m, h) //这句会使若有一个tile还在加载,就阻断了。原版是这么写的。但是为了加快加载速度,改成下面两行。感觉直接全部updateTile也没太卡,不知道很大的场景会不会卡,单帧updateTile次数超过100次的话(应该不会吧,地图大小会限制住个数) -- 2023.12
  431. let childLoaded = this.children[p].update(entity, n, level - 1, .5 * o, f, m, h)
  432. childrenLoaded = childrenLoaded && childLoaded
  433. } else {
  434. if (this.children[p]){
  435. this.children[p].remove()
  436. delete this.children[p]
  437. }
  438. }
  439. }
  440. }
  441. return childrenLoaded && this.removeObject3D(), childrenLoaded //子项加载完,母项mesh可以去除。(最后母项的母项以及前面的都会被删除,只留最后的叶子结点)
  442. }
  443. /*
  444. 一层层往后加载。加入第一次加载到第4层(因为level精细度是第4层),给第4层可见tile加上mesh。
  445. 然后下一次加载到第5层,那么第4层的mesh就要被清空(当它所属的第5层子集都加载完后)
  446. */
  447. createTileObject(t, e, n, a){
  448. var s = this;
  449. this.mesh = this.createMesh(t.transformMapToLocal, e, n, a),
  450. this.textureLoaded = !1;
  451. var c = t.mapSizeM / e
  452. , l = Math.log(c) / Math.log(2)
  453. , u = n / e + .5 * (c - 1)
  454. , d = -a / e + .5 * (c - 1)
  455. , p = t.getTileUrl(Math.round(l), Math.round(u), Math.round(d));
  456. Potree.Utils.setObjectLayers(this.mesh, 'map' )
  457. this.mesh.renderOrder = -(1e6 - l - 100 * (t.zIndex || 0));
  458. this.mesh.name = this.name //add
  459. this.texURL = p
  460. /* let area = math.getArea(this.mesh.geometry.vertices.slice(0,3));
  461. if(area >0){
  462. this.mesh.visible = false
  463. console.log('area>0',this.mesh.name)
  464. } */
  465. addLoadTile(this)
  466. }
  467. createMesh(t, e, n, o){
  468. var a = new THREE.Geometry;
  469. return tempVector.set(n - e / 2, o - e / 2, 0),
  470. a.vertices.push(new THREE.Vector3().copy(t.forward(tempVector))),
  471. tempVector.set(n + e / 2, o - e / 2, 0),
  472. a.vertices.push(new THREE.Vector3().copy(t.forward(tempVector))),
  473. tempVector.set(n + e / 2, o + e / 2, 0),
  474. a.vertices.push(new THREE.Vector3().copy(t.forward(tempVector))),
  475. tempVector.set(n - e / 2, o + e / 2, 0),
  476. a.vertices.push(new THREE.Vector3().copy(t.forward(tempVector))),
  477. a.faces.push(face1),
  478. a.faces.push(face2),
  479. a.faceVertexUvs[0].push(face1UV),
  480. a.faceVertexUvs[0].push(face2UV),
  481. new THREE.Mesh(a,this.createMaterial())
  482. }
  483. createMaterial(){
  484. var t = new THREE.MeshBasicMaterial({
  485. transparent: !0,
  486. depthWrite: !1,
  487. depthTest: !0,
  488. opacity: 0,
  489. side: THREE.DoubleSide,
  490. });
  491. if(Potree.settings.isTest) {
  492. var colorHue = Math.random()
  493. t.color = new THREE.Color().setHSL(colorHue, 0.6, 0.92)
  494. }else{
  495. t.color = this.tileColor ? this.tileColor : new THREE.Color(16777215)
  496. }
  497. return t
  498. }
  499. traverse(f){//add
  500. return THREE.Mesh.prototype.traverse.call(this,f)
  501. }
  502. remove(){
  503. this.removeObject3D(),
  504. this.removeChildren()
  505. }
  506. removeObject3D(){
  507. let hasMesh = !!this.mesh
  508. if (this.mesh){
  509. this.objectGroup.remove(this.mesh)
  510. if (this.textureLoaded){
  511. var t = this.mesh.material.map;
  512. t && t.dispose()
  513. }else{
  514. cancelLoad(this)
  515. }
  516. this.mesh.material.dispose() //o.disposeMeshMaterial(this.mesh),
  517. this.mesh.geometry.dispose()
  518. this.mesh = void 0
  519. }
  520. this.meshAdded = !1,
  521. this.textureLoaded = !1
  522. //this.texURL && this.texURL.includes('testdata') && console.log('removeObject3D', this.id, 'hasMesh',hasMesh, this.texURL.split('map_tiles/')[1] )
  523. }
  524. removeChildren(){
  525. for (var t = 0, e = this.children; t < e.length; t++){
  526. var n = e[t];
  527. n && (n.removeObject3D(),
  528. n.removeChildren())
  529. }
  530. this.children.length = 0
  531. }
  532. cancelChildren(){//子集全部停止加载
  533. for (var t = 0, e = this.children; t < e.length; t++){
  534. var n = e[t];
  535. n && (cancelLoad(n, '提前'), n.cancelChildren())
  536. }
  537. }
  538. }
  539. proj4.defs("EPSG:3857", "+title=WGS 84 / Pseudo-Mercator +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs")
  540. //这里地图世界的中心是不是lon:0,lat:0
  541. export class TiledMapOpenStreetMap extends TiledMapBase{
  542. constructor(mapLayer, tileColor){
  543. //Potree.settings.mapCompany = 'google'
  544. super('map', mapLayer, tileColor/* , projection */ ) //EPSG projection
  545. //this.baseUrl = "https://wprd03.is.autonavi.com/appmaptile?style=7&x=${x}&y=${y}&z=${z}",
  546. //this.baseUrl = "https://webrd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x=${x}&y=${y}&z=${z}" //最高只到18 level
  547. this.switchStyle()
  548. this.tileSizePx = 256
  549. this.mapSizeM = 40075017 //总占据多少米(地球赤道周长) 和三维空间的不一样 - -, 空间上的是直径,地图上的是半个圆周
  550. this.bias = 0.5
  551. if(Potree.settings.mapCompany == 'google'){
  552. this.attribution = "© PopSmart, © 谷歌地图"
  553. this.projection = "EPSG:900913" //"EPSG:4326"//4550
  554. }else{
  555. this.attribution = "© PopSmart, © 高德地图"
  556. this.projection = "EPSG:3857"
  557. }
  558. }
  559. getTileUrl(t, e, n){
  560. return this.baseUrl.replace(/\${z}/, t.toString(10)).replace(/\${x}/, e.toString(10)).replace(/\${y}/, n.toString(10))
  561. }
  562. switchStyle(style = 'standard'){
  563. //if(Potree.settings.mapCompany == 'google')return
  564. if(style == this.style)return
  565. if(Potree.settings.mapCompany == 'google'){
  566. if(style == 'satellite'){//卫星
  567. this.baseUrl = "https://mt2.google.com/vt/lyrs=y@159000000&hl=zh-CN&gl=cn&x=${x}&y=${y}&z=${z}&s=mt1" /* "http://mt2.google.cn/vt/lyrs=m@177000000&hl=zh-CN&gl=cn&src=app&x=${x}&y=${y}&z=${z}" */ //最高只到19
  568. this.maxDepth = 22
  569. }else{
  570. this.baseUrl = "https://mt2.google.com/vt/lyrs=m@159000000&hl=zh-CN&gl=cn&x=${x}&y=${y}&z=${z}&s=mt1" /* "http://mt2.google.cn/vt/lyrs=m@177000000&hl=zh-CN&gl=cn&src=app&x=${x}&y=${y}&z=${z}" */ //最高只到19
  571. this.maxDepth = 22
  572. }
  573. /* 1)lyrs= 表示的是图层类型,即瓦片类型,具体含义如下:
  574. m:路线图
  575. t:地形图
  576. p:带标签的地形图
  577. s:卫星图
  578. y:带标签的卫星图
  579. h:标签层(路名、地名等)
  580. 2)& gl=CN
  581. 谷歌地图针对中国有两套坐标,一套做了偏移,一套没有。经测试在url加入gl=cn地图会有偏移。
  582. Tips:如果谷歌地图和RTK测量的WGS84坐标有偏差,可以尝试在url里去掉& gl=cn。
  583. 5)&hl=
  584. 设置地图注记文字语言类型,缺省默认为中文。
  585. hl=nl 中英双语
  586. hl=zh-CN 中文
  587. */
  588. }else{
  589. //baseUrl = "https://webst01.is.autonavi.com/appmaptile?lang=zh_cn&style=6&yrs=m&x=${x}&y=${y}&z=${z}" //卫星 maxDepth = 18
  590. //搜索高德地图瓦片url
  591. if(Potree.settings.isJiangMen){
  592. if(style == 'satellite'){//卫星
  593. this.maxDepth = 18
  594. this.baseUrl = "http://a.map.jms.gd/tile/weixing/${z}/${x}/${y}.png"
  595. }else{
  596. this.maxDepth = 19
  597. this.baseUrl = "http://a.map.jms.gd/tile/gd_xiangtu/${z}/${x}/${y}.png"
  598. }
  599. }else{
  600. if(style == 'satellite'){//卫星
  601. this.maxDepth = 18
  602. this.baseUrl = "https://webst01.is.autonavi.com/appmaptile?lang=zh_cn&style=6&yrs=m&x=${x}&y=${y}&z=${z}"
  603. }else{
  604. this.maxDepth = 19
  605. this.baseUrl = "https://wprd04.is.autonavi.com/appmaptile?lang=zh_cn&style=7&yrs=m&x=${x}&y=${y}&z=${z}" //
  606. }
  607. }
  608. }
  609. this.style = style
  610. this.setEnable(false)
  611. this.setEnable(true)
  612. viewer.dispatchEvent('content_changed')
  613. }
  614. }
  615. export class TiledMapFromEntity extends TiledMapBase{
  616. constructor(mapLayer, tileColor, data){
  617. super('floorplan', mapLayer, tileColor, "LOCAL" /* "EPSG:3857" *//* "WGS84" */) //直接就是本地坐标,没有projec
  618. let entity = this.tiledMapEntity = this.fillFromData(data)
  619. let time = entity.updateTime || entity.createTime
  620. this.tileSizePx = entity.tileSizePx,
  621. this.mapSizeM = entity.mapSizeM,
  622. this.maxDepth = entity.maxDepth;
  623. this.postStamp = time ? time.replace(/[^0-9]/ig,'') : (new Date).getTime()
  624. //this.projection = n.crsLocal,
  625. this.zIndex = 0,
  626. this.tilePresenceMap = this.decodeBitStream(this.tiledMapEntity.quadtree) //包含tile分裂信息,如果写错了会造成tile显示不全
  627. this.maxLoading = 5
  628. this.bias = 0.5 //越大加载层级越低,越模糊
  629. if(window.devicePixelRatio>=2 && window.innerHeight * window.innerWidth < 768*1024){ //手机还是加载高清点(反正也不需要截图等待),但平板太大了,要铺满屏幕可能慢,所以稍微模糊点?(反正可以继续放大去看)
  630. this.bias = 0 //level更高些
  631. }
  632. }
  633. fillFromData(e){
  634. let data = {}
  635. data.id = e.id
  636. data.globalLocation = Potree.Utils.VectorFactory.fromArray3(e.location)
  637. data.orientation = Potree.Utils.QuaternionFactory.fromArray(e.orientation)
  638. let pointcloud = viewer.scene.pointclouds.find(p=>p.dataset_id == e.datasetId)
  639. if(pointcloud.datasetData.mapping){
  640. data.filePath = `${Potree.settings.urls.prefix1}/${pointcloud.datasetData.mapping}${e.file_path}`
  641. }else{
  642. data.filePath = `${Potree.settings.urls.prefix1}${e.file_path}`
  643. }
  644. data.fileName = '$DEPTH/$X/$Y.png'
  645. data.type = e.type,
  646. data.mapSizeM = e.map_size_m,
  647. data.tileSizePx = e.tile_size_px,
  648. data.maxDepth = e.max_depth,
  649. data.quadtree = e.quadtree,
  650. data.floorId = e.floor_id,
  651. data.bundleId = e.bundle_id
  652. //this.computeLocalCoordinates()
  653. return data
  654. }
  655. computeLocalCoordinates(){
  656. if(proj4.defs("LOCAL_MAP")){
  657. let lonlat = this.tiledMapEntity.globalLocation
  658. /* if(window.AMapWith84){//需要转换
  659. lonlat = AMapWith84.wgs84ToAMap(lonlat)
  660. } */
  661. lonlat = viewer.transform.lonlatToLocal.forward(lonlat)
  662. this.tiledMapEntity.location = new THREE.Vector3().copy(lonlat)
  663. }
  664. }
  665. updateProjection() {
  666. super.updateProjection()
  667. if(!this.position){
  668. this.computeLocalCoordinates()
  669. }
  670. /* this.projection = this.TransformService.crsLocal,
  671. t.prototype.updateProjection.call(this) */
  672. }
  673. get position(){
  674. return this.tiledMapEntity.location
  675. /* enumerable: !0,
  676. configurable: !0 */
  677. }
  678. get quaternion(){
  679. return this.tiledMapEntity.orientation
  680. /* enumerable: !0,
  681. configurable: !0 */
  682. }
  683. getTileUrl(t, e, n) {
  684. var i = (this.tiledMapEntity.filePath + "/" + this.tiledMapEntity.fileName).replace(/\$DEPTH/g, t.toString(10)).replace(/\$X/g, e.toString(10)).replace(/\$Y/g, n.toString(10));
  685. return i += "?t=" + this.postStamp
  686. //this.RestService.addAuthorizationQueryParameter(i) //????
  687. }
  688. decodeBitStream(t) {
  689. if (!t)
  690. return {
  691. empty: !0
  692. };
  693. for (var e = {}, n = [e], i = 0; i < t.length; i++) {
  694. var r = n.shift()
  695. , o = parseInt(t.substr(i, 1), 16);
  696. if (1 & o) {
  697. var a = {};
  698. r[0] = a,
  699. n.push(a)
  700. }
  701. 2 & o && (a = {},
  702. r[1] = a,
  703. n.push(a)),
  704. 4 & o && (a = {},
  705. r[2] = a,
  706. n.push(a)),
  707. 8 & o && (a = {},
  708. r[3] = a,
  709. n.push(a))
  710. }
  711. var s = {
  712. empty: !0
  713. };
  714. return this.computeHashes(s, e, ""),
  715. s
  716. }
  717. computeHashes(t, e, n) {
  718. for (var i = 0; i < 4; i++)
  719. e[i] && (t[n + i.toString(10)] = !0,
  720. t.empty = !1,
  721. this.computeHashes(t, e[i], n + i.toString(10)))
  722. }
  723. }
  724. /*
  725. note:
  726. 目前缩小了能看出形态是一个地球。相机在高空朝下观测,地球平放着。
  727. 所以越靠近赤道和地球朝上的那面所在的中央经度(也就是local 0,0,0所对应的初始经度),tile越接近正方形。
  728. 所以在两极地区要怎么显示?
  729. 注册地理坐标时需要滚动地球吗?(修改初始经度、重定义NAVVIS:TMERC, 就需要更新所有三维世界中的物体位置)
  730. 切换中心点:
  731. var locationLonLat = viewer.transform.lonlatToLocal.inverse(viewer.mapViewer.camera.position.clone())
  732. proj4.defs("LOCAL_MAP", "+proj=tmerc +ellps=WGS84 +lon_0=" + locationLonLat.x.toPrecision(15) + " +lat_0=" + locationLonLat.y.toPrecision(15));
  733. viewer.mapViewer.mapLayer.maps[0].transformMapToLocal = null
  734. 地理注册部分地图上的1和2标记有两层意思。当地图全屏展示时,标记的是当前右侧经纬度的位置;当地图为小窗时,标记的是对应场景里三维位置。(所以感觉最好换一个ui?)且在2023.2.1之前才改好,之前都是后者。
  735. 为什么边缘总是有奇怪的mesh,是因为有顶点到背面了吗
  736. https://lbs.amap.com/tools/picker 高德坐标拾取器(手机登录),但和这里展示的不一样, 要用AMapWith84.aMapToWgs84({x: ,y: })转成84。 (qq or 手机登录) 所以potree本地和有AMapWith84函数的laser地图展现不一样
  737. https://www.google.com/maps/@77.7730021,-34.4952712,4z google取点
  738. 打印所有mapTile的名字,字符串最长的代表有显示的mesh。
  739. viewer.mapViewer.mapLayer.maps[0].baseTile.traverse(function(e){console.log(e.name)})
  740. 能查看有几个显示的mesh
  741. viewer.mapViewer.mapLayer.maps[0].objectGroup.children
  742. 图片地址中 tiles/4/3/9.png 第一个数字越高代表level越高,放得越大 . (tile.name.length也能反映出
  743. viewer.mapViewer.mapLayer.maps[1].objectGroup.children.map(e=>e.name.length-1)
  744. 目前看的几个场景floorplan原图是1米=33.03个像素 图宽度= 512*2^(max_depth-1) , map_size_m 代表整个地图是多少米
  745. 经纬度精度小数点后5位为米级别,6位为分米,7位是厘米
  746. */