Utilizing Google Maps with WebGL to place multiple GLTF objects on a vector map based on geographical coordinates

Trying to incorporate 3D objects onto a Google Map overlay view using Three.js

The main goal is to display bus stations using 3D objects with specific longitudes and latitudes

Attempted to use the '@googlemaps/three' 'latLngAltitudeToVector3' method as directed, but the object does not render at the precise lng lat coordinates desired

Seeking guidance as I have been working on this for hours

import { Loader } from '@googlemaps/js-api-loader';
import { ThreeJSOverlayView, latLngToVector3 } from "@googlemaps/three";
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';

var coords = [
  { lng: 126.9108135, lat: 37.61399168 },
  { lng: 126.9126067, lat: 37.61336021 },
  { lng: 126.9143072, lat: 37.61274979 },
  { lng: 126.9178395, lat: 37.61086107 },
  { lng: 126.9201833, lat: 37.60934572 },
  { lng: 126.922542, lat: 37.60665167 },
];

var stops = [];

const apiOptions = {
  apiKey: 'API_KEY',
  version: "beta"
};

const mapOptions = {
  "tilt": 0,
  "heading": 0,
  "zoom": 18,
  "center": { lat: 37.5234, lng: 126.9234 },
  "mapId": "MAP_ID"
}

async function initMap() {    
  const mapDiv = document.getElementById("map");
  const apiLoader = new Loader(apiOptions);
  await apiLoader.load();
  return new google.maps.Map(mapDiv, mapOptions);
}


function initWebGLOverlayView(map) {  
  let scene, renderer, camera, loader;
  const webGLOverlayView = new google.maps.WebGLOverlayView();
  const overlay = new ThreeJSOverlayView();
  
  webGLOverlayView.onAdd = () => {
    // set up the scene
    scene = new THREE.Scene();
    camera = new THREE.PerspectiveCamera();
    const ambientLight = new THREE.AmbientLight( 0xffffff, 0.75 ); // soft white light
    scene.add(ambientLight);
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.25);
    directionalLight.position.set(0.5, -1, 0.5);
    scene.add(directionalLight);
  
    // load the model    
    loader = new GLTFLoader();     
    const source = "original.glb";

    loader.load(
      source,
      gltf => {
        for (const [i, coord] of coords.entries()) {
          gltf.scene.scale.set(8,8,8);
          gltf.scene.rotation.x = 90 * Math.PI/180;
          stops.push(gltf.scene.clone());
        }
        
        for (const [i, coord] of coords.entries()) {
          stops[i].rotation.x = 90 * Math.PI/180;
          stops[i].position.copy(overlay.latLngAltitudeToVector3({lat: coord.lat, lng: coord.lng}))
          scene.add(stops[i]);
        }
      });
  }
  

  webGLOverlayView.onContextRestored = ({gl}) => {
    // create the three.js renderer, using the
    // maps's WebGL rendering context.
    renderer = new THREE.WebGLRenderer({
      canvas: gl.canvas,
      context: gl,
      ...gl.getContextAttributes(),
    });
    renderer.autoClear = false;

    // wait to move the camera until the 3D model loads    
    loader.manager.onLoad = () => {        
      renderer.setAnimationLoop(() => {
        for (const stop of stops) {
          stop.rotation.y += 0.02
        }
      });
    }
  }

  webGLOverlayView.onDraw = ({gl, transformer}) => {
    // update camera matrix to ensure the model is georeferenced correctly on the map
    const latLngAltitudeLiteral = {
        lat: mapOptions.center.lat,
        lng: mapOptions.center.lng,
        altitude: 50
    }

    const matrix = transformer.fromLatLngAltitude(latLngAltitudeLiteral);
    camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);
    
    webGLOverlayView.requestRedraw();
    renderer.render(scene, camera);

    // always reset the GL state
    renderer.resetState();
  }

  webGLOverlayView.setMap(map);
}

(async () => {        
  const map = await initMap();
  initWebGLOverlayView(map);
})();

Using Vanilla js Started project by https://github.com/googlecodelabs/maps-platform-101-webgl/ instructions

Thanks a lot for the help

Answer №1

I stumbled upon the solution

The key is to establish the Anchor point (where x, y, and z are all 0) before initiating any other tasks in the ThreeJSOverlayView

I'll leave the code here for those who may encounter a similar issue

