In this post we will see how to set up and render 3D object in Google maps using Javascript (Vanilla Javascript). Google Map 3D WebGL Three.js Vanilla Javascript

1: Create Google Map Id from google developer console
- In the Google Cloud console, go to Google Maps Platform => Map Management.
- Click CREATE NEW MAP ID
- In the Map name field, input a name for your Map ID.
- In the Map type dropdown, select JavaScript. JavaScript Options will appear.
- Under JavaScript Options, select the Vector radio button, the Tilt checkbox, and the Rotation checkbox.
- Optional. In the Description field, enter a description for your API key.
- Click the Next button. The Map ID Details page will appear.

2: Create Google Map Api Key

3: Implementation
For implementing Google Map 3D WebGL, we need Google Map and Three.js to render 3D objects in the map
In your script tag include the libraries that are mentione below
<script src="https://unpkg.com/three@0.124.0/build/three.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/googlemaps-js-api-loader/1.16.1/index.min.js"></script>
<script src="https://unpkg.com/three@0.126.0/examples/js/loaders/GLTFLoader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.115/examples/js/lines/LineSegments2.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.115/examples/js/lines/Line2.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.115/examples/js/lines/LineMaterial.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.115/examples/js/lines/LineSegmentsGeometry.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.115/examples/js/lines/LineGeometry.js"></script>Now create a div to load Google map and keep the id.
<div id="map"></div>If your Google map is not showing up in the screen and if there are no script errors just add below css in the page
#map {
position: initial !important;
overflow: hidden !important;
width: 500px;
height: 500px;
}Now we will add the Google map script to load Google Api key
<script>
(g => { var h, a, k, p = "The Google Maps JavaScript API", c = "google", l = "importLibrary", q = "__ib__", m = document, b = window; b = b[c] || (b[c] = {}); var d = b.maps || (b.maps = {}), r = new Set, e = new URLSearchParams, u = () => h || (h = new Promise(async (f, n) => { await (a = m.createElement("script")); e.set("libraries", [...r] + ""); for (k in g) e.set(k.replace(/[A-Z]/g, t => "_" + t[0].toLowerCase()), g[k]); e.set("callback", c + ".maps." + q); a.src = `https://maps.${c}apis.com/maps/api/js?` + e; d[q] = f; a.onerror = () => h = n(Error(p + " could not load.")); a.nonce = m.querySelector("script[nonce]")?.nonce || ""; m.head.append(a) })); d[l] ? console.warn(p + " only loads once. Ignoring:", g) : d[l] = (f, ...n) => r.add(f) && u().then(() => d[l](f, ...n)) })
({ key: "{MAP API KEY}", v: "weekly" });
</script>In place of {MAP API KEY} replace your Google map api key and as of now Google Map 3D is in beta version so we will need to specify the beta version.
Lets initialize the map with default options and with the map id, replace {MAP ID} with your MAP Id that we created in the first step.
const mapOptions = {
tilt: 0,
heading: 0,
zoom: 18,
center: { lat: 35.6594945, lng: 139.6999859 },
mapId: "{MAP ID}",
// disable interactions due to animation loop and moveCamera
disableDefaultUI: true,
gestureHandling: "none",
keyboardShortcuts: false,
};
let map;
async function initMap()
{
const { Map } = await google.maps.importLibrary("maps");
map = new Map(document.getElementById("map"), mapOptions);
}Once Google map is initialized we need to initialize WebGLOverlayView which was introduced with Google map webgl so this is required to overlay 3D objects in Google map.
Google map works on Latitude, Longitude and Altitude but 3D object works in world space coordinates. So this overlay will be used to render 3d objects within map.
const webglOverlayView = new google.maps.WebGLOverlayView();
webGLOverlayView.onAdd = () => {};
webGLOverlayView.onContextRestored = ({gl}) => {};
webGLOverlayView.onDraw = ({gl, coordinateTransformer}) => {};WebGLOverlayView has callback events which we will use to render 3D object using THREE.js library.
THREE.js require Scene, PerspectiveCamer, Renderer and Loader to load and render 3D objects in coordinates.
let scene, renderer, camera, loader;Initialize THREE.js this done in webgl overlay view callback events.
onAdd() is called when the overlay is created and use it to fetch or create intermediate data structures before the overlay is drawn that don’t require immediate access to the WebGL rendering context.
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 THREE.GLTFLoader();
const source =
"https://raw.githubusercontent.com/googlemaps/js-samples/main/assets/pin.gltf";
loader.load(source, (gltf) => {
gltf.scene.scale.set(10, 10, 10);
gltf.scene.rotation.x = Math.PI; // Rotations are in radians.
scene.add(gltf.scene);
});
};onContextRestored({gl}) is called once the rendering context is available, use it to initialize or bind any WebGL state such as shaders, GL buffer objects, and so on. onContextRestored() takes a WebGLStateOptions instance, which has a single field:
glis a handle to theWebGLRenderingContextused by the basemap
webglOverlayView.onContextRestored = ({ gl }) => {
// Create the 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(() => {
webglOverlayView.requestRedraw();
const { tilt, heading, zoom } = mapOptions;
map.moveCamera({ tilt, heading, zoom });
// Rotate the map 360 degrees.
if (mapOptions.tilt < 67.5) {
mapOptions.tilt += 0.5;
} else if (mapOptions.heading <= 360) {
mapOptions.heading += 0.2;
mapOptions.zoom -= 0.0005;
} else {
renderer.setAnimationLoop(null);
}
});
};
};onDraw({gl, transformer}) renders the scene on the basemap. The parameters for onDraw() is a WebGLDrawOptions object, which has two fields:
glis a handle to theWebGLRenderingContextused by the basemap.transformerprovides helper functions to transform from map coordinates to model-view-projection matrix, which can be used to translate map coordinates to world space, camera space, and screen space.
webglOverlayView.onDraw = ({ gl, transformer }) => {
const latLngAltitudeLiteral = {
lat: mapOptions.center.lat,
lng: mapOptions.center.lng,
altitude: 100,
};
// Update camera matrix to ensure the model is georeferenced correctly on the map.
const matrix = transformer.fromLatLngAltitude(latLngAltitudeLiteral);
camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);
webglOverlayView.requestRedraw();
renderer.render(scene, camera);
// Sometimes it is necessary to reset the GL state.
renderer.resetState();
};Now lets set the overlayview to map
webglOverlayView.setMap(map);Run the code in the browser and you should see

