1+ import React , { useState , useEffect } from 'react' ;
2+ import { useThree , useFrame } from '@react-three/fiber' ;
3+ import * as THREE from 'three' ;
4+ import { TilesRenderer as TilesRendererImpl , GlobeControls as GlobeControlsImpl } from '3d-tiles-renderer' ;
5+ import {
6+ GLTFExtensionsPlugin ,
7+ GoogleCloudAuthPlugin ,
8+ TileCompressionPlugin ,
9+ TilesFadePlugin ,
10+ UpdateOnChangePlugin
11+ } from '3d-tiles-renderer/plugins' ;
12+ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' ;
13+ import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader' ;
14+ import { Geodetic , PointOfView , radians } from '@takram/three-geospatial' ;
15+
16+ const API_KEY = import . meta. env . VITE_GOOGLE_MAPS_API_KEY ;
17+
18+ const dracoLoader = new DRACOLoader ( ) ;
19+ dracoLoader . setDecoderPath ( 'https://www.gstatic.com/draco/versioned/decoders/1.5.6/' ) ;
20+
21+ const Globe = ( { onMapLoaded } ) => {
22+ const [ tiles , setTiles ] = useState ( null ) ;
23+ const [ controls , setControls ] = useState ( null ) ;
24+ const { scene, camera, gl } = useThree ( ) ;
25+
26+ const setInitialCameraPosition = ( camera ) => {
27+ const longitude = - 74.0 ;
28+ const latitude = 40.75 ;
29+ const altitude = 1500 ;
30+ const heading = 0 ;
31+ const pitch = - 45 ;
32+
33+ const pov = new PointOfView ( altitude , radians ( heading ) , radians ( pitch ) ) ;
34+ const geodetic = new Geodetic ( radians ( longitude ) , radians ( latitude ) ) ;
35+
36+ pov . decompose (
37+ geodetic . toECEF ( ) ,
38+ camera . position ,
39+ camera . quaternion ,
40+ camera . up
41+ ) ;
42+
43+ camera . updateMatrix ( ) ;
44+ camera . updateMatrixWorld ( true ) ;
45+ } ;
46+
47+ useEffect ( ( ) => {
48+ if ( ! API_KEY ) return ;
49+
50+ const tilesUrl = `https://tile.googleapis.com/v1/3dtiles/root.json?key=${ API_KEY } ` ;
51+ const tilesRenderer = new TilesRendererImpl ( tilesUrl ) ;
52+
53+ tilesRenderer . setCamera ( camera ) ;
54+ tilesRenderer . setResolutionFromRenderer ( camera , gl ) ;
55+
56+ setInitialCameraPosition ( camera ) ;
57+
58+ const globeControls = new GlobeControlsImpl ( scene , camera , gl . domElement , tilesRenderer ) ;
59+ globeControls . enableDamping = true ;
60+ globeControls . adjustHeight = false ;
61+ globeControls . maxAltitude = Math . PI * 0.55 ;
62+
63+ const enableAdjustHeight = ( ) => {
64+ globeControls . adjustHeight = true ;
65+ globeControls . removeEventListener ( 'start' , enableAdjustHeight ) ;
66+ } ;
67+ globeControls . addEventListener ( 'start' , enableAdjustHeight ) ;
68+
69+ const gltfLoader = new GLTFLoader ( tilesRenderer . manager ) ;
70+ gltfLoader . setDRACOLoader ( dracoLoader ) ;
71+ tilesRenderer . manager . addHandler ( / \. g l t f $ / i, gltfLoader ) ;
72+ tilesRenderer . manager . addHandler ( / \. g l b $ / i, gltfLoader ) ;
73+
74+ try {
75+ const authPlugin = new GoogleCloudAuthPlugin ( {
76+ apiToken : API_KEY ,
77+ autoRefreshToken : true
78+ } ) ;
79+ tilesRenderer . registerPlugin ( authPlugin ) ;
80+ tilesRenderer . registerPlugin ( new GLTFExtensionsPlugin ( ) ) ;
81+ tilesRenderer . registerPlugin ( new TileCompressionPlugin ( ) ) ;
82+ tilesRenderer . registerPlugin ( new UpdateOnChangePlugin ( ) ) ;
83+ tilesRenderer . registerPlugin ( new TilesFadePlugin ( ) ) ;
84+ } catch ( error ) {
85+ console . warn ( 'Could not register plugins:' , error ) ;
86+ }
87+
88+ scene . add ( tilesRenderer . group ) ;
89+
90+ setTiles ( tilesRenderer ) ;
91+ setControls ( globeControls ) ;
92+
93+ if ( onMapLoaded ) {
94+ const ecefToLatLng = ( x , y , z ) => {
95+ const a = 6378137.0 ;
96+ const f = 1 / 298.257223563 ;
97+ const e2 = 2 * f - f * f ;
98+
99+ const p = Math . sqrt ( x * x + y * y ) ;
100+ const theta = Math . atan2 ( z * a , p * ( 1 - f ) * a ) ;
101+
102+ const lat = Math . atan2 (
103+ z + ( e2 * ( 1 - f ) / ( 1 - e2 ) ) * a * Math . pow ( Math . sin ( theta ) , 3 ) ,
104+ p - e2 * a * Math . pow ( Math . cos ( theta ) , 3 )
105+ ) ;
106+
107+ const lng = Math . atan2 ( y , x ) ;
108+ const N = a / Math . sqrt ( 1 - e2 * Math . sin ( lat ) * Math . sin ( lat ) ) ;
109+ const alt = p / Math . cos ( lat ) - N ;
110+
111+ return {
112+ lat : lat * 180 / Math . PI ,
113+ lng : lng * 180 / Math . PI ,
114+ alt : alt
115+ } ;
116+ } ;
117+ const latLngToECEF = ( lat , lng , alt = 0 ) => {
118+ const a = 6378137.0 ;
119+ const f = 1 / 298.257223563 ;
120+ const e2 = 2 * f - f * f ;
121+
122+ const latRad = lat * Math . PI / 180 ;
123+ const lngRad = lng * Math . PI / 180 ;
124+
125+ const N = a / Math . sqrt ( 1 - e2 * Math . sin ( latRad ) * Math . sin ( latRad ) ) ;
126+
127+ const x = ( N + alt ) * Math . cos ( latRad ) * Math . cos ( lngRad ) ;
128+ const y = ( N + alt ) * Math . cos ( latRad ) * Math . sin ( lngRad ) ;
129+ const z = ( N * ( 1 - e2 ) + alt ) * Math . sin ( latRad ) ;
130+
131+ return new THREE . Vector3 ( x , y , z ) ;
132+ } ;
133+ onMapLoaded ( { latLngToECEF, ecefToLatLng } ) ;
134+ }
135+
136+ return ( ) => {
137+ scene . remove ( tilesRenderer . group ) ;
138+ tilesRenderer . dispose ( ) ;
139+ globeControls . dispose ( ) ;
140+ } ;
141+ } , [ API_KEY , scene , camera , gl ] ) ;
142+
143+ useFrame ( ( ) => {
144+ if ( tiles ) {
145+ tiles . update ( ) ;
146+ }
147+ if ( controls ) {
148+ controls . update ( ) ;
149+ }
150+ } ) ;
151+
152+ return null ;
153+ } ;
154+
155+ const Map = ( { onMapLoaded } ) => {
156+ return < Globe onMapLoaded = { onMapLoaded } /> ;
157+ }
158+
159+ export default Map ;
0 commit comments