import { Loader } from '@googlemaps/js-api-loader';
import { ThreeJSOverlayView } from "@googlemaps/three";
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';

var coords = [
  { lng: 126.9108135, lat: 37.61399168 },
  { lng: 126.9126067, lat: 37.61336021 },
  { lng: 126.9143072, lat: 37.61274979 },
  { lng: 126.9178395, lat: 37.61086107 },
  { lng: 126.9201833, lat: 37.60934572 },
];
var stops = [];

const apiOptions = {
  apiKey: 'API_KEY',
  version: "beta"
};

const mapOptions = {
  "tilt": 0,
  "heading": 0,
  "zoom": 18,
  "center": { lat: 37.5234, lng: 126.9234 },
  "mapId": "MAP_ID"
}

async function initializeMap() {
  const mapDiv = document.getElementById("map");
  const apiLoader = new Loader(apiOptions);
  await apiLoader.load();
  return new google.maps.Map(mapDiv, mapOptions);
}


function initializeWebGLOverlayView(map) {
  let scene, renderer, camera, loader;

  const webGLOverlayView = new google.maps.WebGLOverlayView();

  const overlay = new ThreeJSOverlayView();
  overlay.setAnchor(mapOptions.center);

  webGLOverlayView.onAdd = () => {
    scene = new THREE.Scene();
    camera = new THREE.PerspectiveCamera();

    const ambientLight = new THREE.AmbientLight(0xffffff, 0.75);
    scene.add(ambientLight);

    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.25);
    directionalLight.position.set(0.5, -1, 0.5);
    scene.add(directionalLight);

    loader = new GLTFLoader();
    const source = "original.glb";
    loader.load(
      source,
      gltf => {
        for (const _ of coords) {
          gltf.scene.scale.set(8,8,8);
          gltf.scene.rotation.x = 90 * Math.PI / 180;
          stops.push(gltf.scene.clone());
        }

        for (const [i, coord] of coords.entries()) {
          stops[i].position.copy(overlay.latLngAltitudeToVector3({ lat: coord.lat, lng: coord.lng }));
          scene.add(stops[i]);
        }
      }
    );
  }


  webGLOverlayView.onContextRestored = ({ gl }) => {
    // create the three.js renderer, using the
    // maps's WebGL rendering context.
    renderer = new THREE.WebGLRenderer({
      canvas: gl.canvas,
      context: gl,
      ...gl.getContextAttributes(),
    });
    renderer.autoClear = false;

    // wait to move the camera until the 3D model loads    
    loader.manager.onLoad = () => {
      renderer.setAnimationLoop(() => {
        for (const stop of stops) {
          stop.rotation.y += 0.02
        }
      });
    }
  }

  webGLOverlayView.onDraw = ({ gl, transformer }) => {
    // update camera matrix to ensure the model is georeferenced correctly on the map
    const latLngAltitudeLiteral = {
      lat: mapOptions.center.lat,
      lng: mapOptions.center.lng,
      altitude: 50
    }

    const matrix = transformer.fromLatLngAltitude(latLngAltitudeLiteral);
    camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);

    webGLOverlayView.requestRedraw();
    renderer.render(scene, camera);

    // always reset the GL state
    renderer.resetState();
  }

  webGLOverlayView.setMap(map);
}

(async () => {
  const map = await initializeMap();
  initializeWebGLOverlayView(map);
})();

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best method to extract text data from the Froala editor?

Currently, my method involves using $('div#edit').froalaEditor('html.get') to get the HTML data from the editor. Unfortunately, this process becomes problematic when trying to process or store the text data in my backend due to the pres ...

Implement the Vue.js @click directive in a location outside of an HTML element

I've set up a link like this: <a href="#" @click="modal=true">Open modal</a> Here's the data setup: export default { data () { return { modal: false } } } Is there a way to trigger the cli ...

Export nested objects from an AngularJS JSON array to a CSV file

When I download my data into a CSV file, the display setting is showing as "[object Object]". This is not the desired outcome. https://i.stack.imgur.com/ej2UO.png The expected display should look like this: https://i.stack.imgur.com/8JJ88.png This is p ...

Foundation and equalizer do not provide an optimal user experience

I am new to using Foundation. Currently, I am utilizing Foundation 5 along with equalizer. I have a row with 2 columns - one containing text and the other containing an image. I want both columns to have the same height, so I am using data-equalizer. ...