Complete Code
<style>
#map {
position: initial !important;
overflow: hidden !important;
width: 500px;
height: 500px;
}
</style>
<div id="map"></div>
<script src="https://unpkg.com/three@0.124.0/build/three.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/googlemaps-js-api-loader/1.16.1/index.min.js"></script>
<script src="https://unpkg.com/three@0.126.0/examples/js/loaders/GLTFLoader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.115/examples/js/lines/LineSegments2.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.115/examples/js/lines/Line2.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.115/examples/js/lines/LineMaterial.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.115/examples/js/lines/LineSegmentsGeometry.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.115/examples/js/lines/LineGeometry.js"></script>
<script>
(g => { var h, a, k, p = "The Google Maps JavaScript API", c = "google", l = "importLibrary", q = "__ib__", m = document, b = window; b = b[c] || (b[c] = {}); var d = b.maps || (b.maps = {}), r = new Set, e = new URLSearchParams, u = () => h || (h = new Promise(async (f, n) => { await (a = m.createElement("script")); e.set("libraries", [...r] + ""); for (k in g) e.set(k.replace(/[A-Z]/g, t => "_" + t[0].toLowerCase()), g[k]); e.set("callback", c + ".maps." + q); a.src = `https://maps.${c}apis.com/maps/api/js?` + e; d[q] = f; a.onerror = () => h = n(Error(p + " could not load.")); a.nonce = m.querySelector("script[nonce]")?.nonce || ""; m.head.append(a) })); d[l] ? console.warn(p + " only loads once. Ignoring:", g) : d[l] = (f, ...n) => r.add(f) && u().then(() => d[l](f, ...n)) })
({ key: "{MAP API KEY}", v: "weekly" });
</script>
<script type="text/javascript" lang="javascript">
const mapOptions = {
tilt: 0,
heading: 0,
zoom: 18,
center: { lat: 35.6594945, lng: 139.6999859 },
mapId: "{MAP ID}",
// disable interactions due to animation loop and moveCamera
disableDefaultUI: true,
gestureHandling: "none",
keyboardShortcuts: false,
};
async function initMap() {
const { Map } = await google.maps.importLibrary("maps");
map = new Map(document.getElementById("map"), mapOptions);
initWebglOverlayView(map);
}
function initWebglOverlayView(map) {
let scene, renderer, camera, loader;
const webglOverlayView = new google.maps.WebGLOverlayView();
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 THREE.GLTFLoader();
const source =
"https://raw.githubusercontent.com/googlemaps/js-samples/main/assets/pin.gltf";
loader.load(source, (gltf) => {
gltf.scene.scale.set(10, 10, 10);
gltf.scene.rotation.x = Math.PI; // Rotations are in radians.
scene.add(gltf.scene);
});
};
webglOverlayView.onContextRestored = ({ gl }) => {
// Create the 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(() => {
webglOverlayView.requestRedraw();
const { tilt, heading, zoom } = mapOptions;
map.moveCamera({ tilt, heading, zoom });
// Rotate the map 360 degrees.
if (mapOptions.tilt < 67.5) {
mapOptions.tilt += 0.5;
} else if (mapOptions.heading <= 360) {
mapOptions.heading += 0.2;
mapOptions.zoom -= 0.0005;
} else {
renderer.setAnimationLoop(null);
}
});
};
};
webglOverlayView.onDraw = ({ gl, transformer }) => {
const latLngAltitudeLiteral = {
lat: mapOptions.center.lat,
lng: mapOptions.center.lng,
altitude: 100,
};
// Update camera matrix to ensure the model is georeferenced correctly on the map.
const matrix = transformer.fromLatLngAltitude(latLngAltitudeLiteral);
camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);
webglOverlayView.requestRedraw();
renderer.render(scene, camera);
// Sometimes it is necessary to reset the GL state.
renderer.resetState();
};
webglOverlayView.setMap(map);
}
//Tly.site.Init(@Html.Raw(@latLongData));
initMap();
</script>
Leave a Reply