Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update loader to support google maps 3d tiles #39

Merged
merged 15 commits into from
Mar 14, 2024
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,20 @@ require('aframe-loader-3dtiles-component');
| wireframe | boolean | (Optional) When viewing b3dm (mesh) tiles, show meshes as wireframe. Can be updated at runtime. | false |
| showStats | boolean | (Optional) Attaches a box with tilestats to the scene, useful for debugging. | false |
| cesiumIONToken | string | (Optional) A Cesium ION access token when loading tilesets from Cesium ION. | '' |
| googleApiKey | string | (Optional) Google API Key for loading Google Maps 3D Tiles. | '' |
| lat | int | (Optional) latitude coordinate for google 3D Tiles API. | '' |
| long | int | (Optional) longitude coordinate for google 3D Tiles API. | '' |
| height | int | (Optional) camera height for google 3D Tiles API. | '' |
| geoTransform | string | (Optional) How to handle geo transformations: Reset any geo location and place the model at (0,0,0), Apply Mercator projection (for use with ccommon 2D mapping applications, or convert WGS84 long/lat to 3D cartesian coordinates). Possible values: 'Reset', 'Mercator', 'WGS84Cartesian' | 'Reset' |

### Using Google 3D Tiles API
To use with the Google Maps 3D Map Tiles API, you need to specify `googleApiKey`, `geoTransform`: 'Mercator' or 'WGS84Cartesian' (see geoTransform property). To set coordinates use: `lat`, `long`, `height`.

