├── .gitignore ├── .npmrc ├── LICENSE ├── Mapbox3DTiles.mjs ├── README.md ├── adam.html ├── ahn └── ahn_points.tar.gz ├── apikeys.js.example ├── app.js ├── dist ├── Mapbox3DTiles.js └── Mapbox3DTiles.js.map ├── index.html ├── models ├── Tree.gltf └── amsterdamcs.glb ├── package-lock.json ├── package.json ├── rollup.config.js ├── rotterdam ├── rotterdam_3dtiles_small.tar.gz └── tileset_min.json ├── screenshot.png ├── treelocs.mjs └── windmill ├── SM_Base.glb ├── SM_Nacelle.glb ├── SM_Pillar.glb └── SM_Rotor.glb /.gitignore: -------------------------------------------------------------------------------- 1 | apikeys.js 2 | *~ 3 | *.pnts 4 | *.b3dm 5 | data 6 | dist 7 | tileset.json 8 | node_modules 9 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://repo.geodan.io/repository/npm/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, Geodan 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Mapbox3DTiles.mjs: -------------------------------------------------------------------------------- 1 | const MERCATOR_A = 6378137.0; 2 | const WORLD_SIZE = MERCATOR_A * Math.PI * 2; 3 | 4 | const ThreeboxConstants = { 5 | WORLD_SIZE: WORLD_SIZE, 6 | PROJECTION_WORLD_SIZE: WORLD_SIZE / (MERCATOR_A * Math.PI * 2), 7 | MERCATOR_A: MERCATOR_A, 8 | DEG2RAD: Math.PI / 180, 9 | RAD2DEG: 180 / Math.PI, 10 | EARTH_CIRCUMFERENCE: 40075000, // In meters 11 | } 12 | 13 | /* 14 | mapbox-gl uses a camera fixed at the orgin (the middle of the canvas) The camera is only updated when rotated (bearing angle), 15 | pitched or when the map view is resized. 16 | When panning and zooming the map, the desired part of the world is translated and zoomed in front of the camera. The world is only updated when 17 | the map is panned or zoomed. 18 | 19 | The mapbox-gl internal coordinate system has origin (0,0) located at longitude -180 degrees and latitude 0 degrees. 20 | The scaling is 2^map.getZoom() * 512/EARTH_CIRCUMFERENCE_IN_METERS. At zoom=0 (scale=2^0=1), the whole world fits in 512 units. 21 | */ 22 | class CameraSync { 23 | constructor (map, camera, world) { 24 | this.map = map; 25 | this.camera = camera; 26 | this.active = true; 27 | this.updateCallback = ()=>{}; 28 | 29 | this.camera.matrixAutoUpdate = false; // We're in charge of the camera now! 30 | 31 | // Postion and configure the world group so we can scale it appropriately when the camera zooms 32 | this.world = world || new THREE.Group(); 33 | this.world.position.x = this.world.position.y = ThreeboxConstants.WORLD_SIZE/2; 34 | this.world.matrixAutoUpdate = false; 35 | 36 | //set up basic camera state 37 | this.state = { 38 | fov: 0.6435011087932844, // Math.atan(0.75); 39 | translateCenter: new THREE.Matrix4(), 40 | worldSizeRatio: 512/ThreeboxConstants.WORLD_SIZE 41 | }; 42 | 43 | this.state.translateCenter.makeTranslation(ThreeboxConstants.WORLD_SIZE/2, -ThreeboxConstants.WORLD_SIZE / 2, 0); 44 | 45 | // Listen for move events from the map and update the Three.js camera. Some attributes only change when viewport resizes, so update those accordingly 46 | this.updateCameraBound = ()=>this.updateCamera(); 47 | this.map.on('move', this.updateCameraBound); 48 | this.setupCameraBound = ()=>this.setupCamera(); 49 | this.map.on('resize', this.setupCameraBound); 50 | //this.map.on('moveend', ()=>this.updateCallback()) 51 | 52 | this.setupCamera(); 53 | } 54 | detachCamera(){ 55 | this.map.off('move', this.updateCameraBound); 56 | this.map.off('resize', this.setupCameraBound); 57 | this.updateCallback = null; 58 | this.map = null; 59 | this.camera = null; 60 | } 61 | setupCamera() { 62 | var t = this.map.transform 63 | const halfFov = this.state.fov / 2; 64 | var cameraToCenterDistance = 0.5 / Math.tan(halfFov) * t.height; 65 | 66 | this.state.cameraToCenterDistance = cameraToCenterDistance; 67 | this.state.cameraTranslateZ = new THREE.Matrix4().makeTranslation(0,0,cameraToCenterDistance); 68 | 69 | this.updateCamera(); 70 | } 71 | updateCamera(ev) { 72 | 73 | if(!this.camera) { 74 | console.log('nocamera') 75 | return; 76 | } 77 | 78 | var t = this.map.transform 79 | 80 | var halfFov = this.state.fov / 2; 81 | const groundAngle = Math.PI / 2 + t._pitch; 82 | this.state.topHalfSurfaceDistance = Math.sin(halfFov) * this.state.cameraToCenterDistance / Math.sin(Math.PI - groundAngle - halfFov); 83 | 84 | // Calculate z distance of the farthest fragment that should be rendered. 85 | const furthestDistance = Math.cos(Math.PI / 2 - t._pitch) * this.state.topHalfSurfaceDistance + this.state.cameraToCenterDistance; 86 | 87 | // Add a bit extra to avoid precision problems when a fragment's distance is exactly `furthestDistance` 88 | const farZ = furthestDistance * 1.01; 89 | 90 | this.camera.projectionMatrix = this.makePerspectiveMatrix(this.state.fov, t.width / t.height, 1, farZ); 91 | 92 | 93 | var cameraWorldMatrix = new THREE.Matrix4(); 94 | var rotatePitch = new THREE.Matrix4().makeRotationX(t._pitch); 95 | var rotateBearing = new THREE.Matrix4().makeRotationZ(t.angle); 96 | 97 | // Unlike the Mapbox GL JS camera, separate camera translation and rotation out into its world matrix 98 | // If this is applied directly to the projection matrix, it will work OK but break raycasting 99 | 100 | cameraWorldMatrix 101 | .premultiply(this.state.cameraTranslateZ) 102 | .premultiply(rotatePitch) 103 | .premultiply(rotateBearing); 104 | 105 | 106 | this.camera.matrixWorld.copy(cameraWorldMatrix); 107 | 108 | // Handle scaling and translation of objects in the map in the world's matrix transform, not the camera 109 | let zoomPow = t.scale * this.state.worldSizeRatio; 110 | let scale = new THREE.Matrix4(); 111 | scale.makeScale( zoomPow, zoomPow, zoomPow ); 112 | //console.log(`zoomPow: ${zoomPow}`); 113 | 114 | let translateMap = new THREE.Matrix4(); 115 | 116 | let x = -this.map.transform.x || -this.map.transform.point.x; 117 | let y = this.map.transform.y || this.map.transform.point.y; 118 | 119 | translateMap.makeTranslation(x, y, 0); 120 | 121 | this.world.matrix = new THREE.Matrix4; 122 | this.world.matrix 123 | //.premultiply(rotateMap) 124 | .premultiply(this.state.translateCenter) 125 | .premultiply(scale) 126 | .premultiply(translateMap); 127 | let matrixWorldInverse = new THREE.Matrix4(); 128 | matrixWorldInverse.getInverse(this.world.matrix); 129 | 130 | this.camera.projectionMatrixInverse.getInverse(this.camera.projectionMatrix); 131 | this.camera.matrixWorldInverse.getInverse(this.camera.matrixWorld); 132 | this.frustum = new THREE.Frustum(); 133 | this.frustum.setFromProjectionMatrix(new THREE.Matrix4().multiplyMatrices(this.camera.projectionMatrix, this.camera.matrixWorldInverse)); 134 | 135 | this.cameraPosition = new THREE.Vector3(0,0,0).unproject(this.camera).applyMatrix4(matrixWorldInverse); 136 | 137 | this.updateCallback(); 138 | } 139 | makePerspectiveMatrix(fovy, aspect, near, far) { 140 | 141 | let out = new THREE.Matrix4(); 142 | let f = 1.0 / Math.tan(fovy / 2), 143 | nf = 1 / (near - far); 144 | 145 | let newMatrix = [ 146 | f / aspect, 0, 0, 0, 147 | 0, f, 0, 0, 148 | 0, 0, (far + near) * nf, -1, 149 | 0, 0, (2 * far * near) * nf, 0 150 | ] 151 | 152 | out.elements = newMatrix 153 | return out; 154 | } 155 | } 156 | 157 | class TileSet { 158 | constructor(updateCallback){ 159 | if (!updateCallback) { 160 | updateCallback = ()=>{}; 161 | } 162 | this.updateCallback = updateCallback; 163 | this.url = null; 164 | this.version = null; 165 | this.gltfUpAxis = 'Z'; 166 | this.geometricError = null; 167 | this.root = null; 168 | } 169 | // TileSet.load 170 | async load(url, styleParams) { 171 | this.url = url; 172 | let resourcePath = THREE.LoaderUtils.extractUrlBase(url); 173 | 174 | let response = await fetch(this.url); 175 | if (!response.ok) { 176 | throw new Error(`HTTP ${response.status} - ${response.statusText}`); 177 | } 178 | let json = await response.json(); 179 | this.version = json.asset.version; 180 | this.geometricError = json.geometricError; 181 | this.refine = json.root.refine ? json.root.refine.toUpperCase() : 'ADD'; 182 | this.root = new ThreeDeeTile(json.root, resourcePath, styleParams, this.updateCallback, this.refine); 183 | return; 184 | } 185 | } 186 | 187 | class ThreeDeeTile { 188 | constructor(json, resourcePath, styleParams, updateCallback, parentRefine, parentTransform) { 189 | this.loaded = false; 190 | this.styleParams = styleParams; 191 | this.updateCallback = updateCallback; 192 | this.resourcePath = resourcePath; 193 | this.totalContent = new THREE.Group(); // Three JS Object3D Group for this tile and all its children 194 | this.tileContent = new THREE.Group(); // Three JS Object3D Group for this tile's content 195 | this.childContent = new THREE.Group(); // Three JS Object3D Group for this tile's children 196 | this.totalContent.add(this.tileContent); 197 | this.totalContent.add(this.childContent); 198 | this.boundingVolume = json.boundingVolume; 199 | if (this.boundingVolume && this.boundingVolume.box) { 200 | let b = this.boundingVolume.box; 201 | let extent = [b[0] - b[3], b[1] - b[7], b[0] + b[3], b[1] + b[7]]; 202 | let sw = new THREE.Vector3(extent[0], extent[1], b[2] - b[11]); 203 | let ne = new THREE.Vector3(extent[2], extent[3], b[2] + b[11]); 204 | this.box = new THREE.Box3(sw, ne); 205 | if (Mapbox3DTiles.DEBUG) { 206 | let geom = new THREE.BoxGeometry(b[3] * 2, b[7] * 2, b[11] * 2); 207 | let edges = new THREE.EdgesGeometry( geom ); 208 | this.debugColor = new THREE.Color( 0xffffff ); 209 | this.debugColor.setHex( Math.random() * 0xffffff ); 210 | let line = new THREE.LineSegments( edges, new THREE.LineBasicMaterial( {color:this.debugColor }) ); 211 | let trans = new THREE.Matrix4().makeTranslation(b[0], b[1], b[2]); 212 | line.applyMatrix4(trans); 213 | this.debugLine = line; 214 | } 215 | } else { 216 | this.extent = null; 217 | this.sw = null; 218 | this.ne = null; 219 | this.box = null; 220 | this.center = null; 221 | } 222 | this.refine = json.refine ? json.refine.toUpperCase() : parentRefine; 223 | this.geometricError = json.geometricError; 224 | this.worldTransform = parentTransform ? parentTransform.clone() : new THREE.Matrix4(); 225 | this.transform = json.transform; 226 | if (this.transform) 227 | { 228 | let tileMatrix = new THREE.Matrix4().fromArray(this.transform); 229 | this.totalContent.applyMatrix4(tileMatrix); 230 | this.worldTransform.multiply(tileMatrix); 231 | } 232 | this.content = json.content; 233 | this.children = []; 234 | if (json.children) { 235 | for (let i=0; ithis.updateCallback()); 275 | await subTileset.load(url, this.styleParams); 276 | if (subTileset.root) { 277 | this.box.applyMatrix4(this.worldTransform); 278 | let inverseMatrix = new THREE.Matrix4().getInverse(this.worldTransform); 279 | this.totalContent.applyMatrix4(inverseMatrix); 280 | this.totalContent.updateMatrixWorld(); 281 | this.worldTransform = new THREE.Matrix4(); 282 | 283 | this.children.push(subTileset.root); 284 | this.childContent.add(subTileset.root.totalContent); 285 | subTileset.root.totalContent.updateMatrixWorld(); 286 | subTileset.root.checkLoad(this.frustum, this.cameraPosition); 287 | } 288 | } catch (error) { 289 | // load failed (wrong url? connection issues?) 290 | // log error, do not break program flow 291 | console.error(error); 292 | } 293 | break; 294 | case 'b3dm': 295 | try { 296 | let loader = new THREE.GLTFLoader(); 297 | let b3dm = new B3DM(url); 298 | let rotateX = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(1, 0, 0), Math.PI / 2); 299 | this.tileContent.applyMatrix4(rotateX); // convert from GLTF Y-up to Z-up 300 | let b3dmData = await b3dm.load(); 301 | loader.parse(b3dmData.glbData, this.resourcePath, (gltf) => { 302 | 303 | gltf.scene.traverse(child => { 304 | if (child instanceof THREE.Mesh) { 305 | // some gltf has wrong bounding data, recompute here 306 | child.geometry.computeBoundingBox(); 307 | child.geometry.computeBoundingSphere(); 308 | child.material.depthWrite = true; // necessary for Velsen dataset? 309 | //Add the batchtable to the userData since gltLoader doesn't deal with it 310 | child.userData = b3dmData.batchTableJson; 311 | } 312 | }); 313 | if (this.styleParams.color != null || this.styleParams.opacity != null) { 314 | let color = new THREE.Color(this.styleParams.color); 315 | gltf.scene.traverse(child => { 316 | if (child instanceof THREE.Mesh) { 317 | if (this.styleParams.color != null) 318 | child.material.color = color; 319 | if (this.styleParams.opacity != null) { 320 | child.material.opacity = this.styleParams.opacity; 321 | child.material.transparent = this.styleParams.opacity < 1.0 ? true : false; 322 | } 323 | } 324 | }); 325 | } 326 | if (this.debugColor) { 327 | gltf.scene.traverse(child => { 328 | if (child instanceof THREE.Mesh) { 329 | child.material.color = this.debugColor; 330 | } 331 | }) 332 | } 333 | this.tileContent.add(gltf.scene); 334 | }, (error) => { 335 | throw new Error('error parsing gltf: ' + error); 336 | } 337 | ); 338 | } catch (error) { 339 | console.error(error); 340 | } 341 | break; 342 | case 'pnts': 343 | try { 344 | let pnts = new PNTS(url); 345 | let pointData = await pnts.load(); 346 | let geometry = new THREE.BufferGeometry(); 347 | geometry.setAttribute('position', new THREE.Float32BufferAttribute(pointData.points, 3)); 348 | let material = new THREE.PointsMaterial(); 349 | material.size = this.styleParams.pointsize != null ? this.styleParams.pointsize : 1.0; 350 | if (this.styleParams.color) { 351 | material.vertexColors = THREE.NoColors; 352 | material.color = new THREE.Color(this.styleParams.color); 353 | material.opacity = this.styleParams.opacity != null ? this.styleParams.opacity : 1.0; 354 | } else if (pointData.rgba) { 355 | geometry.setAttribute('color', new THREE.Float32BufferAttribute(pointData.rgba, 4)); 356 | material.vertexColors = THREE.VertexColors; 357 | } else if (pointData.rgb) { 358 | geometry.setAttribute('color', new THREE.Float32BufferAttribute(pointData.rgb, 3)); 359 | material.vertexColors = THREE.VertexColors; 360 | } 361 | this.tileContent.add(new THREE.Points( geometry, material )); 362 | if (pointData.rtc_center) { 363 | let c = pointData.rtc_center; 364 | this.tileContent.applyMatrix4(new THREE.Matrix4().makeTranslation(c[0], c[1], c[2])); 365 | } 366 | this.tileContent.add(new THREE.Points( geometry, material )); 367 | } catch (error) { 368 | console.error(error); 369 | } 370 | break; 371 | case 'i3dm': 372 | throw new Error('i3dm tiles not yet implemented'); 373 | break; 374 | case 'cmpt': 375 | throw new Error('cmpt tiles not yet implemented'); 376 | break; 377 | default: 378 | throw new Error('invalid tile type: ' + type); 379 | } 380 | } 381 | this.updateCallback(); 382 | } 383 | unload(includeChildren) { 384 | this.unloadedTileContent = true; 385 | this.totalContent.remove(this.tileContent); 386 | 387 | //this.tileContent.visible = false; 388 | if (includeChildren) { 389 | this.unloadedChildContent = true; 390 | this.totalContent.remove(this.childContent); 391 | //this.childContent.visible = false; 392 | } else { 393 | if (this.unloadedChildContent) { 394 | this.unloadedChildContent = false; 395 | this.totalContent.add(this.childContent); 396 | } 397 | } 398 | if (this.debugLine) { 399 | this.totalContent.remove(this.debugLine); 400 | this.unloadedDebugContent = true; 401 | } 402 | this.updateCallback(); 403 | // TODO: should we also free up memory? 404 | } 405 | checkLoad(frustum, cameraPosition) { 406 | 407 | this.frustum = frustum; 408 | this.cameraPosition = cameraPosition; 409 | /*this.load(); 410 | for (let i=0; i 0.0 && dist > this.geometricError * 50.0) { 435 | this.unload(true); 436 | return; 437 | } 438 | //console.log(`camPos: ${cameraPosition.z}, dist: ${dist}, geometricError: ${this.geometricError}`); 439 | 440 | // should we load this tile? 441 | if (this.refine == 'REPLACE' && dist < this.geometricError * 20.0) { 442 | this.unload(false); 443 | } else { 444 | this.load(); 445 | } 446 | 447 | 448 | // should we load its children? 449 | for (let i=0; i 0.25) { 471 | this.load(); 472 | this.children.forEach(child => { 473 | child.checkLoad(camera); 474 | }); 475 | }*/ 476 | 477 | } 478 | } 479 | 480 | class TileLoader { 481 | // This class contains the common code to load tile content, such as b3dm and pnts files. 482 | // It is not to be used directly. Instead, subclasses are used to implement specific 483 | // content loaders for different tile types. 484 | constructor(url) { 485 | this.url = url; 486 | this.type = url.slice(-4); 487 | this.version = null; 488 | this.byteLength = null; 489 | this.featureTableJSON = null; 490 | this.featureTableBinary = null; 491 | this.batchTableJson = null; 492 | this.batchTableBinary = null; 493 | this.binaryData = null; 494 | } 495 | // TileLoader.load 496 | async load() { 497 | let response = await fetch(this.url) 498 | if (!response.ok) { 499 | throw new Error(`HTTP ${response.status} - ${response.statusText}`); 500 | } 501 | let buffer = await response.arrayBuffer(); 502 | let res = this.parseResponse(buffer); 503 | return res; 504 | } 505 | parseResponse(buffer) { 506 | let header = new Uint32Array(buffer.slice(0, 28)); 507 | let decoder = new TextDecoder(); 508 | let magic = decoder.decode(new Uint8Array(buffer.slice(0, 4))); 509 | if (magic != this.type) { 510 | throw new Error(`Invalid magic string, expected '${this.type}', got '${this.magic}'`); 511 | } 512 | this.version = header[1]; 513 | this.byteLength = header[2]; 514 | let featureTableJSONByteLength = header[3]; 515 | let featureTableBinaryByteLength = header[4]; 516 | let batchTableJsonByteLength = header[5]; 517 | let batchTableBinaryByteLength = header[6]; 518 | 519 | /* 520 | console.log('magic: ' + magic); 521 | console.log('version: ' + this.version); 522 | console.log('featureTableJSONByteLength: ' + featureTableJSONByteLength); 523 | console.log('featureTableBinaryByteLength: ' + featureTableBinaryByteLength); 524 | console.log('batchTableJsonByteLength: ' + batchTableJsonByteLength); 525 | console.log('batchTableBinaryByteLength: ' + batchTableBinaryByteLength); 526 | */ 527 | 528 | let pos = 28; // header length 529 | if (featureTableJSONByteLength > 0) { 530 | this.featureTableJSON = JSON.parse(decoder.decode(new Uint8Array(buffer.slice(pos, pos+featureTableJSONByteLength)))); 531 | pos += featureTableJSONByteLength; 532 | } else { 533 | this.featureTableJSON = {}; 534 | } 535 | this.featureTableBinary = buffer.slice(pos, pos+featureTableBinaryByteLength); 536 | pos += featureTableBinaryByteLength; 537 | if (batchTableJsonByteLength > 0) { 538 | this.batchTableJson = JSON.parse(decoder.decode(new Uint8Array(buffer.slice(pos, pos+batchTableJsonByteLength)))); 539 | pos += batchTableJsonByteLength; 540 | } else { 541 | this.batchTableJson = {}; 542 | } 543 | this.batchTableBinary = buffer.slice(pos, pos+batchTableBinaryByteLength); 544 | pos += batchTableBinaryByteLength; 545 | this.binaryData = buffer.slice(pos); 546 | return this; 547 | } 548 | } 549 | 550 | class B3DM extends TileLoader { 551 | constructor(url) { 552 | super(url); 553 | this.glbData = null; 554 | } 555 | parseResponse(buffer) { 556 | super.parseResponse(buffer); 557 | this.glbData = this.binaryData; 558 | return this; 559 | } 560 | } 561 | 562 | class PNTS extends TileLoader{ 563 | constructor(url) { 564 | super(url); 565 | this.points = new Float32Array(); 566 | this.rgba = null; 567 | this.rgb = null; 568 | } 569 | parseResponse(buffer) { 570 | super.parseResponse(buffer); 571 | if (this.featureTableJSON.POINTS_LENGTH && this.featureTableJSON.POSITION) { 572 | let len = this.featureTableJSON.POINTS_LENGTH; 573 | let pos = this.featureTableJSON.POSITION.byteOffset; 574 | this.points = new Float32Array(this.featureTableBinary.slice(pos, pos + len * Float32Array.BYTES_PER_ELEMENT * 3)); 575 | this.rtc_center = this.featureTableJSON.RTC_CENTER; 576 | if (this.featureTableJSON.RGBA) { 577 | pos = this.featureTableJSON.RGBA.byteOffset; 578 | let colorInts = new Uint8Array(this.featureTableBinary.slice(pos, pos + len * Uint8Array.BYTES_PER_ELEMENT * 4)); 579 | let rgba = new Float32Array(colorInts.length); 580 | for (let i=0; i{ 662 | this.scene.add(light); 663 | }); 664 | this.world = new THREE.Group(); 665 | this.world.name = 'flatMercatorWorld'; 666 | this.scene.add(this.world); 667 | 668 | this.renderer = new THREE.WebGLRenderer({ 669 | alpha: true, 670 | antialias: true, 671 | canvas: map.getCanvas(), 672 | context: gl, 673 | }); 674 | 675 | this.renderer.shadowMap.enabled = true; 676 | this.renderer.autoClear = false; 677 | 678 | this.cameraSync = new CameraSync(this.map, this.camera, this.world); 679 | this.cameraSync.updateCallback = ()=>this.loadVisibleTiles(); 680 | 681 | //raycaster for mouse events 682 | this.raycaster = new THREE.Raycaster(); 683 | if (this.url) { 684 | this.tileset = new TileSet(()=>this.map.triggerRepaint()); 685 | this.tileset.load(this.url, this.styleParams).then(()=>{ 686 | if (this.tileset.root) { 687 | this.world.add(this.tileset.root.totalContent); 688 | this.world.updateMatrixWorld(); 689 | this.loadStatus = 1; 690 | this.loadVisibleTiles(); 691 | } 692 | }).catch(error=>{ 693 | console.error(`${error} (${this.url})`); 694 | }) 695 | } 696 | } 697 | onRemove(map, gl) { 698 | // todo: (much) more cleanup? 699 | this.map.queryRenderedFeatures = this.mapQueryRenderedFeatures; 700 | this.cameraSync.detachCamera(); 701 | this.cameraSync = null; 702 | } 703 | queryRenderedFeatures(geometry, options){ 704 | let result = this.mapQueryRenderedFeatures(geometry, options); 705 | if (!this.map || !this.map.transform) { 706 | return result; 707 | } 708 | if (!(options && options.layers && !options.layers.includes(this.id))) { 709 | if (geometry && geometry.x && geometry.y) { 710 | var mouse = new THREE.Vector2(); 711 | 712 | // // scale mouse pixel position to a percentage of the screen's width and height 713 | mouse.x = ( geometry.x / this.map.transform.width ) * 2 - 1; 714 | mouse.y = 1 - ( geometry.y / this.map.transform.height ) * 2; 715 | 716 | this.raycaster.setFromCamera(mouse, this.camera); 717 | 718 | // calculate objects intersecting the picking ray 719 | let intersects = this.raycaster.intersectObjects(this.world.children, true); 720 | if (intersects.length) { 721 | let feature = { 722 | "type": "Feature", 723 | "properties" : {}, 724 | "geometry" :{}, 725 | "layer": {"id": this.id, "type": "custom 3d"}, 726 | "source": this.url, 727 | "source-layer": null, 728 | "state": {} 729 | } 730 | let propertyIndex; 731 | let intersect = intersects[0]; 732 | if (intersect.object && intersect.object.geometry && 733 | intersect.object.geometry.attributes && 734 | intersect.object.geometry.attributes._batchid) { 735 | let geometry = intersect.object.geometry; 736 | let vertexIdx = intersect.faceIndex; 737 | if (geometry.index) { 738 | // indexed BufferGeometry 739 | vertexIdx = geometry.index.array[intersect.faceIndex*3]; 740 | propertyIndex = geometry.attributes._batchid.data.array[vertexIdx*7+6] 741 | } else { 742 | // un-indexed BufferGeometry 743 | propertyIndex = geometry.attributes._batchid.array[vertexIdx*3]; 744 | } 745 | let keys = Object.keys(intersect.object.userData); 746 | if (keys.length) { 747 | for (let propertyName of keys) { 748 | feature.properties[propertyName] = intersect.object.userData[propertyName][propertyIndex]; 749 | } 750 | } else { 751 | feature.properties.batchId = propertyIndex; 752 | } 753 | } else { 754 | if (intersect.index != null) { 755 | feature.properties.index = intersect.index; 756 | } else { 757 | feature.properties.name = this.id; 758 | } 759 | } 760 | if (options.outline != false && (intersect.object !== this.outlinedObject || 761 | (propertyIndex != null && propertyIndex !== this.outlinePropertyIndex) 762 | || (propertyIndex == null && intersect.index !== this.outlineIndex))) { 763 | 764 | //WIP 765 | //this.outlinePass.selectedObjects = [intersect.object]; 766 | 767 | // update outline 768 | if (this.outlineMesh) { 769 | let parent = this.outlineMesh.parent; 770 | parent.remove(this.outlineMesh); 771 | this.outlineMesh = null; 772 | } 773 | this.outlinePropertyIndex = propertyIndex; 774 | this.outlineIndex = intersect.index; 775 | if (intersect.object instanceof THREE.Mesh) { 776 | this.outlinedObject = intersect.object; 777 | let outlineMaterial = new THREE.MeshBasicMaterial({color: options.outlineColor? options.outlineColor : 0xff0000, wireframe: true}); 778 | let outlineMesh; 779 | if (intersect.object && 780 | intersect.object.geometry && 781 | intersect.object.geometry.attributes && 782 | intersect.object.geometry.attributes._batchid) { 783 | // create new geometry from faces that have same _batchid 784 | let geometry = intersect.object.geometry; 785 | if (geometry.index) { 786 | let ip1 = geometry.index.array[intersect.faceIndex*3]; 787 | let idx = geometry.attributes._batchid.data.array[ip1*7+6]; 788 | let blockFaces = []; 789 | for (let faceIndex = 0; faceIndex < geometry.index.array.length; faceIndex += 3) { 790 | let p1 = geometry.index.array[faceIndex]; 791 | if (geometry.attributes._batchid.data.array[p1*7+6] === idx) { 792 | let p2 = geometry.index.array[faceIndex+1]; 793 | if (geometry.attributes._batchid.data.array[p2*7+6] === idx) { 794 | let p3 = geometry.index.array[faceIndex+2]; 795 | if (geometry.attributes._batchid.data.array[p3*7+6] === idx) { 796 | blockFaces.push(faceIndex); 797 | } 798 | } 799 | } 800 | } 801 | let highLightGeometry = new THREE.Geometry(); 802 | for (let vertexCount = 0, face = 0; face < blockFaces.length; face++) { 803 | let faceIndex = blockFaces[face]; 804 | let p1 = geometry.index.array[faceIndex]; 805 | let p2 = geometry.index.array[faceIndex+1]; 806 | let p3 = geometry.index.array[faceIndex+2]; 807 | let positions = geometry.attributes.position.data.array; 808 | highLightGeometry.vertices.push( 809 | new THREE.Vector3(positions[p1*7], positions[p1*7+1], positions[p1*7+2]), 810 | new THREE.Vector3(positions[p2*7], positions[p2*7+1], positions[p2*7+2]), 811 | new THREE.Vector3(positions[p3*7], positions[p3*7+1], positions[p3*7+2]), 812 | ) 813 | highLightGeometry.faces.push(new THREE.Face3(vertexCount, vertexCount+1, vertexCount+2)); 814 | vertexCount += 3; 815 | } 816 | highLightGeometry.computeBoundingSphere(); 817 | outlineMesh = new THREE.Mesh(highLightGeometry, outlineMaterial); 818 | } else { 819 | let ip1 = intersect.faceIndex*3; 820 | let idx = geometry.attributes._batchid.array[ip1]; 821 | let blockFaces = []; 822 | for (let faceIndex = 0; faceIndex < geometry.attributes._batchid.array.length; faceIndex += 3) { 823 | let p1 = faceIndex; 824 | if (geometry.attributes._batchid.array[p1] === idx) { 825 | let p2 = faceIndex + 1; 826 | if (geometry.attributes._batchid.array[p2] === idx) { 827 | let p3 = faceIndex + 2; 828 | if (geometry.attributes._batchid.array[p3] === idx) { 829 | blockFaces.push(faceIndex); 830 | } 831 | } 832 | } 833 | } 834 | let highLightGeometry = new THREE.Geometry(); 835 | for (let vertexCount = 0, face = 0; face < blockFaces.length; face++) { 836 | let faceIndex = blockFaces[face] * 3; 837 | let positions = geometry.attributes.position.array; 838 | highLightGeometry.vertices.push( 839 | new THREE.Vector3(positions[faceIndex], positions[faceIndex+1], positions[faceIndex+2]), 840 | new THREE.Vector3(positions[faceIndex+3], positions[faceIndex+4], positions[faceIndex+5]), 841 | new THREE.Vector3(positions[faceIndex+6], positions[faceIndex+7], positions[faceIndex+8]), 842 | ) 843 | highLightGeometry.faces.push(new THREE.Face3(vertexCount, vertexCount+1, vertexCount+2)); 844 | vertexCount += 3; 845 | } 846 | highLightGeometry.computeBoundingSphere(); 847 | outlineMesh = new THREE.Mesh(highLightGeometry, outlineMaterial); 848 | } 849 | } else { 850 | outlineMesh = new THREE.Mesh(this.outlinedObject.geometry, outlineMaterial); 851 | } 852 | outlineMesh.position.x = this.outlinedObject.position.x+0.1; 853 | outlineMesh.position.y = this.outlinedObject.position.y+0.1; 854 | outlineMesh.position.z = this.outlinedObject.position.z+0.1; 855 | outlineMesh.quaternion.copy(this.outlinedObject.quaternion); 856 | outlineMesh.scale.copy(this.outlinedObject.scale); 857 | outlineMesh.matrix.copy(this.outlinedObject.matrix); 858 | outlineMesh.raycast = () =>{}; 859 | outlineMesh.name = "outline"; 860 | outlineMesh.wireframe = true; 861 | this.outlinedObject.parent.add(outlineMesh); 862 | this.outlineMesh = outlineMesh; 863 | } 864 | } 865 | result.unshift(feature); 866 | this.map.triggerRepaint(); 867 | } else { 868 | this.outlinedObject = null; 869 | if (this.outlineMesh) { 870 | let parent = this.outlineMesh.parent; 871 | parent.remove(this.outlineMesh); 872 | this.outlineMesh = null; 873 | this.map.triggerRepaint(); 874 | } 875 | } 876 | } 877 | } 878 | return result; 879 | } 880 | _update() { 881 | this.renderer.state.reset(); 882 | this.renderer.render (this.scene, this.camera); 883 | 884 | /*if (this.loadStatus == 1) { // first render after root tile is loaded 885 | this.loadStatus = 2; 886 | let frustum = new THREE.Frustum(); 887 | frustum.setFromProjectionMatrix(new THREE.Matrix4().multiplyMatrices(this.camera.projectionMatrix, this.camera.matrixWorldInverse)); 888 | if (this.tileset.root) { 889 | this.tileset.root.checkLoad(frustum, this.getCameraPosition()); 890 | } 891 | }*/ 892 | } 893 | update() { 894 | requestAnimationFrame(()=>this._update()); 895 | } 896 | render(gl, viewProjectionMatrix) { 897 | this._update(); 898 | } 899 | } 900 | 901 | export default class Mapbox3DTiles { 902 | 903 | static projectedUnitsPerMeter(latitude) { 904 | let c = ThreeboxConstants; 905 | return Math.abs( c.WORLD_SIZE / Math.cos( c.DEG2RAD * latitude ) / c.EARTH_CIRCUMFERENCE ); 906 | } 907 | static projectToWorld(coords) { 908 | // Spherical mercator forward projection, re-scaling to WORLD_SIZE 909 | let c = ThreeboxConstants; 910 | var projected = [ 911 | c.MERCATOR_A * c.DEG2RAD * coords[0] * c.PROJECTION_WORLD_SIZE, 912 | c.MERCATOR_A * Math.log(Math.tan((Math.PI*0.25) + (0.5 * c.DEG2RAD * coords[1]) )) * c.PROJECTION_WORLD_SIZE 913 | ]; 914 | 915 | //z dimension, defaulting to 0 if not provided 916 | if (!coords[2]) { 917 | projected.push(0) 918 | } else { 919 | var pixelsPerMeter = projectedUnitsPerMeter(coords[1]); 920 | projected.push( coords[2] * pixelsPerMeter ); 921 | } 922 | 923 | var result = new THREE.Vector3(projected[0], projected[1], projected[2]); 924 | 925 | return result; 926 | } 927 | } 928 | 929 | Mapbox3DTiles.Layer = Layer; 930 | 931 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mapbox-3dtiles 2 | 3D Tiles implementation using Mapbox GL JS custom layers 3 | 4 | See [https://geodan.github.io/mapbox-3dtiles/](https://geodan.github.io/mapbox-3dtiles/) for a working demo. 5 | 6 | ![Screenshot](https://github.com/Geodan/mapbox-3dtiles/raw/master/screenshot.png) 7 | 8 | This is a proof-of-concept implementation of a [3D Tiles](https://github.com/AnalyticalGraphicsInc/3d-tiles) viewer as a [Mapbox GL JS](https://github.com/mapbox/mapbox-gl-js) custom layer. WebGL rendering is implemented using [three.js](https://github.com/mrdoob/three.js/). Only Web Mercator (EPSG:3857) tilesets are supported, as this is the projection mapbox uses. Earth-centered earth-fixed tilesets are explicitly not supported. Tilesets used for testing were generated using [pg2b3dm](https://github.com/Geodan/pg2b3dm), using a PostGIS database with EPSG:3857 geometries. 9 | 10 | This is by no means a complete implementation of the 3D Tile specification. Currently the following features are supported: 11 | 12 | * Geometric error based tile loading 13 | * Replacement and additive refinement 14 | * Only Box bounding volumes are supported 15 | * Tile transforms 16 | * External tilesets 17 | * Tile types: 18 | * Batched 3D Model (b3dm) 19 | * Point Cloud (pnts): basic implementation 20 | 21 | The following features are not supported at this time: 22 | * Any coordinate system other than EPSG:3857 23 | * Region and sphere bounding volumes 24 | * Viewer request volumes 25 | * Instanced 3D Model (i3dm) tiles 26 | * Composite (cmpt) tiles 27 | * [3D Tile Styles](https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification/Styling) 28 | 29 | ## Instructions 30 | In a directory on your webserver run the folowing commands: 31 | ``` 32 | git clone https://github.com/Geodan/mapbox-3dtiles.git 33 | cd mapbox-3dtiles 34 | npm install 35 | cd ./ahn 36 | tar xvf ahn_points.tar.gz 37 | cd ../rotterdam 38 | tar xvf rotterdam_3dtiles_small.tar.gz 39 | ``` 40 | Next, copy file "apikeys.js.example" to "apikeys.js" and add your [mapbox token](https://docs.mapbox.com/help/how-mapbox-works/access-tokens/). Point your browser to the directory in question and you should see a basic viewer with 3d tiles content. 41 | 42 | ## Creating tilesets 43 | Tilesets can be created using [pg2b3dm](https://github.com/Geodan/pg2b3dm), using a PostGIS database table as source. The PostGIS table should contain 3D geometries in EPSG:3857 projection. 44 | 45 | Example query creating extruded 3D buildings in EPSG:3857: 46 | ``` 47 | DROP TABLE IF EXISTS .; 48 | CREATE TABLE . AS ( 49 | WITH extent AS ( 50 | SELECT ST_MakeEnvelope(, , , , ) geom 51 | ), 52 | footprints AS ( 53 | SELECT a.id AS id, a.height, a.geom 54 | FROM . a, extent b 55 | WHERE ST_Intersects(a.geom, b.geom) 56 | ) 57 | SELECT id, ST_Force3D(ST_Extrude(ST_Transform(ST_MakeValid(geom), 3857), 0, 0, height)) AS geom 58 | FROM footprints 59 | ); 60 | DELETE FROM . WHERE geom IS NULL; -- cleanup 61 | DELETE FROM . WHERE ST_GeometryType(geom) NOT LIKE 'ST_PolyhedralSurface'; -- cleanup 62 | ``` 63 | 64 | Creating tileset using pg2b3dm: 65 | 66 | `pg2b3dm -h -U -d -p -c -t ` 67 | 68 | For more information, see the [pg2b3dm](https://github.com/Geodan/pg2b3dm) documentation. 69 | 70 | Creating tileset from point cloud: 71 | 72 | Pointcloud data is not yet supported by pg2b3dm and needs to be exporterd with an earlier tool called [py3dtiles](https://github.com/Oslandia/py3dtiles) 73 | 74 | `py3dtiles convert --srs_in --srs_out 3857 --out pointcloud.las` 75 | 76 | For more information, see the [py3dtiles](https://github.com/Oslandia/py3dtiles) documentation. 77 | 78 | -------------------------------------------------------------------------------- /adam.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 3DTile Layer for Mapbox GL JS 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 |
21 | 22 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /ahn/ahn_points.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Geodan/mapbox-3dtiles/29685d38e94088ba1d1ca721b8cf17a7d9231033/ahn/ahn_points.tar.gz -------------------------------------------------------------------------------- /apikeys.js.example: -------------------------------------------------------------------------------- 1 | const apiKeys = { 2 | mapboxAccessToken: "" 3 | } -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | import Mapbox3DTiles from "./Mapbox3DTiles.mjs"; 2 | 3 | mapboxgl.accessToken = apiKeys.mapboxAccessToken; 4 | const urlParams = new URLSearchParams(window.location.search); 5 | const debug = urlParams.get('debug') ? urlParams.get('debug') == "true" : false; 6 | const update = urlParams.get('update') ? parseInt(urlParams.get('update')) : 0; 7 | const light = urlParams.get('light') ? urlParams.get('light') == "true" : false; 8 | document.querySelector('#debug').checked = debug; 9 | document.querySelector('#light').checked = light; 10 | if (light) { 11 | document.querySelectorAll('.container').forEach(container=>container.classList.add('light')); 12 | } 13 | 14 | Mapbox3DTiles.DEBUG = debug; 15 | 16 | document.querySelector('#rotterdam').addEventListener('click',()=>window.location=`./?debug=${debug}&light=${light}&update=${1+update}#15.97/51.899662/4.478322/34.4/58`); 17 | document.querySelector('#velsen').addEventListener('click',()=>window.location=`./?debug=${debug}&light=${light}&update=${1+update}#17.65/52.455315/4.607382/-10.4/60`); 18 | document.querySelector('#debug').addEventListener('change', function(e){ 19 | window.location=`./?debug=${e.target.checked}&light=${light}${window.location.hash}` 20 | }); 21 | document.querySelector('#light').addEventListener('change', function(e){ 22 | window.location=`./?debug=${debug}&light=${e.target.checked}${window.location.hash}` 23 | }); 24 | 25 | const style = { 26 | version: 8, 27 | name: 'EmptyStyle', 28 | id: 'emptystyle', 29 | sources: {}, 30 | layers: [ 31 | { 32 | id: 'background', 33 | type: 'background', 34 | paint: { 'background-color': 'lightgrey' }, 35 | layout: { visibility: 'visible' } 36 | } 37 | ] 38 | }; 39 | // Load the mapbox map 40 | var map = new mapboxgl.Map({ 41 | container: 'map', 42 | style: `mapbox://styles/mapbox/${light?'light':'dark'}-v10?optimize=true`, 43 | center: [4.94442925, 52.31300579], 44 | zoom: 14.3, 45 | bearing: 0, 46 | pitch: 45, 47 | hash: true 48 | }); 49 | 50 | 51 | 52 | map.on('style.load', function() { 53 | 54 | const rotterdam = new Mapbox3DTiles.Layer( { 55 | id: 'rotterdam', 56 | url: './rotterdam/tileset.json', 57 | color: 0x0033aa, 58 | opacity: 1 59 | } ); 60 | map.addLayer(rotterdam); 61 | 62 | const ahn = new Mapbox3DTiles.Layer( { 63 | id: 'ahn', 64 | url: './ahn/tileset.json', 65 | color: 0x007722, 66 | opacity: 1.0, 67 | pointsize: 1.0 68 | } ); 69 | //map.addLayer(ahn, 'rotterdam'); 70 | 71 | const amsterdam = new Mapbox3DTiles.Layer( { 72 | id: 'amsterdam', 73 | url: 'https://beta.geodan.nl/data/buildingtiles_amsterdam_3857/tileset.json' 74 | }); 75 | map.addLayer(amsterdam); 76 | 77 | 78 | const jca = new Mapbox3DTiles.Layer( { 79 | id: 'jca', 80 | url: 'https://beta.geodan.nl/data/buildingtiles_jca_3857/tileset.json' 81 | }); 82 | map.addLayer(jca); 83 | 84 | 85 | /* 86 | const gltfLoader = new THREE.GLTFLoader(); 87 | gltfLoader.load('https://docs.mapbox.com/mapbox-gl-js/assets/34M_17/34M_17.gltf', (gltf) => { 88 | let matrix = new THREE.Matrix4(); 89 | matrix.makeRotationX(Math.PI/2); 90 | gltf.scene.applyMatrix4(matrix); 91 | let translation = Mapbox3DTiles.projectToWorld([4.605698, 52.456063,0]); 92 | matrix.makeTranslation(translation.x, translation.y, translation.z); 93 | matrix.scale({x:1,y:1,z:1}); 94 | gltf.scene.applyMatrix4(matrix); 95 | velsen.world.add(gltf.scene); 96 | //velsen.update(); 97 | map.triggerRepaint(); 98 | }); 99 | gltfLoader.load('./models/amsterdamcs.glb', (gltf) => { 100 | //let color = new THREE.Color(0xffffff); 101 | //gltf.scene.traverse(child => { 102 | // if (child instanceof THREE.Mesh) { 103 | // child.material.color = color; 104 | // } 105 | //}); 106 | let matrix = new THREE.Matrix4(); 107 | matrix.makeRotationX(Math.PI/2); 108 | gltf.scene.applyMatrix4(matrix); 109 | matrix.makeRotationZ(1.162 * Math.PI); 110 | gltf.scene.applyMatrix4(matrix); 111 | gltf.scene.translateY(0); 112 | let translation = Mapbox3DTiles.projectToWorld([4.60814,52.46326,0]); 113 | matrix.makeTranslation(translation.x, translation.y, translation.z); 114 | matrix.scale({x:1,y:1,z:1}); 115 | gltf.scene.applyMatrix4(matrix); 116 | velsen.world.add(gltf.scene); 117 | //velsen.update(); 118 | map.triggerRepaint(); 119 | }); 120 | gltfLoader.load('./windmill/SM_Base.glb', (gltf) => { 121 | let color = new THREE.Color(0xffffff); 122 | gltf.scene.traverse(child => { 123 | if (child instanceof THREE.Mesh) { 124 | child.material.color = color; 125 | } 126 | }); 127 | let matrix = new THREE.Matrix4(); 128 | //matrix.makeRotationX(Math.PI/2); 129 | //gltf.scene.applyMatrix4(matrix); 130 | let translation = Mapbox3DTiles.projectToWorld([4.60705,52.45464]); 131 | matrix.makeTranslation(translation.x, translation.y, translation.z); 132 | matrix.scale({x:.01,y:.01,z:.01}); 133 | gltf.scene.applyMatrix4(matrix); 134 | velsen.world.add(gltf.scene); 135 | //velsen.update(); 136 | map.triggerRepaint(); 137 | }); 138 | gltfLoader.load('./windmill/SM_Pillar.glb', (gltf) => { 139 | let color = new THREE.Color(0xffffff); 140 | gltf.scene.traverse(child => { 141 | if (child instanceof THREE.Mesh) { 142 | child.material.color = color; 143 | } 144 | }); 145 | let matrix = new THREE.Matrix4(); 146 | //matrix.makeRotationX(Math.PI/2); 147 | //gltf.scene.applyMatrix4(matrix); 148 | let translation = Mapbox3DTiles.projectToWorld([4.60705,52.45464]); 149 | matrix.makeTranslation(translation.x, translation.y, translation.z); 150 | matrix.scale({x:.01,y:.01,z:.01}); 151 | gltf.scene.applyMatrix4(matrix); 152 | velsen.world.add(gltf.scene); 153 | //velsen.update(); 154 | map.triggerRepaint(); 155 | }); 156 | gltfLoader.load('./windmill/SM_Nacelle.glb', (gltf) => { 157 | let color = new THREE.Color(0xffffff); 158 | gltf.scene.traverse(child => { 159 | if (child instanceof THREE.Mesh) { 160 | child.material.color = color; 161 | } 162 | }); 163 | let matrix = new THREE.Matrix4(); 164 | //matrix.makeRotationX(Math.PI/2); 165 | //gltf.scene.applyMatrix4(matrix); 166 | let translation = Mapbox3DTiles.projectToWorld([4.60705,52.45464]); 167 | matrix.makeTranslation(translation.x, translation.y, 100); 168 | matrix.scale({x:.01,y:.01,z:.01}); 169 | gltf.scene.applyMatrix4(matrix); 170 | velsen.world.add(gltf.scene); 171 | //velsen.update(); 172 | map.triggerRepaint(); 173 | }); 174 | gltfLoader.load('./windmill/SM_Rotor.glb', (gltf) => { 175 | let color = new THREE.Color(0xffffff); 176 | gltf.scene.traverse(child => { 177 | if (child instanceof THREE.Mesh) { 178 | child.material.color = color; 179 | } 180 | }); 181 | let location = new THREE.Group(); 182 | //gltf.scene.rotation.x = Math.PI; 183 | location.add(gltf.scene); 184 | let matrix = new THREE.Matrix4(); 185 | let translation = Mapbox3DTiles.projectToWorld([4.60705,52.45464]); 186 | matrix.makeTranslation(translation.x, translation.y-5, 100); 187 | matrix.scale({x:.01,y:.01,z:.01}); 188 | location.applyMatrix4(matrix); 189 | velsen.world.add(location); 190 | let rotation = 0; 191 | //let rotorMatrix = new THREE.Matrix4(); 192 | let start = new Date(); 193 | let rotate = () => { 194 | requestAnimationFrame(rotate); 195 | let elapsed = Date.now() - start; 196 | rotation = Math.PI * (elapsed / 6000) 197 | gltf.scene.rotation.y = rotation; 198 | map.triggerRepaint(); 199 | } 200 | if (mapboxgl.supported({failIfMajorPerformanceCaveat: true})) { 201 | rotate(); 202 | } 203 | }); 204 | */ 205 | 206 | }); 207 | map.on('mousemove', (event)=>{ 208 | let infoElement = document.querySelector('#info'); 209 | let features = map.queryRenderedFeatures(event.point, {outline: true, outlineColor: 0xff0000}); 210 | if (features.length) { 211 | infoElement.innerHTML = 212 | features.map(feature=> 213 | `Layer: ${feature.layer.id}
214 | ${Object.entries(feature.properties).map(entry=>`${entry[0]}:${entry[1]}`).join('
\n')} 215 | `).join('
\n') 216 | } else { 217 | infoElement.innerHTML = "Hover map objects for info"; 218 | } 219 | }) 220 | -------------------------------------------------------------------------------- /dist/Mapbox3DTiles.js: -------------------------------------------------------------------------------- 1 | var Mapbox3DTiles = (function () { 2 | 'use strict'; 3 | 4 | const MERCATOR_A = 6378137.0; 5 | const WORLD_SIZE = MERCATOR_A * Math.PI * 2; 6 | 7 | const ThreeboxConstants = { 8 | WORLD_SIZE: WORLD_SIZE, 9 | PROJECTION_WORLD_SIZE: WORLD_SIZE / (MERCATOR_A * Math.PI * 2), 10 | MERCATOR_A: MERCATOR_A, 11 | DEG2RAD: Math.PI / 180, 12 | RAD2DEG: 180 / Math.PI, 13 | EARTH_CIRCUMFERENCE: 40075000, // In meters 14 | }; 15 | 16 | /* 17 | mapbox-gl uses a camera fixed at the orgin (the middle of the canvas) The camera is only updated when rotated (bearing angle), 18 | pitched or when the map view is resized. 19 | When panning and zooming the map, the desired part of the world is translated and zoomed in front of the camera. The world is only updated when 20 | the map is panned or zoomed. 21 | 22 | The mapbox-gl internal coordinate system has origin (0,0) located at longitude -180 degrees and latitude 0 degrees. 23 | The scaling is 2^map.getZoom() * 512/EARTH_CIRCUMFERENCE_IN_METERS. At zoom=0 (scale=2^0=1), the whole world fits in 512 units. 24 | */ 25 | class CameraSync { 26 | constructor (map, camera, world) { 27 | this.map = map; 28 | this.camera = camera; 29 | this.active = true; 30 | this.updateCallback = ()=>{}; 31 | 32 | this.camera.matrixAutoUpdate = false; // We're in charge of the camera now! 33 | 34 | // Postion and configure the world group so we can scale it appropriately when the camera zooms 35 | this.world = world || new THREE.Group(); 36 | this.world.position.x = this.world.position.y = ThreeboxConstants.WORLD_SIZE/2; 37 | this.world.matrixAutoUpdate = false; 38 | 39 | //set up basic camera state 40 | this.state = { 41 | fov: 0.6435011087932844, // Math.atan(0.75); 42 | translateCenter: new THREE.Matrix4(), 43 | worldSizeRatio: 512/ThreeboxConstants.WORLD_SIZE 44 | }; 45 | 46 | this.state.translateCenter.makeTranslation(ThreeboxConstants.WORLD_SIZE/2, -ThreeboxConstants.WORLD_SIZE / 2, 0); 47 | 48 | // Listen for move events from the map and update the Three.js camera. Some attributes only change when viewport resizes, so update those accordingly 49 | this.map.on('move', ()=>this.updateCamera()); 50 | this.map.on('resize', ()=>this.setupCamera()); 51 | //this.map.on('moveend', ()=>this.updateCallback()) 52 | 53 | this.setupCamera(); 54 | } 55 | setupCamera() { 56 | var t = this.map.transform; 57 | const halfFov = this.state.fov / 2; 58 | var cameraToCenterDistance = 0.5 / Math.tan(halfFov) * t.height; 59 | 60 | this.state.cameraToCenterDistance = cameraToCenterDistance; 61 | this.state.cameraTranslateZ = new THREE.Matrix4().makeTranslation(0,0,cameraToCenterDistance); 62 | 63 | this.updateCamera(); 64 | } 65 | updateCamera(ev) { 66 | 67 | if(!this.camera) { 68 | console.log('nocamera'); 69 | return; 70 | } 71 | 72 | var t = this.map.transform; 73 | 74 | var halfFov = this.state.fov / 2; 75 | const groundAngle = Math.PI / 2 + t._pitch; 76 | this.state.topHalfSurfaceDistance = Math.sin(halfFov) * this.state.cameraToCenterDistance / Math.sin(Math.PI - groundAngle - halfFov); 77 | 78 | // Calculate z distance of the farthest fragment that should be rendered. 79 | const furthestDistance = Math.cos(Math.PI / 2 - t._pitch) * this.state.topHalfSurfaceDistance + this.state.cameraToCenterDistance; 80 | 81 | // Add a bit extra to avoid precision problems when a fragment's distance is exactly `furthestDistance` 82 | const farZ = furthestDistance * 1.01; 83 | 84 | this.camera.projectionMatrix = this.makePerspectiveMatrix(this.state.fov, t.width / t.height, 1, farZ); 85 | 86 | 87 | var cameraWorldMatrix = new THREE.Matrix4(); 88 | var rotatePitch = new THREE.Matrix4().makeRotationX(t._pitch); 89 | var rotateBearing = new THREE.Matrix4().makeRotationZ(t.angle); 90 | 91 | // Unlike the Mapbox GL JS camera, separate camera translation and rotation out into its world matrix 92 | // If this is applied directly to the projection matrix, it will work OK but break raycasting 93 | 94 | cameraWorldMatrix 95 | .premultiply(this.state.cameraTranslateZ) 96 | .premultiply(rotatePitch) 97 | .premultiply(rotateBearing); 98 | 99 | 100 | this.camera.matrixWorld.copy(cameraWorldMatrix); 101 | 102 | // Handle scaling and translation of objects in the map in the world's matrix transform, not the camera 103 | let zoomPow = t.scale * this.state.worldSizeRatio; 104 | let scale = new THREE.Matrix4(); 105 | scale.makeScale( zoomPow, zoomPow, zoomPow ); 106 | //console.log(`zoomPow: ${zoomPow}`); 107 | 108 | let translateMap = new THREE.Matrix4(); 109 | 110 | let x = -this.map.transform.x || -this.map.transform.point.x; 111 | let y = this.map.transform.y || this.map.transform.point.y; 112 | 113 | translateMap.makeTranslation(x, y, 0); 114 | 115 | this.world.matrix = new THREE.Matrix4; 116 | this.world.matrix 117 | //.premultiply(rotateMap) 118 | .premultiply(this.state.translateCenter) 119 | .premultiply(scale) 120 | .premultiply(translateMap); 121 | let matrixWorldInverse = new THREE.Matrix4(); 122 | matrixWorldInverse.getInverse(this.world.matrix); 123 | 124 | this.camera.projectionMatrixInverse.getInverse(this.camera.projectionMatrix); 125 | this.camera.matrixWorldInverse.getInverse(this.camera.matrixWorld); 126 | this.frustum = new THREE.Frustum(); 127 | this.frustum.setFromProjectionMatrix(new THREE.Matrix4().multiplyMatrices(this.camera.projectionMatrix, this.camera.matrixWorldInverse)); 128 | 129 | this.cameraPosition = new THREE.Vector3(0,0,0).unproject(this.camera).applyMatrix4(matrixWorldInverse); 130 | 131 | this.updateCallback(); 132 | } 133 | makePerspectiveMatrix(fovy, aspect, near, far) { 134 | 135 | let out = new THREE.Matrix4(); 136 | let f = 1.0 / Math.tan(fovy / 2), 137 | nf = 1 / (near - far); 138 | 139 | let newMatrix = [ 140 | f / aspect, 0, 0, 0, 141 | 0, f, 0, 0, 142 | 0, 0, (far + near) * nf, -1, 143 | 0, 0, (2 * far * near) * nf, 0 144 | ]; 145 | 146 | out.elements = newMatrix; 147 | return out; 148 | } 149 | } 150 | 151 | class TileSet { 152 | constructor(updateCallback){ 153 | if (!updateCallback) { 154 | updateCallback = ()=>{}; 155 | } 156 | this.updateCallback = updateCallback; 157 | this.url = null; 158 | this.version = null; 159 | this.gltfUpAxis = 'Z'; 160 | this.geometricError = null; 161 | this.root = null; 162 | } 163 | // TileSet.load 164 | async load(url, styleParams) { 165 | this.url = url; 166 | let resourcePath = THREE.LoaderUtils.extractUrlBase(url); 167 | 168 | let response = await fetch(this.url); 169 | if (!response.ok) { 170 | throw new Error(`HTTP ${response.status} - ${response.statusText}`); 171 | } 172 | let json = await response.json(); 173 | this.version = json.asset.version; 174 | this.geometricError = json.geometricError; 175 | this.refine = json.root.refine ? json.root.refine.toUpperCase() : 'ADD'; 176 | this.root = new ThreeDeeTile(json.root, resourcePath, styleParams, this.updateCallback, this.refine); 177 | return; 178 | } 179 | } 180 | 181 | class ThreeDeeTile { 182 | constructor(json, resourcePath, styleParams, updateCallback, parentRefine, parentTransform) { 183 | this.loaded = false; 184 | this.styleParams = styleParams; 185 | this.updateCallback = updateCallback; 186 | this.resourcePath = resourcePath; 187 | this.totalContent = new THREE.Group(); // Three JS Object3D Group for this tile and all its children 188 | this.tileContent = new THREE.Group(); // Three JS Object3D Group for this tile's content 189 | this.childContent = new THREE.Group(); // Three JS Object3D Group for this tile's children 190 | this.totalContent.add(this.tileContent); 191 | this.totalContent.add(this.childContent); 192 | this.boundingVolume = json.boundingVolume; 193 | if (this.boundingVolume && this.boundingVolume.box) { 194 | let b = this.boundingVolume.box; 195 | let extent = [b[0] - b[3], b[1] - b[7], b[0] + b[3], b[1] + b[7]]; 196 | let sw = new THREE.Vector3(extent[0], extent[1], b[2] - b[11]); 197 | let ne = new THREE.Vector3(extent[2], extent[3], b[2] + b[11]); 198 | this.box = new THREE.Box3(sw, ne); 199 | if (Mapbox3DTiles.DEBUG) { 200 | let geom = new THREE.BoxGeometry(b[3] * 2, b[7] * 2, b[11] * 2); 201 | let edges = new THREE.EdgesGeometry( geom ); 202 | this.debugColor = new THREE.Color( 0xffffff ); 203 | this.debugColor.setHex( Math.random() * 0xffffff ); 204 | let line = new THREE.LineSegments( edges, new THREE.LineBasicMaterial( {color:this.debugColor }) ); 205 | let trans = new THREE.Matrix4().makeTranslation(b[0], b[1], b[2]); 206 | line.applyMatrix4(trans); 207 | this.debugLine = line; 208 | } 209 | } else { 210 | this.extent = null; 211 | this.sw = null; 212 | this.ne = null; 213 | this.box = null; 214 | this.center = null; 215 | } 216 | this.refine = json.refine ? json.refine.toUpperCase() : parentRefine; 217 | this.geometricError = json.geometricError; 218 | this.worldTransform = parentTransform ? parentTransform.clone() : new THREE.Matrix4(); 219 | this.transform = json.transform; 220 | if (this.transform) 221 | { 222 | let tileMatrix = new THREE.Matrix4().fromArray(this.transform); 223 | this.totalContent.applyMatrix4(tileMatrix); 224 | this.worldTransform.multiply(tileMatrix); 225 | } 226 | this.content = json.content; 227 | this.children = []; 228 | if (json.children) { 229 | for (let i=0; ithis.updateCallback()); 269 | await subTileset.load(url, this.styleParams); 270 | if (subTileset.root) { 271 | this.box.applyMatrix4(this.worldTransform); 272 | let inverseMatrix = new THREE.Matrix4().getInverse(this.worldTransform); 273 | this.totalContent.applyMatrix4(inverseMatrix); 274 | this.totalContent.updateMatrixWorld(); 275 | this.worldTransform = new THREE.Matrix4(); 276 | 277 | this.children.push(subTileset.root); 278 | this.childContent.add(subTileset.root.totalContent); 279 | subTileset.root.totalContent.updateMatrixWorld(); 280 | subTileset.root.checkLoad(this.frustum, this.cameraPosition); 281 | } 282 | } catch (error) { 283 | // load failed (wrong url? connection issues?) 284 | // log error, do not break program flow 285 | console.error(error); 286 | } 287 | break; 288 | case 'b3dm': 289 | try { 290 | let loader = new THREE.GLTFLoader(); 291 | let b3dm = new B3DM(url); 292 | let rotateX = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(1, 0, 0), Math.PI / 2); 293 | this.tileContent.applyMatrix4(rotateX); // convert from GLTF Y-up to Z-up 294 | let b3dmData = await b3dm.load(); 295 | loader.parse(b3dmData.glbData, this.resourcePath, (gltf) => { 296 | 297 | gltf.scene.traverse(child => { 298 | if (child instanceof THREE.Mesh) { 299 | // some gltf has wrong bounding data, recompute here 300 | child.geometry.computeBoundingBox(); 301 | child.geometry.computeBoundingSphere(); 302 | child.material.depthWrite = true; // necessary for Velsen dataset? 303 | //Add the batchtable to the userData since gltLoader doesn't deal with it 304 | child.userData = b3dmData.batchTableJson; 305 | } 306 | }); 307 | if (this.styleParams.color != null || this.styleParams.opacity != null) { 308 | let color = new THREE.Color(this.styleParams.color); 309 | gltf.scene.traverse(child => { 310 | if (child instanceof THREE.Mesh) { 311 | if (this.styleParams.color != null) 312 | child.material.color = color; 313 | if (this.styleParams.opacity != null) { 314 | child.material.opacity = this.styleParams.opacity; 315 | child.material.transparent = this.styleParams.opacity < 1.0 ? true : false; 316 | } 317 | } 318 | }); 319 | } 320 | if (this.debugColor) { 321 | gltf.scene.traverse(child => { 322 | if (child instanceof THREE.Mesh) { 323 | child.material.color = this.debugColor; 324 | } 325 | }); 326 | } 327 | this.tileContent.add(gltf.scene); 328 | }, (error) => { 329 | throw new Error('error parsing gltf: ' + error); 330 | } 331 | ); 332 | } catch (error) { 333 | console.error(error); 334 | } 335 | break; 336 | case 'pnts': 337 | try { 338 | let pnts = new PNTS(url); 339 | let pointData = await pnts.load(); 340 | let geometry = new THREE.BufferGeometry(); 341 | geometry.setAttribute('position', new THREE.Float32BufferAttribute(pointData.points, 3)); 342 | let material = new THREE.PointsMaterial(); 343 | material.size = this.styleParams.pointsize != null ? this.styleParams.pointsize : 1.0; 344 | if (this.styleParams.color) { 345 | material.vertexColors = THREE.NoColors; 346 | material.color = new THREE.Color(this.styleParams.color); 347 | material.opacity = this.styleParams.opacity != null ? this.styleParams.opacity : 1.0; 348 | } else if (pointData.rgba) { 349 | geometry.setAttribute('color', new THREE.Float32BufferAttribute(pointData.rgba, 4)); 350 | material.vertexColors = THREE.VertexColors; 351 | } else if (pointData.rgb) { 352 | geometry.setAttribute('color', new THREE.Float32BufferAttribute(pointData.rgb, 3)); 353 | material.vertexColors = THREE.VertexColors; 354 | } 355 | this.tileContent.add(new THREE.Points( geometry, material )); 356 | if (pointData.rtc_center) { 357 | let c = pointData.rtc_center; 358 | this.tileContent.applyMatrix4(new THREE.Matrix4().makeTranslation(c[0], c[1], c[2])); 359 | } 360 | this.tileContent.add(new THREE.Points( geometry, material )); 361 | } catch (error) { 362 | console.error(error); 363 | } 364 | break; 365 | case 'i3dm': 366 | throw new Error('i3dm tiles not yet implemented'); 367 | case 'cmpt': 368 | throw new Error('cmpt tiles not yet implemented'); 369 | default: 370 | throw new Error('invalid tile type: ' + type); 371 | } 372 | } 373 | this.updateCallback(); 374 | } 375 | unload(includeChildren) { 376 | this.unloadedTileContent = true; 377 | this.totalContent.remove(this.tileContent); 378 | 379 | //this.tileContent.visible = false; 380 | if (includeChildren) { 381 | this.unloadedChildContent = true; 382 | this.totalContent.remove(this.childContent); 383 | //this.childContent.visible = false; 384 | } else { 385 | if (this.unloadedChildContent) { 386 | this.unloadedChildContent = false; 387 | this.totalContent.add(this.childContent); 388 | } 389 | } 390 | if (this.debugLine) { 391 | this.totalContent.remove(this.debugLine); 392 | this.unloadedDebugContent = true; 393 | } 394 | this.updateCallback(); 395 | // TODO: should we also free up memory? 396 | } 397 | checkLoad(frustum, cameraPosition) { 398 | 399 | this.frustum = frustum; 400 | this.cameraPosition = cameraPosition; 401 | /*this.load(); 402 | for (let i=0; i 0.0 && dist > this.geometricError * 50.0) { 427 | this.unload(true); 428 | return; 429 | } 430 | //console.log(`camPos: ${cameraPosition.z}, dist: ${dist}, geometricError: ${this.geometricError}`); 431 | 432 | // should we load this tile? 433 | if (this.refine == 'REPLACE' && dist < this.geometricError * 20.0) { 434 | this.unload(false); 435 | } else { 436 | this.load(); 437 | } 438 | 439 | 440 | // should we load its children? 441 | for (let i=0; i 0.25) { 463 | this.load(); 464 | this.children.forEach(child => { 465 | child.checkLoad(camera); 466 | }); 467 | }*/ 468 | 469 | } 470 | } 471 | 472 | class TileLoader { 473 | // This class contains the common code to load tile content, such as b3dm and pnts files. 474 | // It is not to be used directly. Instead, subclasses are used to implement specific 475 | // content loaders for different tile types. 476 | constructor(url) { 477 | this.url = url; 478 | this.type = url.slice(-4); 479 | this.version = null; 480 | this.byteLength = null; 481 | this.featureTableJSON = null; 482 | this.featureTableBinary = null; 483 | this.batchTableJson = null; 484 | this.batchTableBinary = null; 485 | this.binaryData = null; 486 | } 487 | // TileLoader.load 488 | async load() { 489 | let response = await fetch(this.url); 490 | if (!response.ok) { 491 | throw new Error(`HTTP ${response.status} - ${response.statusText}`); 492 | } 493 | let buffer = await response.arrayBuffer(); 494 | let res = this.parseResponse(buffer); 495 | return res; 496 | } 497 | parseResponse(buffer) { 498 | let header = new Uint32Array(buffer.slice(0, 28)); 499 | let decoder = new TextDecoder(); 500 | let magic = decoder.decode(new Uint8Array(buffer.slice(0, 4))); 501 | if (magic != this.type) { 502 | throw new Error(`Invalid magic string, expected '${this.type}', got '${this.magic}'`); 503 | } 504 | this.version = header[1]; 505 | this.byteLength = header[2]; 506 | let featureTableJSONByteLength = header[3]; 507 | let featureTableBinaryByteLength = header[4]; 508 | let batchTableJsonByteLength = header[5]; 509 | let batchTableBinaryByteLength = header[6]; 510 | 511 | /* 512 | console.log('magic: ' + magic); 513 | console.log('version: ' + this.version); 514 | console.log('featureTableJSONByteLength: ' + featureTableJSONByteLength); 515 | console.log('featureTableBinaryByteLength: ' + featureTableBinaryByteLength); 516 | console.log('batchTableJsonByteLength: ' + batchTableJsonByteLength); 517 | console.log('batchTableBinaryByteLength: ' + batchTableBinaryByteLength); 518 | */ 519 | 520 | let pos = 28; // header length 521 | if (featureTableJSONByteLength > 0) { 522 | this.featureTableJSON = JSON.parse(decoder.decode(new Uint8Array(buffer.slice(pos, pos+featureTableJSONByteLength)))); 523 | pos += featureTableJSONByteLength; 524 | } else { 525 | this.featureTableJSON = {}; 526 | } 527 | this.featureTableBinary = buffer.slice(pos, pos+featureTableBinaryByteLength); 528 | pos += featureTableBinaryByteLength; 529 | if (batchTableJsonByteLength > 0) { 530 | this.batchTableJson = JSON.parse(decoder.decode(new Uint8Array(buffer.slice(pos, pos+batchTableJsonByteLength)))); 531 | pos += batchTableJsonByteLength; 532 | } else { 533 | this.batchTableJson = {}; 534 | } 535 | this.batchTableBinary = buffer.slice(pos, pos+batchTableBinaryByteLength); 536 | pos += batchTableBinaryByteLength; 537 | this.binaryData = buffer.slice(pos); 538 | return this; 539 | } 540 | } 541 | 542 | class B3DM extends TileLoader { 543 | constructor(url) { 544 | super(url); 545 | this.glbData = null; 546 | } 547 | parseResponse(buffer) { 548 | super.parseResponse(buffer); 549 | this.glbData = this.binaryData; 550 | return this; 551 | } 552 | } 553 | 554 | class PNTS extends TileLoader{ 555 | constructor(url) { 556 | super(url); 557 | this.points = new Float32Array(); 558 | this.rgba = null; 559 | this.rgb = null; 560 | } 561 | parseResponse(buffer) { 562 | super.parseResponse(buffer); 563 | if (this.featureTableJSON.POINTS_LENGTH && this.featureTableJSON.POSITION) { 564 | let len = this.featureTableJSON.POINTS_LENGTH; 565 | let pos = this.featureTableJSON.POSITION.byteOffset; 566 | this.points = new Float32Array(this.featureTableBinary.slice(pos, pos + len * Float32Array.BYTES_PER_ELEMENT * 3)); 567 | this.rtc_center = this.featureTableJSON.RTC_CENTER; 568 | if (this.featureTableJSON.RGBA) { 569 | pos = this.featureTableJSON.RGBA.byteOffset; 570 | let colorInts = new Uint8Array(this.featureTableBinary.slice(pos, pos + len * Uint8Array.BYTES_PER_ELEMENT * 4)); 571 | let rgba = new Float32Array(colorInts.length); 572 | for (let i=0; i{ 654 | this.scene.add(light); 655 | }); 656 | this.world = new THREE.Group(); 657 | this.world.name = 'flatMercatorWorld'; 658 | this.scene.add(this.world); 659 | 660 | this.renderer = new THREE.WebGLRenderer({ 661 | alpha: true, 662 | antialias: true, 663 | canvas: map.getCanvas(), 664 | context: gl, 665 | }); 666 | 667 | this.renderer.shadowMap.enabled = true; 668 | this.renderer.autoClear = false; 669 | 670 | this.cameraSync = new CameraSync(this.map, this.camera, this.world); 671 | this.cameraSync.updateCallback = ()=>this.loadVisibleTiles(); 672 | 673 | //raycaster for mouse events 674 | this.raycaster = new THREE.Raycaster(); 675 | if (this.url) { 676 | this.tileset = new TileSet(()=>this.map.triggerRepaint()); 677 | this.tileset.load(this.url, this.styleParams).then(()=>{ 678 | if (this.tileset.root) { 679 | this.world.add(this.tileset.root.totalContent); 680 | this.world.updateMatrixWorld(); 681 | this.loadStatus = 1; 682 | this.loadVisibleTiles(); 683 | } 684 | }).catch(error=>{ 685 | console.error(`${error} (${this.url})`); 686 | }); 687 | } 688 | } 689 | onRemove(map, gl) { 690 | // todo: (much) more cleanup? 691 | this.map.queryRenderedFeatures = this.mapQueryRenderedFeatures; 692 | this.cameraSync = null; 693 | } 694 | queryRenderedFeatures(geometry, options){ 695 | let result = this.mapQueryRenderedFeatures(geometry, options); 696 | if (!this.map || !this.map.transform) { 697 | return result; 698 | } 699 | if (!(options && options.layers && !options.layers.includes(this.id))) { 700 | if (geometry && geometry.x && geometry.y) { 701 | var mouse = new THREE.Vector2(); 702 | 703 | // // scale mouse pixel position to a percentage of the screen's width and height 704 | mouse.x = ( geometry.x / this.map.transform.width ) * 2 - 1; 705 | mouse.y = 1 - ( geometry.y / this.map.transform.height ) * 2; 706 | 707 | this.raycaster.setFromCamera(mouse, this.camera); 708 | 709 | // calculate objects intersecting the picking ray 710 | let intersects = this.raycaster.intersectObjects(this.world.children, true); 711 | if (intersects.length) { 712 | let feature = { 713 | "type": "Feature", 714 | "properties" : {}, 715 | "geometry" :{}, 716 | "layer": {"id": this.id, "type": "custom 3d"}, 717 | "source": this.url, 718 | "source-layer": null, 719 | "state": {} 720 | }; 721 | let propertyIndex; 722 | let intersect = intersects[0]; 723 | if (intersect.object && intersect.object.geometry && 724 | intersect.object.geometry.attributes && 725 | intersect.object.geometry.attributes._batchid) { 726 | let geometry = intersect.object.geometry; 727 | let vertexIdx = intersect.faceIndex; 728 | if (geometry.index) { 729 | // indexed BufferGeometry 730 | vertexIdx = geometry.index.array[intersect.faceIndex*3]; 731 | propertyIndex = geometry.attributes._batchid.data.array[vertexIdx*7+6]; 732 | } else { 733 | // un-indexed BufferGeometry 734 | propertyIndex = geometry.attributes._batchid.array[vertexIdx*3]; 735 | } 736 | let keys = Object.keys(intersect.object.userData); 737 | if (keys.length) { 738 | for (let propertyName of keys) { 739 | feature.properties[propertyName] = intersect.object.userData[propertyName][propertyIndex]; 740 | } 741 | } else { 742 | feature.properties.batchId = propertyIndex; 743 | } 744 | } else { 745 | if (intersect.index != null) { 746 | feature.properties.index = intersect.index; 747 | } else { 748 | feature.properties.name = this.id; 749 | } 750 | } 751 | if (options.outline != false && (intersect.object !== this.outlinedObject || 752 | (propertyIndex != null && propertyIndex !== this.outlinePropertyIndex) 753 | || (propertyIndex == null && intersect.index !== this.outlineIndex))) { 754 | 755 | //WIP 756 | this.outlinePass.selectedObjects = [intersect.object]; 757 | 758 | // update outline 759 | if (this.outlineMesh) { 760 | let parent = this.outlineMesh.parent; 761 | parent.remove(this.outlineMesh); 762 | this.outlineMesh = null; 763 | } 764 | this.outlinePropertyIndex = propertyIndex; 765 | this.outlineIndex = intersect.index; 766 | if (intersect.object instanceof THREE.Mesh) { 767 | this.outlinedObject = intersect.object; 768 | let outlineMaterial = new THREE.MeshBasicMaterial({color: options.outlineColor? options.outlineColor : 0xff0000, wireframe: true}); 769 | let outlineMesh; 770 | if (intersect.object && 771 | intersect.object.geometry && 772 | intersect.object.geometry.attributes && 773 | intersect.object.geometry.attributes._batchid) { 774 | // create new geometry from faces that have same _batchid 775 | let geometry = intersect.object.geometry; 776 | if (geometry.index) { 777 | let ip1 = geometry.index.array[intersect.faceIndex*3]; 778 | let idx = geometry.attributes._batchid.data.array[ip1*7+6]; 779 | let blockFaces = []; 780 | for (let faceIndex = 0; faceIndex < geometry.index.array.length; faceIndex += 3) { 781 | let p1 = geometry.index.array[faceIndex]; 782 | if (geometry.attributes._batchid.data.array[p1*7+6] === idx) { 783 | let p2 = geometry.index.array[faceIndex+1]; 784 | if (geometry.attributes._batchid.data.array[p2*7+6] === idx) { 785 | let p3 = geometry.index.array[faceIndex+2]; 786 | if (geometry.attributes._batchid.data.array[p3*7+6] === idx) { 787 | blockFaces.push(faceIndex); 788 | } 789 | } 790 | } 791 | } 792 | let highLightGeometry = new THREE.Geometry(); 793 | for (let vertexCount = 0, face = 0; face < blockFaces.length; face++) { 794 | let faceIndex = blockFaces[face]; 795 | let p1 = geometry.index.array[faceIndex]; 796 | let p2 = geometry.index.array[faceIndex+1]; 797 | let p3 = geometry.index.array[faceIndex+2]; 798 | let positions = geometry.attributes.position.data.array; 799 | highLightGeometry.vertices.push( 800 | new THREE.Vector3(positions[p1*7], positions[p1*7+1], positions[p1*7+2]), 801 | new THREE.Vector3(positions[p2*7], positions[p2*7+1], positions[p2*7+2]), 802 | new THREE.Vector3(positions[p3*7], positions[p3*7+1], positions[p3*7+2]), 803 | ); 804 | highLightGeometry.faces.push(new THREE.Face3(vertexCount, vertexCount+1, vertexCount+2)); 805 | vertexCount += 3; 806 | } 807 | highLightGeometry.computeBoundingSphere(); 808 | outlineMesh = new THREE.Mesh(highLightGeometry, outlineMaterial); 809 | } else { 810 | let ip1 = intersect.faceIndex*3; 811 | let idx = geometry.attributes._batchid.array[ip1]; 812 | let blockFaces = []; 813 | for (let faceIndex = 0; faceIndex < geometry.attributes._batchid.array.length; faceIndex += 3) { 814 | let p1 = faceIndex; 815 | if (geometry.attributes._batchid.array[p1] === idx) { 816 | let p2 = faceIndex + 1; 817 | if (geometry.attributes._batchid.array[p2] === idx) { 818 | let p3 = faceIndex + 2; 819 | if (geometry.attributes._batchid.array[p3] === idx) { 820 | blockFaces.push(faceIndex); 821 | } 822 | } 823 | } 824 | } 825 | let highLightGeometry = new THREE.Geometry(); 826 | for (let vertexCount = 0, face = 0; face < blockFaces.length; face++) { 827 | let faceIndex = blockFaces[face] * 3; 828 | let positions = geometry.attributes.position.array; 829 | highLightGeometry.vertices.push( 830 | new THREE.Vector3(positions[faceIndex], positions[faceIndex+1], positions[faceIndex+2]), 831 | new THREE.Vector3(positions[faceIndex+3], positions[faceIndex+4], positions[faceIndex+5]), 832 | new THREE.Vector3(positions[faceIndex+6], positions[faceIndex+7], positions[faceIndex+8]), 833 | ); 834 | highLightGeometry.faces.push(new THREE.Face3(vertexCount, vertexCount+1, vertexCount+2)); 835 | vertexCount += 3; 836 | } 837 | highLightGeometry.computeBoundingSphere(); 838 | outlineMesh = new THREE.Mesh(highLightGeometry, outlineMaterial); 839 | } 840 | } else { 841 | outlineMesh = new THREE.Mesh(this.outlinedObject.geometry, outlineMaterial); 842 | } 843 | outlineMesh.position.x = this.outlinedObject.position.x+0.1; 844 | outlineMesh.position.y = this.outlinedObject.position.y+0.1; 845 | outlineMesh.position.z = this.outlinedObject.position.z+0.1; 846 | outlineMesh.quaternion.copy(this.outlinedObject.quaternion); 847 | outlineMesh.scale.copy(this.outlinedObject.scale); 848 | outlineMesh.matrix.copy(this.outlinedObject.matrix); 849 | outlineMesh.raycast = () =>{}; 850 | outlineMesh.name = "outline"; 851 | outlineMesh.wireframe = true; 852 | this.outlinedObject.parent.add(outlineMesh); 853 | this.outlineMesh = outlineMesh; 854 | } 855 | } 856 | result.unshift(feature); 857 | this.map.triggerRepaint(); 858 | } else { 859 | this.outlinedObject = null; 860 | if (this.outlineMesh) { 861 | let parent = this.outlineMesh.parent; 862 | parent.remove(this.outlineMesh); 863 | this.outlineMesh = null; 864 | this.map.triggerRepaint(); 865 | } 866 | } 867 | } 868 | } 869 | return result; 870 | } 871 | _update() { 872 | this.renderer.state.reset(); 873 | this.renderer.render (this.scene, this.camera); 874 | 875 | /*if (this.loadStatus == 1) { // first render after root tile is loaded 876 | this.loadStatus = 2; 877 | let frustum = new THREE.Frustum(); 878 | frustum.setFromProjectionMatrix(new THREE.Matrix4().multiplyMatrices(this.camera.projectionMatrix, this.camera.matrixWorldInverse)); 879 | if (this.tileset.root) { 880 | this.tileset.root.checkLoad(frustum, this.getCameraPosition()); 881 | } 882 | }*/ 883 | } 884 | update() { 885 | requestAnimationFrame(()=>this._update()); 886 | } 887 | render(gl, viewProjectionMatrix) { 888 | this._update(); 889 | } 890 | } 891 | 892 | class Mapbox3DTiles { 893 | 894 | static projectedUnitsPerMeter(latitude) { 895 | let c = ThreeboxConstants; 896 | return Math.abs( c.WORLD_SIZE / Math.cos( c.DEG2RAD * latitude ) / c.EARTH_CIRCUMFERENCE ); 897 | } 898 | static projectToWorld(coords) { 899 | // Spherical mercator forward projection, re-scaling to WORLD_SIZE 900 | let c = ThreeboxConstants; 901 | var projected = [ 902 | c.MERCATOR_A * c.DEG2RAD * coords[0] * c.PROJECTION_WORLD_SIZE, 903 | c.MERCATOR_A * Math.log(Math.tan((Math.PI*0.25) + (0.5 * c.DEG2RAD * coords[1]) )) * c.PROJECTION_WORLD_SIZE 904 | ]; 905 | 906 | //z dimension, defaulting to 0 if not provided 907 | if (!coords[2]) { 908 | projected.push(0); 909 | } else { 910 | var pixelsPerMeter = projectedUnitsPerMeter(coords[1]); 911 | projected.push( coords[2] * pixelsPerMeter ); 912 | } 913 | 914 | var result = new THREE.Vector3(projected[0], projected[1], projected[2]); 915 | 916 | return result; 917 | } 918 | } 919 | 920 | Mapbox3DTiles.Layer = Layer; 921 | 922 | return Mapbox3DTiles; 923 | 924 | }()); 925 | //# sourceMappingURL=Mapbox3DTiles.js.map 926 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 3DTile Layer for Mapbox GL JS 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 39 | 40 | 41 |
42 |
43 | 44 |
45 | 46 | 47 |
48 |
49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /models/Tree.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "accessors": [ 3 | { 4 | "name": "Hedra001_1_0_positions", 5 | "componentType": 5126, 6 | "count": 180, 7 | "min": [ 8 | -36.43159866333008, 9 | 55.592098236083984, 10 | -28.351699829101562 11 | ], 12 | "max": [ 13 | 29.362499237060547, 14 | 121.38619995117188, 15 | 39.23509979248047 16 | ], 17 | "type": "VEC3", 18 | "bufferView": 0, 19 | "byteOffset": 0 20 | }, 21 | { 22 | "name": "Hedra001_1_0_normals", 23 | "componentType": 5126, 24 | "count": 180, 25 | "min": [ 26 | -0.9574223756790161, 27 | -0.9112201929092407, 28 | -0.904851496219635 29 | ], 30 | "max": [ 31 | 0.9643883109092712, 32 | 0.9492332339286804, 33 | 0.9485108256340027 34 | ], 35 | "type": "VEC3", 36 | "bufferView": 1, 37 | "byteOffset": 0 38 | }, 39 | { 40 | "name": "Hedra001_1_0_texcoords", 41 | "componentType": 5126, 42 | "count": 180, 43 | "min": [ 44 | -0.4535999894142151, 45 | 0.5130000114440918 46 | ], 47 | "max": [ 48 | 0.4309000074863434, 49 | 1.4486000537872314 50 | ], 51 | "type": "VEC2", 52 | "bufferView": 2, 53 | "byteOffset": 0 54 | }, 55 | { 56 | "name": "Hedra001_1_0_indices", 57 | "componentType": 5123, 58 | "count": 348, 59 | "min": [ 60 | 0 61 | ], 62 | "max": [ 63 | 179 64 | ], 65 | "type": "SCALAR", 66 | "bufferView": 3, 67 | "byteOffset": 0 68 | }, 69 | { 70 | "name": "Cylinder002_1_0_positions", 71 | "componentType": 5126, 72 | "count": 77, 73 | "min": [ 74 | -10.637700080871582, 75 | 0, 76 | -3.6338000297546387 77 | ], 78 | "max": [ 79 | 12.249199867248535, 80 | 62.5635986328125, 81 | 8.694899559020996 82 | ], 83 | "type": "VEC3", 84 | "bufferView": 0, 85 | "byteOffset": 2160 86 | }, 87 | { 88 | "name": "Cylinder002_1_0_normals", 89 | "componentType": 5126, 90 | "count": 77, 91 | "min": [ 92 | -0.9697530269622803, 93 | -1, 94 | -0.9990031123161316 95 | ], 96 | "max": [ 97 | 0.97041255235672, 98 | 0.6907729506492615, 99 | 0.9975789189338684 100 | ], 101 | "type": "VEC3", 102 | "bufferView": 1, 103 | "byteOffset": 2160 104 | }, 105 | { 106 | "name": "Cylinder002_1_0_texcoords", 107 | "componentType": 5126, 108 | "count": 77, 109 | "min": [ 110 | 0.02449999935925007, 111 | 0 112 | ], 113 | "max": [ 114 | 1.149999976158142, 115 | 1 116 | ], 117 | "type": "VEC2", 118 | "bufferView": 2, 119 | "byteOffset": 1440 120 | }, 121 | { 122 | "name": "Cylinder002_1_0_indices", 123 | "componentType": 5123, 124 | "count": 129, 125 | "min": [ 126 | 0 127 | ], 128 | "max": [ 129 | 76 130 | ], 131 | "type": "SCALAR", 132 | "bufferView": 3, 133 | "byteOffset": 696 134 | }, 135 | { 136 | "name": "Cylinder002_1_1_positions", 137 | "componentType": 5126, 138 | "count": 25, 139 | "min": [ 140 | -3.749799966812134, 141 | 58.39059829711914, 142 | -0.4828000068664551 143 | ], 144 | "max": [ 145 | 8.645000457763672, 146 | 71.70939636230469, 147 | 5.543900012969971 148 | ], 149 | "type": "VEC3", 150 | "bufferView": 0, 151 | "byteOffset": 3084 152 | }, 153 | { 154 | "name": "Cylinder002_1_1_normals", 155 | "componentType": 5126, 156 | "count": 25, 157 | "min": [ 158 | -0.8071070313453674, 159 | -0.5904051661491394, 160 | -0.9714550971984863 161 | ], 162 | "max": [ 163 | 0.35139596462249756, 164 | 0.8071070313453674, 165 | 0.9714550971984863 166 | ], 167 | "type": "VEC3", 168 | "bufferView": 1, 169 | "byteOffset": 3084 170 | }, 171 | { 172 | "name": "Cylinder002_1_1_texcoords", 173 | "componentType": 5126, 174 | "count": 25, 175 | "min": [ 176 | 0.02449999935925007, 177 | 0 178 | ], 179 | "max": [ 180 | 0.9754999876022339, 181 | 0.9045000076293945 182 | ], 183 | "type": "VEC2", 184 | "bufferView": 2, 185 | "byteOffset": 2056 186 | }, 187 | { 188 | "name": "Cylinder002_1_1_indices", 189 | "componentType": 5123, 190 | "count": 39, 191 | "min": [ 192 | 0 193 | ], 194 | "max": [ 195 | 24 196 | ], 197 | "type": "SCALAR", 198 | "bufferView": 3, 199 | "byteOffset": 956 200 | } 201 | ], 202 | "asset": { 203 | "generator": "obj2gltf", 204 | "version": "2.0" 205 | }, 206 | "buffers": [ 207 | { 208 | "name": "Tree low", 209 | "byteLength": 10060, 210 | "uri": "data:application/octet-stream;base64,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AAAAPwAAAAAAAAA/AAAAAF66eT9g5bA+AQAFAAAABQADAAQAAwABAAIAAQADAAUABwALAAYACwAJAAoACQAHAAgABwAJAAsADQARAAwAEQAPABAADwANAA4ADQAPABEAEwAXABIAFwAVABYAFQATABQAEwAVABcAGQAdABgAHQAbABwAGwAZABoAGQAbAB0AHwAjAB4AIwAhACIAIQAfACAAHwAhACMAKAApACQAJAAlACYAJgAnACgAKAAkACYALgAvACoAKgArACwALAAtAC4ALgAqACwANAA1ADAAMAAxADIAMgAzADQANAAwADIAOgA7ADYANgA3ADgAOAA5ADoAOgA2ADgAQABBADwAPAA9AD4APgA/AEAAQAA8AD4ARgBHAEIAQgBDAEQARABFAEYARgBCAEQATABNAEgASABJAEoASgBLAEwATABIAEoAUgBTAE4ATgBPAFAAUABRAFIAUgBOAFAAWABZAFQAVABVAFYAVgBXAFgAWABUAFYAXgBfAFoAWgBbAFwAXABdAF4AXgBaAFwAYQBlAGAAZQBjAGQAYwBhAGIAYQBjAGUAZwBrAGYAawBpAGoAaQBnAGgAZwBpAGsAbQBxAGwAcQBvAHAAbwBtAG4AbQBvAHEAcwB3AHIAdwB1AHYAdQBzAHQAcwB1AHcAeQB8AHgAfAB6AHsAegB8AHkAfgCBAH0AgQB/AIAAfwCBAH4AgwCGAIIAhgCEAIUAhACGAIMAigCLAIcAhwCIAIkAiQCKAIcAjwCQAIwAjACNAI4AjgCPAIwAlACVAJEAkQCSAJMAkwCUAJEAmQCaAJYAlgCXAJgAmACZAJYAngCfAJsAmwCcAJ0AnQCeAJsAoQCkAKAApACiAKMAogCkAKEApgCpAKUAqQCnAKgApwCpAKYAqwCuAKoArgCsAK0ArACuAKsAsACzAK8AswCxALIAsQCzALAAAgADAAAAAAABAAIABAACAAEAAgAEAAUABwAJAAYACQAHAAgACwAIAAcABwAKAAsAAwALAAoACgAAAAMADwAQAAwADAANAA4ADgAPAAwAEwAUABEAEQASABMAFgAYABUAGAAWABcAGgAcABkAHAAaABsAHwAgAB0AHQAeAB8AIwAkACEAIQAiACMAJwAoACUAJQAmACcAKgAsACkALAAqACsALgAwAC0AMAAuAC8AMwA0ADEAMQAyADMANwA4ADUANQA2ADcAOwA8ADkAOQA6ADsAPgBAAD0AQAA+AD8AQgBEAEEARABCAEMARwBIAEUARQBGAEcASwBMAEkASQBKAEsAAAABAAQAAAAEAAIAAwACAAQAAQAGAAgABQAIAAYABwAKAAwACQAMAAoACwAOABAADQAQAA4ADwATABQAEQARABIAEwAXABgAFQAVABYAFwAAAA==" 211 | } 212 | ], 213 | "bufferViews": [ 214 | { 215 | "name": "bufferView_0", 216 | "buffer": 0, 217 | "byteLength": 3384, 218 | "byteOffset": 0, 219 | "byteStride": 12, 220 | "target": 34962 221 | }, 222 | { 223 | "name": "bufferView_1", 224 | "buffer": 0, 225 | "byteLength": 3384, 226 | "byteOffset": 3384, 227 | "byteStride": 12, 228 | "target": 34962 229 | }, 230 | { 231 | "name": "bufferView_2", 232 | "buffer": 0, 233 | "byteLength": 2256, 234 | "byteOffset": 6768, 235 | "byteStride": 8, 236 | "target": 34962 237 | }, 238 | { 239 | "name": "bufferView_3", 240 | "buffer": 0, 241 | "byteLength": 1036, 242 | "byteOffset": 9024, 243 | "target": 34963 244 | } 245 | ], 246 | "materials": [ 247 | { 248 | "name": "wire_154185229", 249 | "pbrMetallicRoughness": { 250 | "baseColorFactor": [ 251 | 0.6039, 252 | 0.7255, 253 | 0.898, 254 | 1 255 | ], 256 | "metallicFactor": 0, 257 | "roughnessFactor": 0.968 258 | }, 259 | "emissiveFactor": [ 260 | 0, 261 | 0, 262 | 0 263 | ], 264 | "alphaMode": "OPAQUE", 265 | "doubleSided": false 266 | }, 267 | { 268 | "name": "wire_224086086", 269 | "pbrMetallicRoughness": { 270 | "baseColorFactor": [ 271 | 0.8784, 272 | 0.3373, 273 | 0.3373, 274 | 1 275 | ], 276 | "metallicFactor": 0, 277 | "roughnessFactor": 0.968 278 | }, 279 | "emissiveFactor": [ 280 | 0, 281 | 0, 282 | 0 283 | ], 284 | "alphaMode": "OPAQUE", 285 | "doubleSided": false 286 | }, 287 | { 288 | "name": "Material__49", 289 | "pbrMetallicRoughness": { 290 | "baseColorFactor": [ 291 | 0.588, 292 | 0.588, 293 | 0.588, 294 | 1 295 | ], 296 | "metallicFactor": 0, 297 | "roughnessFactor": 0.99 298 | }, 299 | "emissiveFactor": [ 300 | 0, 301 | 0, 302 | 0 303 | ], 304 | "alphaMode": "OPAQUE", 305 | "doubleSided": false 306 | } 307 | ], 308 | "meshes": [ 309 | { 310 | "name": "Hedra001_1", 311 | "primitives": [ 312 | { 313 | "attributes": { 314 | "POSITION": 0, 315 | "NORMAL": 1, 316 | "TEXCOORD_0": 2 317 | }, 318 | "indices": 3, 319 | "material": 0, 320 | "mode": 4 321 | } 322 | ] 323 | }, 324 | { 325 | "name": "Cylinder002_1", 326 | "primitives": [ 327 | { 328 | "attributes": { 329 | "POSITION": 4, 330 | "NORMAL": 5, 331 | "TEXCOORD_0": 6 332 | }, 333 | "indices": 7, 334 | "material": 1, 335 | "mode": 4 336 | }, 337 | { 338 | "attributes": { 339 | "POSITION": 8, 340 | "NORMAL": 9, 341 | "TEXCOORD_0": 10 342 | }, 343 | "indices": 11, 344 | "material": 2, 345 | "mode": 4 346 | } 347 | ] 348 | } 349 | ], 350 | "nodes": [ 351 | { 352 | "name": "Hedra001", 353 | "mesh": 0 354 | }, 355 | { 356 | "name": "Cylinder002", 357 | "mesh": 1 358 | } 359 | ], 360 | "scene": 0, 361 | "scenes": [ 362 | { 363 | "nodes": [ 364 | 0, 365 | 1 366 | ] 367 | } 368 | ] 369 | } 370 | -------------------------------------------------------------------------------- /models/amsterdamcs.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Geodan/mapbox-3dtiles/29685d38e94088ba1d1ca721b8cf17a7d9231033/models/amsterdamcs.glb -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@geodan/mapbox-3dtiles", 3 | "version": "0.7.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@mapbox/geojson-area": { 8 | "version": "0.2.2", 9 | "resolved": "https://registry.npmjs.org/@mapbox/geojson-area/-/geojson-area-0.2.2.tgz", 10 | "integrity": "sha1-GNeBSqNr8j+7zDefjiaiKSfevxA=", 11 | "requires": { 12 | "wgs84": "0.0.0" 13 | } 14 | }, 15 | "@mapbox/geojson-rewind": { 16 | "version": "0.4.1", 17 | "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.4.1.tgz", 18 | "integrity": "sha512-mxo2MEr7izA1uOXcDsw99Kgg6xW3P4H2j4n1lmldsgviIelpssvP+jQDivFKOHrOVJDpTTi5oZJvRcHtU9Uufw==", 19 | "requires": { 20 | "@mapbox/geojson-area": "0.2.2", 21 | "concat-stream": "~1.6.0", 22 | "minimist": "^1.2.5", 23 | "sharkdown": "^0.1.0" 24 | }, 25 | "dependencies": { 26 | "minimist": { 27 | "version": "1.2.5", 28 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 29 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 30 | } 31 | } 32 | }, 33 | "@mapbox/geojson-types": { 34 | "version": "1.0.2", 35 | "resolved": "https://registry.npmjs.org/@mapbox/geojson-types/-/geojson-types-1.0.2.tgz", 36 | "integrity": "sha512-e9EBqHHv3EORHrSfbR9DqecPNn+AmuAoQxV6aL8Xu30bJMJR1o8PZLZzpk1Wq7/NfCbuhmakHTPYRhoqLsXRnw==" 37 | }, 38 | "@mapbox/jsonlint-lines-primitives": { 39 | "version": "2.0.2", 40 | "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", 41 | "integrity": "sha1-zlblOfg1UrWNENZy6k1vya3HsjQ=" 42 | }, 43 | "@mapbox/mapbox-gl-supported": { 44 | "version": "1.5.0", 45 | "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.5.0.tgz", 46 | "integrity": "sha512-/PT1P6DNf7vjEEiPkVIRJkvibbqWtqnyGaBz3nfRdcxclNSnSdaLU5tfAgcD7I8Yt5i+L19s406YLl1koLnLbg==" 47 | }, 48 | "@mapbox/point-geometry": { 49 | "version": "0.1.0", 50 | "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", 51 | "integrity": "sha1-ioP5M1x4YO/6Lu7KJUMyqgru2PI=" 52 | }, 53 | "@mapbox/tiny-sdf": { 54 | "version": "1.1.1", 55 | "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-1.1.1.tgz", 56 | "integrity": "sha512-Ihn1nZcGIswJ5XGbgFAvVumOgWpvIjBX9jiRlIl46uQG9vJOF51ViBYHF95rEZupuyQbEmhLaDPLQlU7fUTsBg==" 57 | }, 58 | "@mapbox/unitbezier": { 59 | "version": "0.0.0", 60 | "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz", 61 | "integrity": "sha1-FWUb1VOme4WB+zmIEMmK2Go0Uk4=" 62 | }, 63 | "@mapbox/vector-tile": { 64 | "version": "1.3.1", 65 | "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz", 66 | "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==", 67 | "requires": { 68 | "@mapbox/point-geometry": "~0.1.0" 69 | } 70 | }, 71 | "@mapbox/whoots-js": { 72 | "version": "3.1.0", 73 | "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", 74 | "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==" 75 | }, 76 | "ansi-regex": { 77 | "version": "0.2.1", 78 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", 79 | "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", 80 | "dev": true 81 | }, 82 | "ansi-styles": { 83 | "version": "1.1.0", 84 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", 85 | "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=", 86 | "dev": true 87 | }, 88 | "ansicolors": { 89 | "version": "0.2.1", 90 | "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.2.1.tgz", 91 | "integrity": "sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8=" 92 | }, 93 | "buffer-from": { 94 | "version": "1.1.1", 95 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 96 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" 97 | }, 98 | "cardinal": { 99 | "version": "0.4.4", 100 | "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-0.4.4.tgz", 101 | "integrity": "sha1-ylu2iltRG5D+k7ms6km97lwyv+I=", 102 | "requires": { 103 | "ansicolors": "~0.2.1", 104 | "redeyed": "~0.4.0" 105 | } 106 | }, 107 | "chalk": { 108 | "version": "0.5.1", 109 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", 110 | "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", 111 | "dev": true, 112 | "requires": { 113 | "ansi-styles": "^1.1.0", 114 | "escape-string-regexp": "^1.0.0", 115 | "has-ansi": "^0.1.0", 116 | "strip-ansi": "^0.3.0", 117 | "supports-color": "^0.2.0" 118 | } 119 | }, 120 | "commander": { 121 | "version": "2.20.3", 122 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 123 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 124 | "dev": true 125 | }, 126 | "concat-stream": { 127 | "version": "1.6.2", 128 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", 129 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", 130 | "requires": { 131 | "buffer-from": "^1.0.0", 132 | "inherits": "^2.0.3", 133 | "readable-stream": "^2.2.2", 134 | "typedarray": "^0.0.6" 135 | } 136 | }, 137 | "core-util-is": { 138 | "version": "1.0.2", 139 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 140 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 141 | }, 142 | "csscolorparser": { 143 | "version": "1.0.3", 144 | "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", 145 | "integrity": "sha1-s085HupNqPPpgjHizNjfnAQfFxs=" 146 | }, 147 | "earcut": { 148 | "version": "2.2.2", 149 | "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.2.tgz", 150 | "integrity": "sha512-eZoZPPJcUHnfRZ0PjLvx2qBordSiO8ofC3vt+qACLM95u+4DovnbYNpQtJh0DNsWj8RnxrQytD4WA8gj5cRIaQ==" 151 | }, 152 | "escape-string-regexp": { 153 | "version": "1.0.5", 154 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 155 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 156 | "dev": true 157 | }, 158 | "esm": { 159 | "version": "3.2.25", 160 | "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", 161 | "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", 162 | "dev": true 163 | }, 164 | "esprima": { 165 | "version": "1.0.4", 166 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", 167 | "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=" 168 | }, 169 | "file-size": { 170 | "version": "0.0.5", 171 | "resolved": "https://registry.npmjs.org/file-size/-/file-size-0.0.5.tgz", 172 | "integrity": "sha1-BX1Dw6Ptc12j+Q1gUqs4Dx5tXjs=", 173 | "dev": true 174 | }, 175 | "fsevents": { 176 | "version": "2.1.3", 177 | "resolved": "https://repo.geodan.io/repository/npm/fsevents/-/fsevents-2.1.3.tgz", 178 | "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", 179 | "dev": true, 180 | "optional": true 181 | }, 182 | "geojson-vt": { 183 | "version": "3.2.1", 184 | "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz", 185 | "integrity": "sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==" 186 | }, 187 | "gl-matrix": { 188 | "version": "3.3.0", 189 | "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.3.0.tgz", 190 | "integrity": "sha512-COb7LDz+SXaHtl/h4LeaFcNdJdAQSDeVqjiIihSXNrkWObZLhDI4hIkZC11Aeqp7bcE72clzB0BnDXr2SmslRA==" 191 | }, 192 | "grid-index": { 193 | "version": "1.1.0", 194 | "resolved": "https://registry.npmjs.org/grid-index/-/grid-index-1.1.0.tgz", 195 | "integrity": "sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==" 196 | }, 197 | "has-ansi": { 198 | "version": "0.1.0", 199 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", 200 | "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", 201 | "dev": true, 202 | "requires": { 203 | "ansi-regex": "^0.2.0" 204 | } 205 | }, 206 | "ieee754": { 207 | "version": "1.1.13", 208 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", 209 | "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" 210 | }, 211 | "inherits": { 212 | "version": "2.0.4", 213 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 214 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 215 | }, 216 | "is-wsl": { 217 | "version": "1.1.0", 218 | "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", 219 | "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", 220 | "dev": true 221 | }, 222 | "isarray": { 223 | "version": "1.0.0", 224 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 225 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 226 | }, 227 | "kdbush": { 228 | "version": "3.0.0", 229 | "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", 230 | "integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==" 231 | }, 232 | "mapbox-gl": { 233 | "version": "1.9.1", 234 | "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-1.9.1.tgz", 235 | "integrity": "sha512-jpBcqh+4qpOkj8RdxRdvwKPA8gzNYyMQ8HOcXgZYuEM5nKevRDjD3cEs+rUxi1JuYj4t8bIk68Lfh7aQQC1MjQ==", 236 | "requires": { 237 | "@mapbox/geojson-rewind": "^0.4.0", 238 | "@mapbox/geojson-types": "^1.0.2", 239 | "@mapbox/jsonlint-lines-primitives": "^2.0.2", 240 | "@mapbox/mapbox-gl-supported": "^1.4.0", 241 | "@mapbox/point-geometry": "^0.1.0", 242 | "@mapbox/tiny-sdf": "^1.1.0", 243 | "@mapbox/unitbezier": "^0.0.0", 244 | "@mapbox/vector-tile": "^1.3.1", 245 | "@mapbox/whoots-js": "^3.1.0", 246 | "csscolorparser": "~1.0.2", 247 | "earcut": "^2.2.2", 248 | "geojson-vt": "^3.2.1", 249 | "gl-matrix": "^3.0.0", 250 | "grid-index": "^1.1.0", 251 | "minimist": "0.0.8", 252 | "murmurhash-js": "^1.0.0", 253 | "pbf": "^3.2.1", 254 | "potpack": "^1.0.1", 255 | "quickselect": "^2.0.0", 256 | "rw": "^1.3.3", 257 | "supercluster": "^7.0.0", 258 | "tinyqueue": "^2.0.0", 259 | "vt-pbf": "^3.1.1" 260 | } 261 | }, 262 | "mime": { 263 | "version": "1.6.0", 264 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 265 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 266 | "dev": true 267 | }, 268 | "minimist": { 269 | "version": "0.0.8", 270 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 271 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 272 | }, 273 | "murmurhash-js": { 274 | "version": "1.0.0", 275 | "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", 276 | "integrity": "sha1-sGJ44h/Gw3+lMTcysEEry2rhX1E=" 277 | }, 278 | "opn": { 279 | "version": "5.5.0", 280 | "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", 281 | "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", 282 | "dev": true, 283 | "requires": { 284 | "is-wsl": "^1.1.0" 285 | } 286 | }, 287 | "pbf": { 288 | "version": "3.2.1", 289 | "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz", 290 | "integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==", 291 | "requires": { 292 | "ieee754": "^1.1.12", 293 | "resolve-protobuf-schema": "^2.1.0" 294 | } 295 | }, 296 | "potpack": { 297 | "version": "1.0.1", 298 | "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.1.tgz", 299 | "integrity": "sha512-15vItUAbViaYrmaB/Pbw7z6qX2xENbFSTA7Ii4tgbPtasxm5v6ryKhKtL91tpWovDJzTiZqdwzhcFBCwiMVdVw==" 300 | }, 301 | "process-nextick-args": { 302 | "version": "2.0.1", 303 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 304 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 305 | }, 306 | "protocol-buffers-schema": { 307 | "version": "3.4.0", 308 | "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.4.0.tgz", 309 | "integrity": "sha512-G/2kcamPF2S49W5yaMGdIpkG6+5wZF0fzBteLKgEHjbNzqjZQ85aAs1iJGto31EJaSTkNvHs5IXuHSaTLWBAiA==" 310 | }, 311 | "quickselect": { 312 | "version": "2.0.0", 313 | "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", 314 | "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" 315 | }, 316 | "readable-stream": { 317 | "version": "2.3.7", 318 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 319 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 320 | "requires": { 321 | "core-util-is": "~1.0.0", 322 | "inherits": "~2.0.3", 323 | "isarray": "~1.0.0", 324 | "process-nextick-args": "~2.0.0", 325 | "safe-buffer": "~5.1.1", 326 | "string_decoder": "~1.1.1", 327 | "util-deprecate": "~1.0.1" 328 | } 329 | }, 330 | "redeyed": { 331 | "version": "0.4.4", 332 | "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-0.4.4.tgz", 333 | "integrity": "sha1-N+mQpvKyGyoRwuakj9QTVpjLqX8=", 334 | "requires": { 335 | "esprima": "~1.0.4" 336 | } 337 | }, 338 | "resolve-protobuf-schema": { 339 | "version": "2.1.0", 340 | "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", 341 | "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", 342 | "requires": { 343 | "protocol-buffers-schema": "^3.3.1" 344 | } 345 | }, 346 | "rollup": { 347 | "version": "2.8.2", 348 | "resolved": "https://repo.geodan.io/repository/npm/rollup/-/rollup-2.8.2.tgz", 349 | "integrity": "sha512-LRzMcB8V1M69pSvf6uCbR+W9OPCy5FuxcIwqioWg5RKidrrqKbzjJF9pEGXceaMVkbptNFZgIVJlUokCU0sfng==", 350 | "dev": true, 351 | "requires": { 352 | "fsevents": "~2.1.2" 353 | } 354 | }, 355 | "rw": { 356 | "version": "1.3.3", 357 | "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", 358 | "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" 359 | }, 360 | "safe-buffer": { 361 | "version": "5.1.2", 362 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 363 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 364 | }, 365 | "sharkdown": { 366 | "version": "0.1.1", 367 | "resolved": "https://registry.npmjs.org/sharkdown/-/sharkdown-0.1.1.tgz", 368 | "integrity": "sha512-exwooSpmo5s45lrexgz6Q0rFQM574wYIX3iDZ7RLLqOb7IAoQZu9nxlZODU972g19sR69OIpKP2cpHTzU+PHIg==", 369 | "requires": { 370 | "cardinal": "~0.4.2", 371 | "minimist": "0.0.5", 372 | "split": "~0.2.10" 373 | }, 374 | "dependencies": { 375 | "minimist": { 376 | "version": "0.0.5", 377 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.5.tgz", 378 | "integrity": "sha1-16oye87PUY+RBqxrjwA/o7zqhWY=" 379 | } 380 | } 381 | }, 382 | "split": { 383 | "version": "0.2.10", 384 | "resolved": "https://registry.npmjs.org/split/-/split-0.2.10.tgz", 385 | "integrity": "sha1-Zwl8YB1pfOE2j0GPBs0gHPBSGlc=", 386 | "requires": { 387 | "through": "2" 388 | } 389 | }, 390 | "static-server": { 391 | "version": "2.2.1", 392 | "resolved": "https://registry.npmjs.org/static-server/-/static-server-2.2.1.tgz", 393 | "integrity": "sha512-j5eeW6higxYNmXMIT8iHjsdiViTpQDthg7o+SHsRtqdbxscdHqBHXwrXjHC8hL3F0Tsu34ApUpDkwzMBPBsrLw==", 394 | "dev": true, 395 | "requires": { 396 | "chalk": "^0.5.1", 397 | "commander": "^2.3.0", 398 | "file-size": "0.0.5", 399 | "mime": "^1.2.11", 400 | "opn": "^5.2.0" 401 | } 402 | }, 403 | "string_decoder": { 404 | "version": "1.1.1", 405 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 406 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 407 | "requires": { 408 | "safe-buffer": "~5.1.0" 409 | } 410 | }, 411 | "strip-ansi": { 412 | "version": "0.3.0", 413 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", 414 | "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", 415 | "dev": true, 416 | "requires": { 417 | "ansi-regex": "^0.2.1" 418 | } 419 | }, 420 | "supercluster": { 421 | "version": "7.0.0", 422 | "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.0.0.tgz", 423 | "integrity": "sha512-8VuHI8ynylYQj7Qf6PBMWy1PdgsnBiIxujOgc9Z83QvJ8ualIYWNx2iMKyKeC4DZI5ntD9tz/CIwwZvIelixsA==", 424 | "requires": { 425 | "kdbush": "^3.0.0" 426 | } 427 | }, 428 | "supports-color": { 429 | "version": "0.2.0", 430 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", 431 | "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=", 432 | "dev": true 433 | }, 434 | "three": { 435 | "version": "0.115.0", 436 | "resolved": "https://registry.npmjs.org/three/-/three-0.115.0.tgz", 437 | "integrity": "sha512-mAV2Ky3RdcbdSbR9capI+tKLvRldWYxd4151PZTT/o7+U2jh9Is3a4KmnYwzyUAhB2ZA3pXSgCd2DOY4Tj5kow==" 438 | }, 439 | "through": { 440 | "version": "2.3.8", 441 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 442 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" 443 | }, 444 | "tinyqueue": { 445 | "version": "2.0.3", 446 | "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", 447 | "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==" 448 | }, 449 | "typedarray": { 450 | "version": "0.0.6", 451 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 452 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" 453 | }, 454 | "util-deprecate": { 455 | "version": "1.0.2", 456 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 457 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 458 | }, 459 | "vt-pbf": { 460 | "version": "3.1.1", 461 | "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.1.tgz", 462 | "integrity": "sha512-pHjWdrIoxurpmTcbfBWXaPwSmtPAHS105253P1qyEfSTV2HJddqjM+kIHquaT/L6lVJIk9ltTGc0IxR/G47hYA==", 463 | "requires": { 464 | "@mapbox/point-geometry": "0.1.0", 465 | "@mapbox/vector-tile": "^1.3.1", 466 | "pbf": "^3.0.5" 467 | } 468 | }, 469 | "wgs84": { 470 | "version": "0.0.0", 471 | "resolved": "https://registry.npmjs.org/wgs84/-/wgs84-0.0.0.tgz", 472 | "integrity": "sha1-NP3FVZF7blfPKigu0ENxDASc3HY=" 473 | } 474 | } 475 | } 476 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@geodan/mapbox-3dtiles", 3 | "author": "Rubio Vaughan, Anne Blankert, Tom van Tilburg", 4 | "version": "0.7.0", 5 | "description": "OGC 3D Tiles layer for mapbox-gl", 6 | "main": "Mapbox3DTiles", 7 | "scripts": { 8 | "build": "npx rollup -c", 9 | "start": "npm run build && npx static-server -p 8082", 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "version": "rollup -c rollup.config.js && exit 0" 12 | }, 13 | "files": [ 14 | "Mapbox3DTiles.*" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/geodan/mapbox-3dtiles.git" 19 | }, 20 | "publishConfig": { 21 | "registry": "https://repo.geodan.io/repository/npm-hosted/" 22 | }, 23 | "keywords": [ 24 | "3d", 25 | "gltf", 26 | "b3dm", 27 | "mapbox-gl", 28 | "mapbox", 29 | "three", 30 | "javascript", 31 | "tiles", 32 | "ogc" 33 | ], 34 | "license": "BSD-3-Clause", 35 | "bugs": { 36 | "url": "https://github.com/geodan/mapbox-3dtiles/issues" 37 | }, 38 | "homepage": "https://github.com/geodan/mapbox-3dtiles#readme", 39 | "dependencies": { 40 | "mapbox-gl": "^1.10.0", 41 | "three": "^0.116.0" 42 | }, 43 | "devDependencies": { 44 | "esm": "^3.2.25", 45 | "rollup": "^2.8.2", 46 | "static-server": "^2.2.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | input: ['./Mapbox3DTiles.mjs'], 3 | output: { 4 | name: 'Mapbox3DTiles', 5 | file: 'dist/Mapbox3DTiles.js', 6 | format: 'iife', 7 | sourcemap: true 8 | } 9 | }; -------------------------------------------------------------------------------- /rotterdam/rotterdam_3dtiles_small.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Geodan/mapbox-3dtiles/29685d38e94088ba1d1ca721b8cf17a7d9231033/rotterdam/rotterdam_3dtiles_small.tar.gz -------------------------------------------------------------------------------- /rotterdam/tileset_min.json: -------------------------------------------------------------------------------- 1 | { 2 | "asset": { 3 | "version": "1.0" 4 | }, 5 | "geometricError": 500, 6 | "root": { 7 | "boundingVolume": { 8 | "box": [ 9 | 0.0, 10 | 82.102, 11 | 0.0, 12 | 3389.66, 13 | 0, 14 | 0, 15 | 0, 16 | 2190.162, 17 | 0, 18 | 0, 19 | 0, 20 | 87.9 21 | ] 22 | }, 23 | "geometricError": 500, 24 | "children": [ 25 | { 26 | "boundingVolume": { 27 | "box": [ 28 | -2294.571, 29 | -1108.153, 30 | 0.25, 31 | 1026.006, 32 | 0, 33 | 0, 34 | 0, 35 | 999.907, 36 | 0, 37 | 0, 38 | 0, 39 | 87.65 40 | ] 41 | }, 42 | "geometricError": 250.0, 43 | "refine": "add", 44 | "content": { 45 | "uri": "tiles/1.b3dm" 46 | }, 47 | "children": [{ 48 | "boundingVolume": { 49 | "box": [ 50 | -2851.337, 51 | -1877.414, 52 | -63.4, 53 | 403.129, 54 | 0, 55 | 0, 56 | 0, 57 | 230.646, 58 | 0, 59 | 0, 60 | 0, 61 | 24.0 62 | ] 63 | }, 64 | "geometricError": 125.0, 65 | "children": [ { 66 | "geometricError": 62.5, 67 | "children": [], 68 | "refine": "add", 69 | "content": { 70 | "uri": "tiles/3.b3dm" 71 | }, 72 | "boundingVolume": { 73 | "box": [ 74 | -3031.677, 75 | -1902.989, 76 | -83.1, 77 | 141.919, 78 | 0, 79 | 0, 80 | 0, 81 | 200.866, 82 | -1, 83 | -2, 84 | -3, 85 | 4.3 86 | ] 87 | } 88 | 89 | }], 90 | "refine": "add", 91 | "content": { 92 | "uri": "tiles/2.b3dm" 93 | } 94 | }] 95 | 96 | } 97 | ], 98 | "refine": "add", 99 | "transform": [ 100 | 1.0, 101 | 0.0, 102 | 0.0, 103 | 0.0, 104 | 0.0, 105 | 1.0, 106 | 0.0, 107 | 0.0, 108 | 0.0, 109 | 0.0, 110 | 1.0, 111 | 0.0, 112 | 499946.281, 113 | 6783362.155, 114 | 87.4, 115 | 1.0 116 | ] 117 | } 118 | } -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Geodan/mapbox-3dtiles/29685d38e94088ba1d1ca721b8cf17a7d9231033/screenshot.png -------------------------------------------------------------------------------- /treelocs.mjs: -------------------------------------------------------------------------------- 1 | let treelocs = [ 2 | [4.93934125792659,52.31068267498259], 3 | [4.939084829325851,52.310586792762095], 4 | [4.940580352044657,52.310883113848796], 5 | [4.939872859246081,52.310708342315465], 6 | [4.937482514566313,52.31083249607341], 7 | [4.940549566189623,52.31036141803879], 8 | [4.940492379951698,52.3104195148999], 9 | [4.94063289495592,52.30988333937074], 10 | [4.940396431330629,52.31038921419106], 11 | [4.9402798766225295,52.31051528606533], 12 | [4.9401650553222,52.310640986886725], 13 | [4.939989143110358,52.31083229223405], 14 | [4.94051440876224,52.310012118505206], 15 | [4.940337781874507,52.31045280598021], 16 | [4.940048187760483,52.31076887288175], 17 | [4.940219729813943,52.31032679150942], 18 | [4.938096614547563,52.31016328812162], 19 | [4.9375698264850465,52.31073512167192], 20 | [4.940161596401904,52.31039154458762], 21 | [4.946629761490149,52.311507648136434], 22 | [4.947992043753911,52.312681665335916], 23 | [4.949034978184495,52.31326552357491], 24 | [4.945156582229633,52.310689710120435], 25 | [4.9471515996541715,52.310936124679216], 26 | [4.938319421216934,52.30986945099073], 27 | [4.940459203049052,52.31007308095018], 28 | [4.945457581166043,52.310576815422564], 29 | [4.938233362957837,52.30997079432882], 30 | [4.938950618936025,52.31053511676179], 31 | [4.945745431641138,52.311906932276365], 32 | [4.945627323400759,52.31051254833972], 33 | [4.950371029160187,52.3145330322278], 34 | [4.94913412135922,52.31412435880354], 35 | [4.944874807755698,52.32013954311462], 36 | [4.9430956118198885,52.31996625355329], 37 | [4.944437989325806,52.319988487446956], 38 | [4.944454102367928,52.320217205701844], 39 | [4.944781529747888,52.31996237786863], 40 | [4.943453130872304,52.31763319081588], 41 | [4.945283985535638,52.31801650592866], 42 | [4.939006641531251,52.315819456824364], 43 | [4.948356549723852,52.31634451447712], 44 | [4.942207407064759,52.31897665151341], 45 | [4.948144961607109,52.316272312523495], 46 | [4.943481376225846,52.31944700321531], 47 | [4.95025229595555,52.317993310517174], 48 | [4.946710119233332,52.31967079349671], 49 | [4.949877642459289,52.318507644693646], 50 | [4.944968902327532,52.32000931150226], 51 | [4.939969784242815,52.317032264168965], 52 | [4.935974914866142,52.31518648061391], 53 | [4.937893828094217,52.31680300209046], 54 | [4.938122631640651,52.31688130957961], 55 | [4.93751743313421,52.31679226962589], 56 | [4.934898676708615,52.3163638871476], 57 | [4.94545232381993,52.320173247929446], 58 | [4.948640641256683,52.312918023666185], 59 | [4.949785201016353,52.313017837388294], 60 | [4.947805435606168,52.313257260116714], 61 | [4.948262399606632,52.31368078017301], 62 | [4.949750555827209,52.31204583320143], 63 | [4.948575658840881,52.31225131153471], 64 | [4.9498049805693105,52.313159683816565], 65 | [4.948277669405524,52.31227742127634], 66 | [4.947446627885353,52.31313481646057], 67 | [4.950020199478805,52.31323275912009], 68 | [4.949733996833015,52.31250343895364], 69 | [4.948654677967069,52.31368151601154], 70 | [4.948603915352935,52.313735757832056], 71 | [4.948774918605107,52.31307476502686], 72 | [4.948175190146154,52.31246339373952], 73 | [4.944072622156499,52.31077988255269], 74 | [4.944407423630788,52.312962912858296], 75 | [4.945400833311467,52.31178765412582], 76 | [4.946723712799464,52.31140577090793], 77 | [4.941519758762003,52.31281860926677], 78 | [4.946981015690854,52.31112390998547], 79 | [4.944505641699502,52.312998818159336], 80 | [4.945408922656327,52.31255814441895], 81 | [4.94689640214261,52.311217355680974], 82 | [4.944553147625089,52.31301282813016], 83 | [4.944721527610621,52.311553338071775], 84 | [4.945862372091681,52.31194606104116], 85 | [4.945169965952085,52.31170736659306], 86 | [4.942398166847113,52.31275578324384], 87 | [4.944419333707513,52.311449755531115], 88 | [4.942841175098931,52.312724400053085], 89 | [4.941737360110055,52.312803187941554], 90 | [4.945881900790829,52.31806534844067], 91 | [4.946822996914899,52.31821167999323], 92 | [4.94321022802356,52.31792538891239], 93 | [4.950140947656686,52.31813512176215], 94 | [4.943268358897143,52.31786143431853], 95 | [4.944854546697784,52.31804393157185], 96 | [4.944402952953231,52.31853426009686], 97 | [4.946912979554488,52.31857679276105], 98 | [4.946394434454242,52.320017696209284], 99 | [4.9456349548280265,52.31831268737136], 100 | [4.945236928464386,52.31945329574796], 101 | [4.942452769266459,52.31870812956706], 102 | [4.94502375573807,52.31960269391871], 103 | [4.94574528979516,52.31817546032015], 104 | [4.942830951610175,52.318345392857736], 105 | [4.942816457680336,52.31927099855808], 106 | [4.94295543327417,52.3183856941833], 107 | [4.946480248775795,52.31992476776218], 108 | [4.9436013420886145,52.31969903889018], 109 | [4.946069409532776,52.31799890905984], 110 | [4.944282227245662,52.317847798110435], 111 | [4.947036891376397,52.31812285886773], 112 | [4.946230048177247,52.32019932869196], 113 | [4.944576043039055,52.319001319458735], 114 | [4.949719674103762,52.318791703455275], 115 | [4.9470514265950944,52.31929972725593], 116 | [4.947803351391152,52.317968979170224], 117 | [4.945626575560521,52.318134608243504], 118 | [4.9471474721940085,52.3179010924073], 119 | [4.947960103430892,52.3177792271181], 120 | [4.949904588840669,52.31858620756145], 121 | [4.944914495782593,52.31892712385074], 122 | [4.942851966841084,52.32022701778791], 123 | [4.946468837717823,52.31811216073012], 124 | [4.947970572631678,52.317809150342214], 125 | [4.9464422151745655,52.31858852152504], 126 | [4.945831786942463,52.31881523634173], 127 | [4.943003864016713,52.318332668387015], 128 | [4.943517602005488,52.31951174320423], 129 | [4.942626867805494,52.3192043429678], 130 | [4.949879356800316,52.31861658284796], 131 | [4.944403822504639,52.317714155631], 132 | [4.945633943581841,52.31892797774311], 133 | [4.942980049450158,52.32009107333711], 134 | [4.94108871634851,52.32019558501233], 135 | [4.945162188468137,52.3181485037832], 136 | [4.94229731303843,52.31887710847529], 137 | [4.942291939916552,52.319246701604], 138 | [4.943967142619751,52.31773552913384], 139 | [4.948032620977066,52.31797134040926], 140 | [4.941046730348497,52.32024174968396], 141 | [4.944616728999561,52.31895708104413], 142 | [4.946567387749637,52.31863053827559], 143 | [4.948008269247808,52.3178977300049], 144 | [4.944077310323654,52.3188918508619], 145 | [4.944809210627382,52.31889112063774], 146 | [4.945504376493327,52.318265805033086], 147 | [4.946795954784214,52.31957854796753], 148 | [4.946487619821967,52.31815707071344], 149 | [4.943336314386108,52.31960187568662], 150 | [4.944871764535551,52.319104643431174], 151 | [4.943128956096061,52.318015500930734], 152 | [4.9498286674217375,52.31867041198608], 153 | [4.945006432922206,52.31896033580864], 154 | [4.946054717344251,52.31857315519991], 155 | [4.947514460650914,52.31878495755421], 156 | [4.943605628623692,52.31866158699861], 157 | [4.943713201878036,52.31957900408979], 158 | [4.942822239200904,52.320157853062014], 159 | [4.941391790730974,52.31986578853382], 160 | [4.944376682720053,52.31846241234941], 161 | [4.946236625184764,52.318344186695555], 162 | [4.947393013881128,52.31874127494749], 163 | [4.946627452991028,52.31958391545186], 164 | [4.943049278572804,52.31991053517315], 165 | [4.9411754509249395,52.3201012527768], 166 | [4.9447413746606905,52.318163890659505], 167 | [4.944081901873567,52.319704956155434], 168 | [4.944504348149557,52.31985105205523], 169 | [4.947135802447125,52.319208114178565], 170 | [4.942936440743156,52.320034241672005], 171 | [4.9440096877859805,52.31984112907025], 172 | [4.947988049888656,52.31785118814132], 173 | [4.945304016619918,52.319206813953556], 174 | [4.945242042231208,52.317616707724646], 175 | [4.942667185945571,52.318470022314514], 176 | [4.946650760501075,52.31974050634358], 177 | [4.945070761490534,52.317704570393055], 178 | [4.946571418492493,52.318460729270214], 179 | [4.944187221057537,52.31763874617831], 180 | [4.9433470787324385,52.31777715468448], 181 | [4.942247855588341,52.31892853029463], 182 | [4.943930545037389,52.3198161431581], 183 | [4.941217930986339,52.320055521334545], 184 | [4.944714266078966,52.317821283050655], 185 | [4.944238637866232,52.31992062053386], 186 | [4.9426924485915995,52.32022402781887], 187 | [4.950304431178634,52.31794146384346], 188 | [4.950104117985387,52.318192022113756], 189 | [4.9470202016288685,52.31807543119264], 190 | [4.946454863793301,52.318419364783296], 191 | [4.94975768903512,52.318744118943236], 192 | [4.945206842139142,52.32009065021455], 193 | [4.945954274263351,52.31868017555805], 194 | [4.945389832560684,52.319298153649086], 195 | [4.94502525693785,52.319696486954804], 196 | [4.945147663120363,52.31956859895771], 197 | [4.947223178476334,52.31911564038352], 198 | [4.94432917071791,52.319790295195865], 199 | [4.941555054730184,52.319914865708995], 200 | [4.943166340485247,52.317972949302664], 201 | [4.945827358914656,52.31793077770834], 202 | [4.94603944581411,52.31791476161755], 203 | [4.944099565036819,52.31869277407838], 204 | [4.945187480172208,52.31757286822234], 205 | [4.947985156756359,52.309989743543895], 206 | [4.9479340580528355,52.310060530379445], 207 | [4.947980553670967,52.31033678993791], 208 | [4.947638110033757,52.31039019966265], 209 | [4.94928176857347,52.31154410821046], 210 | [4.947665041704552,52.3132091752152], 211 | [4.947804262868139,52.312695141740164], 212 | [4.949822997006001,52.31196693664899], 213 | [4.950506947712382,52.31124397839009], 214 | [4.950117600522129,52.313256828803276], 215 | [4.950113973413466,52.31208549145824], 216 | [4.949806421880132,52.3132486502227], 217 | [4.948323510511245,52.313610758390745], 218 | [4.948683223071384,52.31317553772581], 219 | [4.950086155559059,52.31310365966155], 220 | [4.948887498999164,52.31342715279763], 221 | [4.9502345243344035,52.31330138177426], 222 | [4.949672763214253,52.31257141180145], 223 | [4.948816391236405,52.31350574812186], 224 | [4.950066692668837,52.31213795820982], 225 | [4.948395721798686,52.313531897821605], 226 | [4.948219370097855,52.313400498264905], 227 | [4.949986781260351,52.313110266949806], 228 | [4.948554799613389,52.3137880553882], 229 | [4.949759476621383,52.3116952616673], 230 | [4.948183704132623,52.31282173237008], 231 | [4.950445738923071,52.31130972276788], 232 | [4.949397707706657,52.31157940996033], 233 | [4.950163393998448,52.31203163948637], 234 | [4.948574025037758,52.31281795835162], 235 | [4.948885847099096,52.312576228233794], 236 | [4.949167432212828,52.31150838086734], 237 | [4.947999745442327,52.31332382618588], 238 | [4.948866863366867,52.312973957228294], 239 | [4.950172000312201,52.31137721669166], 240 | [4.949100638690845,52.313194960998715], 241 | [4.948821498828688,52.313023698293115], 242 | [4.940106429193565,52.3107051089277], 243 | [4.9469144079605005,52.31034280299292], 244 | [4.94542290741689,52.310500954816185], 245 | [4.94668834286536,52.3102637496153], 246 | [4.945003474168237,52.31074446671466], 247 | [4.947033811912594,52.31038458219833], 248 | [4.947376176103818,52.31050373049148], 249 | [4.9450213024735765,52.31061213422347], 250 | [4.945025586808627,52.30983521863914], 251 | [4.945861386656555,52.3099884719695], 252 | [4.946802572174929,52.310303838056996], 253 | [4.945976632959801,52.309887582523665], 254 | [4.9451460574845925,52.30987705767717], 255 | [4.94531141140162,52.31062994457088], 256 | [4.947142999926855,52.310422943869945], 257 | [4.94787938803129,52.31036351245199], 258 | [4.947554471684033,52.310480216123324], 259 | [4.948349054378946,52.31242753946364], 260 | [4.945635781088443,52.31637162698871], 261 | [4.946546822863158,52.31529615772994], 262 | [4.94596626369245,52.316829034772], 263 | [4.939178821468897,52.31562834564454], 264 | [4.945410265373253,52.31262074037847], 265 | [4.9355330442556316,52.317903320693496], 266 | [4.93619074374311,52.31796067327224], 267 | [4.935339467471679,52.31811783888724], 268 | [4.947035730698759,52.3186189977345], 269 | [4.947165847480653,52.31794419422697], 270 | [4.948098381708851,52.31235761003729], 271 | [4.9481060768027625,52.31795876054223], 272 | [4.948789098104796,52.3123421064219], 273 | [4.945615543783766,52.320227316519], 274 | [4.944387468370751,52.31788351403373], 275 | [4.949780517154573,52.31871957684786], 276 | [4.944919151541756,52.31971027305441], 277 | [4.9502141505679855,52.318039366788604], 278 | [4.949691321744394,52.3188276395823], 279 | [4.941720342999734,52.319503200932324], 280 | [4.942768783363493,52.31941249379236], 281 | [4.944593710596026,52.31795367144595], 282 | [4.946907441272652,52.31798423505748], 283 | [4.945924014352147,52.31613910107074], 284 | [4.944004143003699,52.31701240320764], 285 | [4.9440633577984,52.31694687941695], 286 | [4.9419405957809035,52.319264532783095], 287 | [4.950154045098642,52.31811122671593], 288 | [4.947130190015754,52.31511192264903], 289 | [4.944514039806009,52.31840885592953], 290 | [4.9446129827846566,52.31778576197996], 291 | [4.947234505472403,52.31659145653535], 292 | [4.949087851548694,52.312361970741144], 293 | [4.943775807142998,52.31922413174231], 294 | [4.945408213126371,52.31805888888681], 295 | [4.945136827578849,52.31947540917616], 296 | [4.942491543793367,52.319320990044204], 297 | [4.946425837928277,52.315678577499405], 298 | [4.943069712605031,52.31935711770916], 299 | [4.948717350432118,52.31730666367388], 300 | [4.945858643290864,52.31868364764387], 301 | [4.9468429549149935,52.315225807537566], 302 | [4.946980434514226,52.31810718983141], 303 | [4.943852494514985,52.3190394327396], 304 | [4.944484480642982,52.31834325139065], 305 | [4.947777066210648,52.31684673963074], 306 | [4.943753583924643,52.31766390515213], 307 | [4.948756586416059,52.31648138061817], 308 | [4.946012181855244,52.318514342361915], 309 | [4.942969381002798,52.319482893445844], 310 | [4.945478157777724,52.31909731907651], 311 | [4.946021099358275,52.31787182150722], 312 | [4.94571228680735,52.318949229611114], 313 | [4.950405860888822,52.31782832277493], 314 | [4.941602199680483,52.31986314786569], 315 | [4.946310707545156,52.32010863708661], 316 | [4.943407443178222,52.31770979304532], 317 | [4.945129813439758,52.3177408410409], 318 | [4.945844214525886,52.31797613898666], 319 | [4.945874825206969,52.316192296008715], 320 | [4.9446751531326845,52.31903589768814], 321 | [4.945287132523158,52.31819113221376], 322 | [4.939094746582895,52.315724061691256], 323 | [4.9380930048936404,52.316585472085], 324 | [4.940880497238156,52.31663966585505], 325 | [4.938748004315593,52.31610082792108], 326 | [4.9390057623623225,52.31594150760233], 327 | [4.939716297377331,52.31694487013528], 328 | [4.939184327725132,52.31574549530331], 329 | [4.939113258472407,52.31582401305598], 330 | [4.940951821974928,52.31656181311714], 331 | [4.939969020874558,52.316854581825204], 332 | [4.940098131360995,52.31707840722386], 333 | [4.940663722308903,52.316875945521886], 334 | [4.941275732747058,52.31620808000589], 335 | [4.94262049309034,52.312740009561395], 336 | [4.9389262845457065,52.3159070888416], 337 | [4.9388621237170405,52.3160973597623], 338 | [4.945499643474924,52.312560478479], 339 | [4.940916065778295,52.316601323343406], 340 | [4.938954691285483,52.31650607946266], 341 | [4.93887304538517,52.316589050880374], 342 | [4.944405812625738,52.31302009611247], 343 | [4.940771747961703,52.31675808299461], 344 | [4.9388272472168175,52.31613674686931], 345 | [4.941053267510079,52.316647795365455], 346 | [4.941167311285528,52.316326193164684], 347 | [4.9498925604519775,52.31188986292166], 348 | [4.94851891707266,52.312162706261205], 349 | [4.948359739771906,52.31282006668036], 350 | [4.949051344242544,52.31147333898482], 351 | [4.9502229153189905,52.311963165727974], 352 | [4.944820616869216,52.31066769239652], 353 | [4.945514053751242,52.31182772204983], 354 | [4.9388278774277055,52.31048911043862], 355 | [4.942179404978047,52.31277145307627], 356 | [4.941295991506779,52.31283469002251], 357 | [4.945603892165389,52.31260193287647], 358 | [4.9410741760349755,52.312850615935595], 359 | [4.9394533674141305,52.31072448816548], 360 | [4.939813167881058,52.31077189395241], 361 | [4.940044522303489,52.31051961854657], 362 | [4.945632963213346,52.31186788300266], 363 | [4.94456804313247,52.31150125484977], 364 | [4.938175140112833,52.31007463402117], 365 | [4.937776734394485,52.317261266793224], 366 | [4.936407372722601,52.314713322493546], 367 | [4.937825511138179,52.31687726031966], 368 | [4.937614210012072,52.317110032012906], 369 | [4.935761493437086,52.31541992406214], 370 | [4.936050016303126,52.31798997613975], 371 | [4.935616539027493,52.31557874225789], 372 | [4.9363128459382315,52.3202030685107], 373 | [4.935331718630798,52.31589035775371], 374 | [4.935811736392801,52.31784380670114], 375 | [4.935484541970498,52.318102475536264], 376 | [4.935187540879952,52.31812050310787], 377 | [4.934612965503957,52.3166770164217], 378 | [4.937157835130069,52.317741072631506], 379 | [4.93608802651098,52.31778534345248], 380 | [4.934741884685753,52.318066185068986], 381 | [4.934605137604521,52.31802424630133], 382 | [4.934461925977998,52.31797625183349], 383 | [4.935903950526236,52.31526430479968], 384 | [4.934685998747178,52.31659665739032], 385 | [4.937750872331315,52.3169602755556], 386 | [4.9367482827235865,52.31784501254368], 387 | [4.9351847071525405,52.31605069548026], 388 | [4.9357654234889825,52.31804846154618], 389 | [4.947873085717221,52.31013180181484], 390 | [4.947958039777358,52.31028240212027], 391 | [4.937829429916046,52.31045444865336], 392 | [4.940687743516828,52.309819930815635], 393 | [4.945285477180606,52.31174707479817], 394 | [4.949510926633403,52.311615474480774], 395 | [4.948222222438807,52.312161713501986], 396 | [4.950385409767679,52.31137547036178], 397 | [4.939209298109529,52.310633155881675], 398 | [4.940526976822952,52.310939849978844], 399 | [4.947240177087871,52.3104570005572], 400 | [4.946809821984793,52.31131092881064], 401 | [4.947067843867655,52.31103039157849], 402 | [4.945221164554328,52.31055676137724], 403 | [4.94555774009443,52.312642052830206], 404 | [4.940278551906956,52.31026498900035], 405 | [4.939986885726308,52.3105801221899], 406 | [4.937396714637234,52.31092532833527], 407 | [4.937661507691159,52.310637763809545], 408 | [4.937921211728815,52.31035427778998], 409 | [4.940567708903079,52.30994961192656], 410 | [4.943985172155818,52.31899207893891], 411 | [4.934886070571642,52.31809633318419], 412 | [4.941306135931207,52.319959307111816], 413 | [4.942507784707923,52.31864646451057], 414 | [4.946510935871941,52.316436276760726], 415 | [4.946813719371396,52.31854396846326], 416 | [4.945410325237681,52.31823439210352], 417 | [4.945757376085586,52.318353948731136], 418 | [4.944636603481567,52.31827672679337], 419 | [4.9446144467636906,52.32004452135964], 420 | [4.946911306108881,52.3187500978003], 421 | [4.947939367087882,52.316202136717436], 422 | [4.9450448922752495,52.3178340594909], 423 | [4.945579445211651,52.31909000366731], 424 | [4.949961500527973,52.31852602006733], 425 | [4.949686465295877,52.3188352702181], 426 | [4.943907567006636,52.31964512756337], 427 | [4.94605512123666,52.31796083748668], 428 | [4.945045163202266,52.31810600368076], 429 | [4.9441266958802,52.31687818037704], 430 | [4.947826113019084,52.31679295019035], 431 | [4.947784524482692,52.31792254130916], 432 | [4.946321159430984,52.31564268570654], 433 | [4.947156506818568,52.318659244886426], 434 | [4.943236348465051,52.31941514806295], 435 | [4.945343657489964,52.31750678028896], 436 | [4.944779288299494,52.31907252586271], 437 | [4.94580845013551,52.317885408766934], 438 | [4.947874949927858,52.31688051971946], 439 | [4.9458691838389175,52.31821902793237], 440 | [4.942566765910638,52.31858127314826], 441 | [4.943732965115293,52.31917275881524], 442 | [4.947513207117804,52.316055778475274], 443 | [4.9445100855318715,52.31775032466037], 444 | [4.945233394943329,52.319366191011376], 445 | [4.9482169435414525,52.316928166147555], 446 | [4.947000547595057,52.31803113820675], 447 | [4.949150106372321,52.31724378851709], 448 | [4.9489283980633685,52.317278136561804], 449 | [4.943859688882191,52.317701152708885], 450 | [4.943631231483522,52.31938190046342], 451 | [4.94586445044173,52.31802079384997], 452 | [4.9431341836255065,52.31953947901369], 453 | [4.944066196743622,52.31777391849898], 454 | [4.9468294840519,52.31652138183266], 455 | [4.944695077477159,52.3181133679752], 456 | [4.943874429948458,52.31753196807903], 457 | [4.946725573369008,52.3157820287458], 458 | [4.9466948663094215,52.318502128294995], 459 | [4.944267173530449,52.31858398451381], 460 | [4.946562746176242,52.31983202462956], 461 | [4.949828535492766,52.318564625402644], 462 | [4.9459079802774575,52.31534807162004], 463 | [4.941262568083318,52.32000676013898], 464 | [4.944281846993119,52.31875394437522], 465 | [4.945946243511886,52.31859534594285], 466 | [4.94496927580337,52.319002250725276], 467 | [4.945926598423554,52.31676640378969], 468 | [4.945290331313547,52.3174640239823], 469 | [4.942626288147628,52.318517692601525], 470 | [4.946291027631325,52.31626577709799], 471 | [4.94679328926103,52.31813705175757], 472 | [4.945386218608801,52.31735929701122], 473 | [4.9439600274525155,52.317058003706144], 474 | [4.946925349549187,52.31802708351856], 475 | [4.946919651048852,52.31655082884734], 476 | [4.941838550752182,52.31937512076744], 477 | [4.945911600775592,52.31646608412061], 478 | [4.949848823736719,52.31864909559854], 479 | [4.946810730788366,52.318182594830155], 480 | [4.94449065735735,52.31791913230576], 481 | [4.944209182388126,52.318834023033766], 482 | [4.943977317221433,52.317568304696266], 483 | [4.944433032791084,52.316540218337416], 484 | [4.943635503277527,52.3192757528732], 485 | [4.942695309691814,52.31842816405022], 486 | [4.946966114905861,52.31939397919559], 487 | [4.9470798579715565,52.3151667586046], 488 | [4.945733971164779,52.318820102269676], 489 | [4.9450625051588295,52.32020403093196], 490 | [4.9459635068901635,52.31622349982165], 491 | [4.944140612250185,52.319725659173145], 492 | [4.945990566458776,52.318260015543814], 493 | [4.944082704991614,52.31760393156673], 494 | [4.944670373836045,52.31889875331699], 495 | [4.944588681769649,52.31637085982222], 496 | [4.941239760664309,52.316247238993995], 497 | [4.939437338247633,52.31547043833741], 498 | [4.941136830174629,52.316554646524295], 499 | [4.940795922009645,52.31692986780468], 500 | [4.938614583192959,52.31650102444132], 501 | [4.939328838945714,52.31558723732454], 502 | [4.941097090493703,52.316405182657434], 503 | [4.939257409906802,52.31566658067895], 504 | [4.940092355774597,52.316897407425415], 505 | [4.938950397337699,52.316681666495995], 506 | [4.940270536699475,52.317070484316254], 507 | [4.939887148197643,52.31693980901004], 508 | [4.93882640710426,52.31663854960577], 509 | [4.93936503929581,52.315548825759926], 510 | [4.939840662754174,52.316987313420384], 511 | [4.940224131949753,52.31694088494595], 512 | [4.939042446342098,52.31590173182663], 513 | [4.939762606106595,52.316895989974256], 514 | [4.939149200216425,52.31578510625051], 515 | [4.940736667769088,52.31679742494905], 516 | [4.939078746879984,52.31586216130603], 517 | [4.938998346518675,52.31663305349154], 518 | [4.940628198049376,52.316914216221626], 519 | [4.941309222097874,52.316366624883], 520 | [4.938835881227935,52.31600780489329], 521 | [4.940844975379863,52.31667919491919], 522 | [4.938790799124897,52.31617636169146], 523 | [4.939078543304397,52.31654978889029], 524 | [4.941203032293428,52.3162872219958], 525 | [4.938756213473856,52.31621452754107], 526 | [4.938896598847078,52.31605850140311], 527 | [4.939400306552536,52.31550986239724], 528 | [4.940879123535211,52.3168362773839], 529 | [4.9410241063818185,52.3164832988592], 530 | [4.941059992749947,52.316443950870855], 531 | [4.938718170741532,52.31625374083337], 532 | [4.938662447330706,52.31619644816048], 533 | [4.9398389704647,52.31681017581493], 534 | [4.940347008007609,52.31698417590958], 535 | [4.934542529562008,52.31675432949846], 536 | [4.935583247787395,52.31525991724099], 537 | [4.936118590121485,52.31502917603221], 538 | [4.935673158351167,52.314612240958155], 539 | [4.9351784982148486,52.31471631792431], 540 | [4.9349711710929185,52.316284496558644], 541 | [4.937678286329136,52.31703895744489], 542 | [4.936046963084194,52.31510732133828], 543 | [4.938278572372842,52.316758157626474], 544 | [4.938051720516152,52.31695832632734], 545 | [4.937022461126341,52.3177849572252], 546 | [4.935402447319653,52.31581282961245], 547 | [4.936480483003942,52.31463340303578], 548 | [4.9354727430296155,52.31573458973993], 549 | [4.935575359290916,52.31463934330576], 550 | [4.935950429832579,52.31781402000648], 551 | [4.937905175267154,52.31712104193042], 552 | [4.935116077161638,52.316126559781644], 553 | [4.935032837889286,52.31811207458629], 554 | [4.935691801879892,52.31786917093624], 555 | [4.934756873689009,52.31651939084498], 556 | [4.937546034200748,52.31718329297119], 557 | [4.936609238753853,52.31787325293907], 558 | [4.9362348816336645,52.31775480560898], 559 | [4.937980465060944,52.317037462841306], 560 | [4.936334303162508,52.31479353865774], 561 | [4.936370356879708,52.317727028406495], 562 | [4.935650154293218,52.31518747941766], 563 | [4.937841169732313,52.31718949245599], 564 | [4.93446895537873,52.316834470663224], 565 | [4.934829142288851,52.31644061963779], 566 | [4.934687334192997,52.31786233124591], 567 | [4.937587534710029,52.31671705666207], 568 | [4.937479362980566,52.317257125839554], 569 | [4.936264481213379,52.314870190016165], 570 | [4.935628048240783,52.318076895901136], 571 | [4.937410481262303,52.317333430893626], 572 | [4.935043215293855,52.316206083872736], 573 | [4.935718859000766,52.31511413166438], 574 | [4.9361907347337945,52.3149505832661], 575 | [4.935687580530307,52.31550063991977], 576 | [4.937707718708624,52.31733636714727], 577 | [4.950223636265603,52.31448165299706], 578 | [4.948958155008582,52.314063461965205], 579 | [4.950075475040661,52.31443238287068], 580 | [4.938933311330381,52.3160202806563], 581 | [4.946346945905174,52.316381289710705], 582 | [4.947187774333778,52.31664387022855], 583 | [4.945857997365752,52.31694444744544], 584 | [4.948071457120834,52.31694747122912], 585 | [4.945677338163019,52.31704224345323], 586 | [4.945440809516277,52.31630572871263], 587 | [4.944638695246538,52.31631753368351], 588 | [4.945162540326852,52.31620855600025], 589 | [4.9480424321037155,52.31623683469104], 590 | [4.946398314583582,52.31524504820076], 591 | [4.945722558156244,52.316401179108176], 592 | [4.946051788780675,52.316253623543204], 593 | [4.945728976468946,52.31708806711104], 594 | [4.9466286028025,52.315748188176144], 595 | [4.945752900248688,52.31695781561848], 596 | [4.9453453603950335,52.316272252287064], 597 | [4.948553468856329,52.31641265311188], 598 | [4.946327196774658,52.315220453264764], 599 | [4.9461046466787,52.315143910610416], 600 | [4.948980606784584,52.31653192203803], 601 | [4.947685855359167,52.31611467879845], 602 | [4.946877195771279,52.31646772262737], 603 | [4.947835499291558,52.31616558418951], 604 | [4.9462445080691335,52.31632043815721], 605 | [4.944235521012227,52.31675569798537], 606 | [4.944488004395616,52.31648093394572], 607 | [4.944386635730186,52.3165911581872], 608 | [4.948169045666761,52.31698148365925], 609 | [4.948453763856477,52.31637771639024], 610 | [4.94581748489015,52.31643414996236], 611 | [4.945481658325676,52.317253813316974], 612 | [4.946012412842568,52.31522991229266], 613 | [4.947006830780687,52.316581307259995], 614 | [4.944287317110067,52.31670165066147], 615 | [4.946001240376115,52.3166903579599], 616 | [4.947596725255772,52.316084229586195], 617 | [4.946154908420793,52.316288593002035], 618 | [4.9486532591375445,52.316446394690445], 619 | [4.946107721010749,52.31653460134132], 620 | [4.9462517094188545,52.315194314076244], 621 | [4.945942408062883,52.315308869506595], 622 | [4.945829688362506,52.316876906530325], 623 | [4.945456390343607,52.31738345754937], 624 | [4.946006238380635,52.316497184285545], 625 | [4.945637164204492,52.31718724613674], 626 | [4.9459782309131946,52.315269474854915], 627 | [4.9406997835827395,52.316836769072175], 628 | [4.939291846340491,52.315627128862815], 629 | [4.937504996999392,52.31754425709687], 630 | [4.946526928657983,52.31571395251902], 631 | [4.940853714374242,52.312866061200516], 632 | [4.947098597046883,52.3166121891447], 633 | [4.944303980778193,52.318647818683104], 634 | [4.947973577998658,52.31691467997683], 635 | [4.942698475559111,52.31938856484458], 636 | [4.945529790670604,52.31810378738557], 637 | [4.941890149410619,52.319319420011134], 638 | [4.943825450037633,52.317206660610445], 639 | [4.9461787595603095,52.315169622343824], 640 | [4.947271205666279,52.318698570538515], 641 | [4.946111892545989,52.31830228807101], 642 | [4.94387255864221,52.319118789097566], 643 | [4.950343736332419,52.31333831147424], 644 | [4.943758273364072,52.31975530645941], 645 | [4.94403109387468,52.31884205623712], 646 | [4.946880975615297,52.31948559829684], 647 | [4.941348138856363,52.319912863789675], 648 | [4.942900888878865,52.31826542163274], 649 | [4.948729328754728,52.313125026512324], 650 | [4.946790131878778,52.318708581640465], 651 | [4.946473295081856,52.31527055626204], 652 | [4.944902600982571,52.31788702286247], 653 | [4.946449029046157,52.318067246918886], 654 | [4.948244465608573,52.31630676342161], 655 | [4.943885120164799,52.317141120641], 656 | [4.9388236878849225,52.316460392451425], 657 | [4.938970382897814,52.31597989520255], 658 | [4.941132285663964,52.31636601179616], 659 | [4.940967567325914,52.31674054062192], 660 | [4.9409881819112655,52.316522053499206], 661 | [4.940015204254601,52.31698277836928], 662 | [4.941958625441778,52.31278650374077], 663 | [4.94542492705334,52.31269773058399], 664 | [4.940809236728837,52.31671839959434], 665 | [4.941221460164956,52.31646178925594], 666 | [4.9401451813859145,52.317027246820075], 667 | [4.935545432994035,52.315655954515584], 668 | [4.937655285186471,52.31739166747322], 669 | [4.93525956319823,52.31596971391005], 670 | [4.936468905925382,52.317902746550416], 671 | [4.935910701155178,52.31801883483832], 672 | [4.93632930984568,52.31793188329206], 673 | [4.934555242002402,52.317817021827466], 674 | [4.9373364718359305,52.3174106241747], 675 | [4.949313638333775,52.31418513370402], 676 | [4.944597139828523,52.31822188853814], 677 | [4.946587283955875,52.319626457785006], 678 | [4.94537507706313,52.31912862506154], 679 | [4.942898839190677,52.31929901727841], 680 | [4.941131211617078,52.3201498177078], 681 | [4.944028823819838,52.318771152153786], 682 | [4.941398667265577,52.31627175439608], 683 | [4.939220657958507,52.315707317957646], 684 | [4.937279037661166,52.317460027309444], 685 | [4.935833309386283,52.31534204928539], 686 | [4.936884693068652,52.31781613281585] 687 | ] 688 | export default treelocs -------------------------------------------------------------------------------- /windmill/SM_Base.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Geodan/mapbox-3dtiles/29685d38e94088ba1d1ca721b8cf17a7d9231033/windmill/SM_Base.glb -------------------------------------------------------------------------------- /windmill/SM_Nacelle.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Geodan/mapbox-3dtiles/29685d38e94088ba1d1ca721b8cf17a7d9231033/windmill/SM_Nacelle.glb -------------------------------------------------------------------------------- /windmill/SM_Pillar.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Geodan/mapbox-3dtiles/29685d38e94088ba1d1ca721b8cf17a7d9231033/windmill/SM_Pillar.glb -------------------------------------------------------------------------------- /windmill/SM_Rotor.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Geodan/mapbox-3dtiles/29685d38e94088ba1d1ca721b8cf17a7d9231033/windmill/SM_Rotor.glb --------------------------------------------------------------------------------