Add three.js objects to Google Maps Platform JS. The
library provides a ThreeJSOverlayView class extending google.maps.WebGLOverlayView
and utility functions for converting geo-coordinates (latitude/longitude) to
vectors in the coordinate system used by three.js.
- Sign up with Google Maps Platform
- A Google Maps Platform project with the Maps Javascript API enabled
- An API key associated with the project above
- Node.js and NPM
Available via npm as the package @googlemaps/three.
npm i @googlemaps/three
Alternatively you can load the package directly to the html document using unpkg or other CDNs. In this case, make sure to load three.js before loading this library:
<script src="https://unpkg.com/three/build/three.min.js"></script>
<script src="https://unpkg.com/@googlemaps/three/dist/index.min.js"></script>
When adding via unpkg, the package can be accessed as
google.maps.plugins.three. A version can be specified by using
https://unpkg.com/@googlemaps/three@VERSION/dist/....
The third option to use it is via ES-Module imports, similar to how the three.js examples work. For this, you first need to specify an importmap (example using unpkg.com, but it works the same way with any other CDN or self-hosted files):
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three/build/three.module.js",
"@googlemaps/three": "https://unpkg.com/@googlemaps/three/dist/index.esm.js"
}
}
</script>In order to support browsers that don't yet implement importmap, you can use the es-module-shims package.
After that, you can use three.js and the ThreeJSOverlayView like you would when using a bundler.
<script type="module">
import * as THREE from "three";
import { ThreeJSOverlayView } from "@googlemaps/three";
// ...
</script>See the reference documentation for more information.
The coordinate system within the three.js scene (so-called 'world
coordinates') is a right-handed coordinate system in z-up orientation.
The y-axis is pointing true north, and the x-axis is pointing east. The
units are meters. So the point new Vector3(0, 50, 10) is 10 meters
above ground and 50 meters east of the specified anchor point.
This anchor-point and orientation can be set in the constructor, or by using the
setAnchor() and setUpAxis()-methods (be aware that all object-positions in
your scene depend on the anchor-point and orientation, so they have to be
recomputed when either of them is changed):
import { ThreeJSOverlayView } from "@googlemaps/three";
const overlay = new ThreeJSOverlayView({
anchor: { lat: 37.7793, lng: -122.4192, altitude: 0 },
upAxis: "Y",
});
overlay.setAnchor({ lat: 35.680432, lng: 139.769013, altitude: 0 });
overlay.setUpAxis("Z");
// can also be specified as Vector3:
overlay.setUpAxis(new Vector3(0, 0, 1));The default up-axis used in this library is the z-axis (+x is east and +y is north), which is different from the y-up orientation normally used in three.
All computations on the GPU related to the position use float32 numbers, which limits the possible precision to about 7 decimal digits. Because of this, we cannot use a global reference system and still have the precision to show details in the meters to centimeters range.
This is where the anchor point is important. The anchor specifies the geo-coordinates (lat/lng/altitude) where the origin of the world-space coordinate system is, and you should always define it close to where the objects are placed in the scene - unless of course you are only working with large-scale (city-sized) objects distributed globally.
Another reason why setting the anchor close to the objects in the scene is generally a good idea: In the mercator map-projection used in Google Maps, the scaling of meters is only accurate in regions close to the equator. This can be compensated for by applying a scale factor that depends on the latitude of the anchor. This scale factor is factored into the coordinate calculations in WebGlOverlayView based on the latitude of the anchor.
When you need more than just a single georeferenced object in your scene,
you need to compute the world-space position for those coordinates. The
ThreeJSOverlayView class provides a helper function for this conversion that
takes the current anchor and upAxis into account:
const coordinates = { lat: 12.34, lng: 56.78 };
const position: Vector3 = overlay.latLngAltitudeToVector3(coordinates);
// alternative: pass the Vector3 to write the position
// to as the second parameter, so to set the position of a mesh:
overlay.latLngAltitudeToVector3(coordinates, mesh.position);If you want to add interactivity to any three.js content, you typically
have to implement raycasting. We took care of that for you, and the
ThreeJSOverlayView provides a method overlay.raycast() for this. To make
use of it, you first have to keep track of mouse movements on the map:
import { Vector2 } from "three";
// ...
const mapDiv = map.getDiv();
const mousePosition = new Vector2();
map.addListener("mousemove", (ev) => {
const { domEvent } = ev;
const { left, top, width, height } = mapDiv.getBoundingClientRect();
const x = domEvent.clientX - left;
const y = domEvent.clientY - top;
mousePosition.x = 2 * (x / width) - 1;
mousePosition.y = 1 - 2 * (y / height);
// since the actual raycasting is performed when the next frame is
// rendered, we have to make sure that it will be called for the next frame.
overlay.requestRedraw();
});With the mouse position being always up to date, you can then use the
raycast() function in the onBeforeDraw callback.
In this example, we change the color of the object under the cursor:
const DEFAULT_COLOR = 0xffffff;
const HIGHLIGHT_COLOR = 0xff0000;
let highlightedObject = null;
overlay.onBeforeDraw = () => {
const intersections = overlay.raycast(mousePosition);
if (highlightedObject) {
highlightedObject.material.color.setHex(DEFAULT_COLOR);
}
if (intersections.length === 0) return;
highlightedObject = intersections[0].object;
highlightedObject.material.color.setHex(HIGHLIGHT_COLOR);
};The full examples can be found in ./examples/raycasting.ts.
The following example provides a skeleton for adding objects to the map with this library.
import * as THREE from "three";
import { ThreeJSOverlayView, latLngToVector3 } from "@googlemaps/three";
// when loading via UMD, remove the imports and use this instead:
// const { ThreeJSOverlayView, latLngToVector3 } = google.maps.plugins.three;
const map = new google.maps.Map(document.getElementById("map"), mapOptions);
const overlay = new ThreeJSOverlayView({
map,
upAxis: "Y",
anchor: mapOptions.center,
});
// create a box mesh
const box = new THREE.Mesh(
new THREE.BoxGeometry(10, 50, 10),
new THREE.MeshMatcapMaterial()
);
// move the box up so the origin of the box is at the bottom
box.geometry.translateY(25);
// set position at center of map
box.position.copy(overlay.latLngAltitudeToVector3(mapOptions.center));
// add box mesh to the scene
overlay.scene.add(box);
// rotate the box using requestAnimationFrame
const animate = () => {
box.rotateY(THREE.MathUtils.degToRad(0.1));
requestAnimationFrame(animate);
};
// start animation loop
requestAnimationFrame(animate);This adds a box to the map.
View the package in action:
Contributions are welcome and encouraged! If you'd like to contribute, send us a pull request and refer to our code of conduct and contributing guide.
This library uses Google Maps Platform services. Use of Google Maps Platform services through this library is subject to the Google Maps Platform Terms of Service.
This library is not a Google Maps Platform Core Service. Therefore, the Google Maps Platform Terms of Service, e.g., Technical Support Services Guidelines, Service Level Agreement "SLA", and Deprecation Policy, do not apply to the code in this library.
This library is offered via an open source license. It is not governed by the Google Maps Platform Support Technical Support Services Guidelines, the SLA, or the Deprecation Policy. However, any Google Maps Platform services used by the library remain subject to the Google Maps Platform Terms of Service.
This library adheres to semantic versioning to indicate when backwards-incompatible changes are introduced. Accordingly, while the library is in version 0.x, backwards-incompatible changes may be introduced at any time.
If you find a bug, or have a feature request, please file an issue on GitHub. If you would like to get answers to technical questions from other Google Maps Platform developers, ask through one of our developer community channels. If you'd like to contribute, please check the contributing guide.
You can also discuss this library on our Discord server.