To get a 'Google API key', go to the Google Cloud Console and follow the instructions to [create an Maps JavaScript API key](https://developers.google.com/maps/documentation/javascript/cloud-setup). Then you need to enable the Google Map Tiles API option on the APIs & Services page.

Please note there are costs from Google to provide this service to users of your application. [Pricing starts at $6 per 1,000 loads](https://developers.google.com/maps/documentation/tile/usage-and-billing#p3dt).

To prevent huge surprise bills, Google 3D Tiles has a [low default quota cap of 300 root JSON load events per day](https://developers.google.com/maps/documentation/tile/usage-and-billing#photorealistic-3d-tiles) which effectively means a limit of roughly 300 unique users per day. This cap is ok for development but you may wish to request a quota increase to support more users in a production application. The fastest route to approval is to [directly contact the Maps support team](https://developers.google.com/maps/support#contact-maps-support), which is separate from GCP billing or other teams. You will get a quick response but it may take up to 2 business days to lift the quota. Expect to provide additional information about your product, use case and target audience. Caching 3d tiles responses is not permitted by the terms of use.

### Camera Requirement
To use the `loader-3dtiles` component, your A-Frame scene must have a camera defined using [`<a-camera>`](https://aframe.io/docs/1.2.0/primitives/a-camera.html) or the [`camera` component](https://aframe.io/docs/1.2.0/components/camera.html) . The component will attempt to use one of these automatically, or you can specify your own selector with the `cameraEl` property.
Expand Down
8 changes: 4 additions & 4 deletions dist/aframe-loader-3dtiles-component.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/aframe-loader-3dtiles-component.min.js

Large diffs are not rendered by default.

33 changes: 19 additions & 14 deletions dist/aframe-loader-3dtiles-component.min.js.LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/**
* @license
* Copyright 2009 The Closure Library Authors
* Copyright 2020 Daniel Wirtz / The long.js Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
12 changes: 7 additions & 5 deletions examples/basic/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
<meta charset="utf-8">
<title>A-Frame 3D Tiles Component - Basic example</title>
<meta name="description" content="Basic example for 3D Tiles component showing photogrammetry."></meta>
<script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>
<script src='https://cdn.jsdelivr.net/npm/three@0.137.0/examples/js/utils/WorkerPool.js'></script>
<script src='https://cdn.jsdelivr.net/npm/three@0.137.0/examples/js/loaders/KTX2Loader.js'></script>
<script src="https://aframe.io/releases/1.5.0/aframe.min.js"></script>
<script src="../../dist/aframe-loader-3dtiles-component.js"></script>
<style>
#guide {
Expand All @@ -30,11 +28,15 @@
<a-scene vr-mode-ui="enabled: false" renderer="colorManagement: true;">
<a-camera id="camera"></a-camera>
<a-entity
id="tileset"
id="freeman-tiles"
position="-1.5 10 -28"
rotation="-90 0 90"
scale="2 2 2"
loader-3dtiles="url: https://int.nyt.com/data/3dscenes/ONA360/TILESET/0731_FREEMAN_ALLEY_10M_A_36x8K__10K-PN_50P_DB/tileset_tileset.json; maximumSSE: 48; cameraEl: #camera"
loader-3dtiles="
url: https://int.nyt.com/data/3dscenes/ONA360/TILESET/0731_FREEMAN_ALLEY_10M_A_36x8K__10K-PN_50P_DB/tileset_tileset.json;
maximumSSE: 48;
cameraEl: #camera;
"
>
</a-entity>
</a-scene>
Expand Down
84 changes: 84 additions & 0 deletions examples/google-tiles/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>A-Frame 3D Tiles Component - Basic example</title>
<meta name="description" content="Basic example for 3D Tiles component showing google 3d Tiles."></meta>
<script src="https://aframe.io/releases/1.5.0/aframe.min.js"></script>
<script src="../../dist/aframe-loader-3dtiles-component.js"></script>
<!-- <script src="https://unpkg.com/3dstreet@0.4.5/dist/aframe-street-component.js"></script> -->

<!-- vr teleport controls -->
<!-- <script src="https://cdn.jsdelivr.net/npm/aframe-blink-controls@0.4.3/dist/aframe-blink-controls.min.js"></script> -->
<style>
#guide {
position: fixed;
top: 0;
right: 0;
width: 300px;
padding: 1rem 2rem;
font-family:'Courier New', Courier, monospace;
line-height: 1.2;
background-color: white;
color: black;
}

#guide p {
margin-top: 10px;
}
</style>
</head>
<body>
<a-scene vr-mode-ui="enabled: false" renderer="colorManagement: true;">
<a-entity camera="fov:45; near:1; far: 1000" look-controls="reverseMouseDrag: true" wasd-controls="enabled: true" id="camera" cursor-teleport="cameraRig: #camera; cameraHead: #camera;"></a-entity>
<a-entity
id="tileset"
loader-3dtiles="
url: https://tile.googleapis.com/v1/3dtiles/root.json;
lat: 37.77522354250163;
long: -122.41931773049723;
height: -16.5;
googleApiKey: INSERTYOURKEYHERE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example demo states that this and also lat/long/height are available parameters but there is no option to insert them via the query string.

Copy link
Contributor Author

@kfarr kfarr Mar 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but there is no option to insert them via the query string.

Correct, the expected behavior in this example is only to place tiles with a long / lat / height specified in the index.html centered at scene origin 0 0 0. Supporting a dynamic, user-specified querystring with lat / long / height was not intended to be part of this example.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it.
It's maybe OK since the example is anyway not exposed in the demo showcase, but if it works well we should probably consider adding it in the future and also make the example usable without editing the code. Maybe after we have occlusion culling and teleport controls?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

geoTransform: WGS84Cartesian;
maximumSSE: 48;
maximumMem: 400;
cameraEl: #camera"
>
</a-entity>
</a-scene>

<!-- GitHub Corner. -->
<a href="https://github.com/nytimes/aframe-loader-3dtiles-component" class="github-corner">
<svg width="80" height="80" viewBox="0 0 250 250" style="fill:#222; color:#fff; position: absolute; top: 0; border: 0; right: 0;">
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path>
</svg>
</a>
<div id="guide">
<span id="example-desc">
<b>Google 3D Tiles example.</b>
</span>
<p>Use arrow/WASD keys to move around and click and drag to turn/rotate the camera.</p>
<p>
<u>Available component parameters:</u>
<ul>
<li><b>lat, long</b>: coordinates of Google Map.</li>
<li><b>height</b>: camera height.</li>
<li><b>googleApiKey</b>: Google Api Key.</li>
</ul>
</p>
</div>
<script>
const queryParams = new URLSearchParams(document.location.search);

if (queryParams.get('tilesetUrl')) {
document.querySelector('#tileset').addEventListener('object3dset', (e) => {
e.target.setAttribute('loader-3dtiles', {
url: queryParams.get('tilesetUrl')
})
})
}
</script>
<style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
<!-- End GitHub Corner. -->
</body>
</html>
4 changes: 1 addition & 3 deletions examples/raysmith/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
<meta charset="utf-8">
<title>A-Frame 3D Tiles Component - Ray Smith example</title>
<meta name="description" content="WebXR example for 3D Tiles component showing photogrammetry."></meta>
<script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>
<script src='https://cdn.jsdelivr.net/npm/three@0.137.0/examples/js/utils/WorkerPool.js'></script>
<script src='https://cdn.jsdelivr.net/npm/three@0.137.0/examples/js/loaders/KTX2Loader.js'></script>
<script src="https://aframe.io/releases/1.5.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/aframe-blink-controls/dist/aframe-blink-controls.min.js"></script>
<script src="../../dist/aframe-loader-3dtiles-component.js"></script>
</head>
Expand Down
4 changes: 1 addition & 3 deletions examples/stats/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
<meta charset="utf-8">
<title>A-Frame 3D Tiles Component - Stats example</title>
<meta name="description" content="Stats example for 3D Tiles component showing photogrammetry."></meta>
<script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>
<script src='https://cdn.jsdelivr.net/npm/three@0.137.0/examples/js/utils/WorkerPool.js'></script>
<script src='https://cdn.jsdelivr.net/npm/three@0.137.0/examples/js/loaders/KTX2Loader.js'></script>
<script src="https://aframe.io/releases/1.5.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/aframe-blink-controls/dist/aframe-blink-controls.min.js"></script>
<script src="../../dist/aframe-loader-3dtiles-component.js"></script>
</head>
Expand Down
4 changes: 1 addition & 3 deletions examples/webxr/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
<meta charset="utf-8">
<title>A-Frame 3D Tiles Component - WebXR example</title>
<meta name="description" content="WebXR example for 3D Tiles component showing photogrammetry."></meta>
<script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>
<script src='https://cdn.jsdelivr.net/npm/three@0.137.0/examples/js/utils/WorkerPool.js'></script>
<script src='https://cdn.jsdelivr.net/npm/three@0.137.0/examples/js/loaders/KTX2Loader.js'></script>
<script src="https://aframe.io/releases/1.5.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/aframe-blink-controls/dist/aframe-blink-controls.min.js"></script>
<script src="../../dist/aframe-loader-3dtiles-component.js"></script>
</head>
Expand Down
45 changes: 36 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Loader3DTiles, PointCloudColoring } from 'three-loader-3dtiles';
import { Vector3 } from 'three';
import { Loader3DTiles, PointCloudColoring, GeoTransform } from 'three-loader-3dtiles';
import './textarea';
import { Vector3 } from 'three';

if (typeof AFRAME === 'undefined') {
throw new Error('Component attempted to register before AFRAME was available.');
Expand Down Expand Up @@ -28,24 +28,33 @@ AFRAME.registerComponent('loader-3dtiles', {
pointcloudElevationRange: { type: 'array', default: ['0', '400'] },
wireframe: { type: 'boolean', default: false },
showStats: { type: 'boolean', default: false },
cesiumIONToken: { type: 'string' }
cesiumIONToken: { type: 'string' },
googleApiKey: { type: 'string' },
lat: { type: 'number' },
long: { type: 'number' },
height: { type: 'number' },
geoTransform: { type: 'string', default: 'Reset' }
},
init: async function () {
this.camera = this.data.cameraEl?.object3D.children[0] ?? document.querySelector('a-scene').camera;
if (!this.camera) {
throw new Error('3D Tiles: Please add an active camera or specify the target camera via the cameraEl property');
}
const { model, runtime } = await this._initTileset();

this.el.setObject3D('tileset', model);

this.originalCamera = this.camera;
this.el.sceneEl.renderer.preserveDrawingBuffer = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll check with Alex, but I'm pretty sure this is not needed and was only included to improve generation of screenshots for our use case. We can remove this and add elsewhere in our application instead.

Copy link
Contributor Author

@kfarr kfarr Mar 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok this has been removed. Just to make sure I tested with and without and didn't notice any effect.


this.el.sceneEl.addEventListener('camera-set-active', (e) => {
// TODO: For some reason after closing the inspector this event is fired with an empty camera,
// so revert to the original camera used.
//
// TODO: Does not provide the right Inspector perspective camera
this.camera = e.detail.cameraEl.object3D.children[0] ?? this.originalCamera;
});

this.el.sceneEl.addEventListener('enter-vr', (e) => {
this.originalCamera = this.camera;
try {
Expand Down Expand Up @@ -86,7 +95,9 @@ AFRAME.registerComponent('loader-3dtiles', {
this.runtime = null;
}
const { model, runtime } = await this._initTileset();

this.el.setObject3D('tileset', model);

await this._nextFrame();
this.runtime = runtime;
} else if (this.runtime) {
Expand All @@ -103,10 +114,24 @@ AFRAME.registerComponent('loader-3dtiles', {
this.el.sceneEl.removeChild(this.stats);
this.stats = null;
}

// set parameters for google 3dtiles API
if (this.data.lat && this.data.long && this.data.height) {
// eslint-disable-next-line no-unused-vars
const { model, runtime } = await this._initTileset();

console.log(this.data.lat, this.data.long, this.data.height);

this.runtime.orientToGeocoord({
lat: Number(this.data.lat),
long: Number(this.data.long),
height: Number(this.data.height)
});
}
},
tick: function (t, dt) {
if (this.runtime) {
this.runtime.update(dt, this.el.sceneEl.renderer, this.camera);
this.runtime.update(dt, this.el.sceneEl.clientHeight, this.camera);
if (this.stats) {
const worldPos = new Vector3();
this.camera.getWorldPosition(worldPos);
Expand Down Expand Up @@ -139,20 +164,22 @@ AFRAME.registerComponent('loader-3dtiles', {
},
_initTileset: async function () {
const pointCloudColoring = this._resolvePointcloudColoring(this.data.pointcloudColoring);

return Loader3DTiles.load({
url: this.data.url,
renderer: this.el.sceneEl.renderer,
options: {
dracoDecoderPath: 'https://unpkg.com/three@0.137.0/examples/js/libs/draco',
basisTranscoderPath: 'https://unpkg.com/three@0.137.0/examples/js/libs/basis',
googleApiKey: this.data.googleApiKey,
cesiumIONToken: this.data.cesiumIONToken,
dracoDecoderPath: 'https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco',
basisTranscoderPath: 'https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/basis',
maximumScreenSpaceError: this.data.maximumSSE,
maximumMemoryUsage: this.data.maximumMem,
memoryCacheOverflow: 128,
pointCloudColoring: pointCloudColoring,
viewDistanceScale: this.data.distanceScale,
wireframe: this.data.wireframe,
pointCloudColoring: pointCloudColoring,
updateTransforms: true
updateTransforms: true,
geoTransform: GeoTransform[this.data.geoTransform]
}
});
},
Expand Down
Loading