This post is all about moving 3D car object in Google Map using GoogleMapThreeJSOverlayView Wrapper ( Vanilla).
Instead of using Google Map WebGLOverlayView and then in the callback events of overlay view and initializing THREE.js and so on
There is a wrapper library which makes rendering 3d objects easier inside Google Map. Cdn script and npm can be found in https://www.npmjs.com/package/@googlemaps/three

Lets get started
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 of GoogleMapThreeJSOverlayView
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://unpkg.com/three/build/three.min.js"></script>
<script src="https://unpkg.com/@googlemaps/three/dist/index.min.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);
} Lets call the Google direction service to get the sample coordinates between two points to draw the line and move car on it.
const directionsService = new google.maps.DirectionsService();
var route = [];
directionsService.route(
{
origin: "27 Front St E Toronto",
destination: "75 Yonge Street Toronto",
travelMode: "DRIVING",
},
(response, status) => {
if (status === "OK") {
route = response.routes[0].overview_path.map((path) => ({
lat: path.lat(),
lng: path.lng(),
}));
return callback(route);
}
}
)Once we get the coordinates lets initialize ThreeJSOverlayView instead of Google overlay and center the map with default latitude and longitude and set the map to ThreeJSOverlayView
const { ThreeJSOverlayView } = google.maps.plugins.three;
let overlayRef, trackRef, carRef;
map.setCenter(route[Math.floor(route.length / 2)], 17);
if (!overlayRef) {
overlayRef = new ThreeJSOverlayView({
anchor: { lat: mapOptions.center.lat, lng: mapOptions.center.lng, altitude: 0 }
});
overlayRef.setMap(map);The sample coordinates that we got from direction service is a latitude and logitude but to draw 3d object using THREE.js we need world space coordinates X,Y and Z.
So lets convert the Lat, Long to Vector3 which has x,y and z using latLngAltitudeToVector3 which is available in three js overlay view.
Once the Vector3 points are ready we can create CatmullRomCurve3 for the calculated vector3 points.
const scene = overlayRef.scene;
var points = [];
$.map(route, function (val, i) {
var vector = overlayRef.latLngAltitudeToVector3({ lat: val.lat, lng: val.lng });
points.push(vector);
});
const curve = new THREE.CatmullRomCurve3(points);
Lets draw lines in 3D space using the CatmullRomCurve3 and set the line in the overlay
var trackRef = new THREE.Line2(
new THREE.LineGeometry().setPositions(positions),
new THREE.LineMaterial({
color: 0xFF03B7,
linewidth: 2,
})
scene.add(trackRef);Hopefully at this stage you should see line in the google map. Let continue to add the car 3D object in the map. You can download the 3d object from the link.
https://sketchfab.com/3d-models/low-poly-car-f822f0c500a24ca9ac2af183d2e630b4
const loader = new THREE.GLTFLoader();
// This work is based on "Low poly Car" (https://sketchfab.com/3d-models/low-poly-car-f822f0c500a24ca9ac2af183d2e630b4) by reyad.bader (https://sketchfab.com/reyad.bader) licensed under CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)
const object = await loader.loadAsync("/img/scene.gltf");
const group = object.scene;
group.scale.setScalar(0.5);We are almost done once you execute the code you should see the line with car in the Google Map
Lets move the 3d object in the Map
overlayRef.onBeforeDraw = () => {
trackRef.material.resolution.set(window.innerWidth, window.innerHeight);
if (carRef) {
const ANIMATION_MS = 10000;
const FRONT_VECTOR = new THREE.Vector3(0, -1, 0);
const progress = (performance.now() % ANIMATION_MS) / ANIMATION_MS;
curve.getPointAt(progress, carRef.position);
carRef.quaternion.setFromUnitVectors(
FRONT_VECTOR,
curve.getTangentAt(progress)
);
carRef.rotateX(Math.PI / 2);
}
overlayRef.requestRedraw();
};OnBrforeDraw is like update which will be call before drawing the data in the Google Map. So now we should see car moving on the line in Google Map.

Code
async function initMap(latLonData) {
mapOptions = {
"tilt": 25,
zoom: 18,
disableDefaultUI: true,
heading: 25,
tilt: 60,
"mapId": "{MAP ID}",
center: { lat: 43.66293, lng: -79.39314 },
};
const { Map } = await google.maps.importLibrary("maps");
map = new Map(document.getElementById("map"), mapOptions);
fetchDirections(function (routes) {
animate(map, routes);
});
}
var animate = function (map, route) {
const { ThreeJSOverlayView } = google.maps.plugins.three;
let overlayRef, trackRef, carRef;
map.setCenter(route[Math.floor(route.length / 2)], 17);
if (!overlayRef) {
overlayRef = new ThreeJSOverlayView({
anchor: { lat: mapOptions.center.lat, lng: mapOptions.center.lng, altitude: 0 }
});
overlayRef.setMap(map);
overlayRef.onBeforeDraw = () => {
trackRef.material.resolution.set(window.innerWidth, window.innerHeight);
if (carRef) {
const ANIMATION_MS = 10000;
const FRONT_VECTOR = new THREE.Vector3(0, -1, 0);
const progress = (performance.now() % ANIMATION_MS) / ANIMATION_MS;
curve.getPointAt(progress, carRef.position);
carRef.quaternion.setFromUnitVectors(
FRONT_VECTOR,
curve.getTangentAt(progress)
);
carRef.rotateX(Math.PI / 2);
}
overlayRef.requestRedraw();
};
}
const scene = overlayRef.scene;
var points = [];
$.map(route, function (val, i) {
var vector = overlayRef.latLngAltitudeToVector3({ lat: val.lat, lng: val.lng });
points.push(vector);
});
const curve = new THREE.CatmullRomCurve3(points);
if (trackRef) {
scene.remove(trackRef);
}
trackRef = createTrackFromCurve(curve);
scene.add(trackRef);
loadModel().then((model) => {
if (carRef) {
scene.remove(carRef);
}
carRef = model;
scene.add(carRef);
});
}
var createTrackFromCurve = function (curve) {
const points = curve.getSpacedPoints(curve.points.length * 10);
const positions = points.map((point) => point.toArray()).flat();
return new THREE.Line2(
new THREE.LineGeometry().setPositions(positions),
new THREE.LineMaterial({
color: 0xFF03B7,
linewidth: 2,
})
);
}
async function loadModel() {
const loader = new THREE.GLTFLoader();
// This work is based on "Low poly Car" (https://sketchfab.com/3d-models/low-poly-car-f822f0c500a24ca9ac2af183d2e630b4) by reyad.bader (https://sketchfab.com/reyad.bader) licensed under CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)
const object = await loader.loadAsync("/img/scene.gltf");
const group = object.scene;
group.scale.setScalar(0.5);
return group;
}
var fetchDirections = function (callback) {
const directionsService = new google.maps.DirectionsService();
var route = [];
directionsService.route(
{
origin: "27 Front St E Toronto",
destination: "75 Yonge Street Toronto",
travelMode: "DRIVING",
},
(response, status) => {
if (status === "OK") {
route = response.routes[0].overview_path.map((path) => ({
lat: path.lat(),
lng: path.lng(),
}));
return callback(route);
}
}
)
}
Leave a Reply