Using the MongoDB aggregate framework to determine the total employee count per unique state

I'm currently working on displaying the total number of employees for each state within companies located in the USA. I aim to showcase this information for all states included in the dataset using sample numbers as a reference: AZ : 1234 CA : 30000 ...

Is it possible for me to traverse a CSS stylesheet using code?

Exploring the depths of jQuery, one can effortlessly traverse the DOM. But what if we could also navigate through a stylesheet, accessing and modifying attributes for the specified styles? Sample Stylesheet div { background: #FF0000; display: blo ...

The NGRX state in Angular is not being properly saved by the local storage

Currently, I am utilizing NGRX for state management within my Angular application. While NGRX is functioning correctly, I have encountered an issue with using local storage to persist the NGRX state. Upon refreshing the browser, the NGRX data reverts back ...

Creating a hierarchical tree in JavaScript and generating nested UL/LI output with ExpressJS

I have a piece of code from another request that is functioning properly. It creates a hierarchical tree with deep levels using JSON. However, I require the output to be in the form of an HTML UL/LI nested structure or a select menu for parent and child el ...

Is it possible to have the soft keyboard automatically appear when the page loads?

On an HTML5 website, I have an input element and I want the soft keyboard to automatically appear and focus on that input element. I attempted to use the 'autofocus' attribute on the 'input' element, but this only focused on the element ...

Combining arrays using JavaScript

I'm struggling to enhance the following code - it looks a bit messy: Here is my data format: date d1 d2 d3 d4 d5 d6 110522 5 1 3 5 0 7 110523 9 2 4 6 5 9 110524 0 0 0 0 1 0 110525 0 0 3 0 4 0 ... I am importing data from a text file using d3.j ...

Tips for positioning a canvas and a div element next to each other on a webpage

I have a canvas and a div element that I need to split in a 60% to 40% ratio. However, no matter what changes I make to the display settings, the div is always displayed before the canvas. The Div element contains buttons with color-changing properties fo ...

Discovering the total number of tickets based on priority in an array with Javascript

I have the following data set { agent_id:001, priority:"High", task_id:T1 }, { agent_id:001, priority:"High", task_id:T1 }, { agent_id:001, priority:"Medium", task_id:T1 } { agent_id:002, priority:"High", task_id:T1 ...

Leverage jQuery deferred objects to handle a dynamic amount of AJAX requests

When faced with multiple ajax requests, how can I execute them using deferreds? This is my approach: //qty_of_gets = 3; function getHTML(productID, qty_of_gets){ var dfd = $.Deferred(), i = 0, c = 0; // hypothetical cod ...

Tips on invoking a factory method from a different service method in AngularJS

I have a factory method that goes like this.. (function (angular) { "use strict"; angular .module('app') .factory('UserService', ['$rootScope', '$q', function ($rootScope, $q) { ...

Please ensure that the menu is included within the HTML file

How can I include a menu from menu.html into index.html? The content of menu.html is: <li><a href="home.html">Home</a></li> <li><a href="news.html">News</a></li> In index.html, the code is: <!docty ...

Exploring the equality of objects in NodeJS

Currently, we are in the process of creating tests for a program. Our goal is to develop a functional test that validates whether the output of the program aligns with certain expectations. The data returned from the program consists of a complex JavaScrip ...

Analyzing similarities between objects and arrays to find and return the matches

Items {670: true, 671: true} List 0: {id: 669, item_id: 35} 1: {id: 670, item_id: 35} Desired outcome 0: {id: 670, item_id: 35} Is there a way to compare two datasets and return the matching entries based on their ids? ...

struggling to determine the connection status between tables (Many-to-many or one-to-one)

Seeking assistance: I am working with two tables (member, event) where each member attends multiple events and each event has multiple attendees. Do these relationships represent a many-to-many or one-to-one relationship? ...

Should I return X in async functions, or should I return "Promise.Resolve(X)"?

I've always found this to be a tricky concept to fully grasp. Let's delve into async functions in Typescript. Which implementation is accurate? async function asyncFunctionOne(string1: string, string2: string, string3: string) { var returnOb ...

Adjusting the dimensions of a tri-fiber canvas prior to saving it

I have a unique inquiry regarding Three Fiber. Once the download button is clicked, it generates a base64 using toDataURL, which can then be downloaded. The resulting image adopts the height and width of the canvas, which in turn matches the height and w ...