Google Maps 3D API – World Cup 2026: https://geteach.com/maps3d/worldcup2026/
For the 2026 World Cup, I wanted a way to explore all 16 host cities across the US, Canada, and Mexico — not as a flat map with pins, but as something you could actually fly into. The result uses Google’s new Maps 3D JavaScript API alongside classic Street View, stitched together with a custom sidebar I built from scratch.
Here’s how the pieces fit.
The 3D layer
The map itself is a Map3DElement — part of the maps3d library and loaded dynamically via google.maps.importLibrary. It opens in satellite mode, tilted flat, looking down at North America:
let map = new Map3DElement({
center: { lat: 36.337, lng: -63.598, altitude: 2233 },
mode: MapMode.SATELLITE,
tilt: 0,
range: 63167767
});
Each stadium gets a Marker3DInteractiveElement with a custom-colored PinElement — red for Canada, green for Mexico, blue for the US — clickable to jump straight to that city.
The fun part: flying the camera
The signature feel of the site comes from map.flyCameraTo(), which animates the camera between two positions. I wrote a small Flyto() wrapper that takes a center point, heading, tilt, range, roll, and duration, and every stadium page in the sidebar triggers its own flight when selected.
The hard part wasn’t the API — it was finding good camera angles. There’s no UI for “drag the camera until it looks right,” so I bound a keyboard shortcut (Ctrl+Space) that dumps the map’s current center, heading, tilt, range, and roll to the console. I’d fly around manually, hit the shortcut when I found an angle I liked, and paste the values straight into the stadium’s entry. It’s a crude workflow, but it turned manual camera-hunting into something fast enough to do for all 16 stadiums in one sitting.
Street View, the old-school way
Each stadium page also embeds a classic StreetViewPanorama — no 3D involved, just the regular Street View service. This had its own quirks:
- Saved
panoIds aren’t permanent. Google occasionally retires or swaps panorama IDs, so I built a fallback: try the savedpanoIdfirst, and if that fails, fall back to agetPanorama()lookup by lat/lng with a small search radius. - Hidden containers don’t render. Street View panoramas measure their container’s dimensions on init — if you initialize one while its parent is
display: none(which mine were, since they live in collapsed sidebar pages), you get a blank gray box. I solved this with a visibility poll: each panorama waits, checking every 100ms, until its container actually has rendered dimensions before initializing.
The sidebar, built by hand
Rather than reach for a UI framework, the entire sidebar — table of contents, pagination arrows, collapse/expand, label and pin toggles — is vanilla JS syncing against a single source of truth: a currentpage attribute on a custom <sidebar-pagination> element. Every interaction (clicking a TOC item, clicking a map pin, hitting next/prev) funnels through that one attribute, which then triggers the camera flight and swaps the visible panel. It’s not React-level elegant, but for 16 fixed pages it kept the logic easy to trace.
A known rough edge: rapid clicking
One thing worth flagging if you build something similar: both APIs can get unstable if a user clicks around quickly — jumping between stadiums, mashing the next/prev buttons, or rapid-toggling the Street View panel. This doesn’t seem to be a bug in my code so much as something baked into the APIs themselves right now.
For the 3D side, the Maps 3D API is still in Preview (not General Availability), and Google’s own best-practices guide for it recommends sequencing actions off gmp-steadystate and gmp-animationend events rather than firing camera moves back-to-back — which suggests overlapping flyCameraTo() calls before the previous one resolves is a known stress point, not just something I happened to hit.
Street View has its own long-standing quirk: rapidly toggling a panorama’s visibility can leave it in a broken state where getVisible() reports true but nothing is actually rendered. This has been reported as far back as 2010, and the old workaround people found was nudging the POV or zoom slightly to force a redraw — which lines up with the “needs a mouse shake” behavior I’ve seen. I haven’t found anything suggesting Google has fully fixed this at the API level.
Net takeaway: if you’re building on either of these, debounce rapid input where you can, and don’t assume every glitch is your own code.
What I’d explore next
The Maps 3D API is still relatively new, and it shows in small ways. One I noticed: Google Earth Pro has had a declarative KML touring format for years — you define a playlist of fly-tos, waits, and sound cues as data, and the player runs the whole sequence for you. Maps 3D doesn’t have anything like that yet; flyCameraTo() and flyCameraAround() only handle one leg of a flight at a time, so chaining a full multi-stop tour (like flying through all 16 stadiums) means hand-rolling your own sequencing with gmp-animationend listeners — which is basically what my Flyto() wrapper does. A KML-style tour playlist for Maps 3D would be a neat addition. It’s also genuinely demanding on hardware: it’s rendering photorealistic 3D tiles in real time, and on lower-end graphics cards I’ve seen it struggle or crash outright, independent of anything related to clicking. For now, manual camera-hunting plus a console-dump shortcut got the job done. While I am at it… would love a way to programmatically disable the clouds.
If you’re building something similar, the maps3d + Street View combination works well together — just budget extra time for the visibility timing issues Street View doesn’t usually have on a normal page.
Other Google Maps API 3D Examples
- Planet Money Makes A T-Shirt: https://geteach.com/maps3d/tshirt/
- Neighborhoods Worlds Apart: https://geteach.com/maps3d/divide/
- US Geography Quiz: https://geteach.com/maps3d/usquiz/
- I’m Australian Too: https://geteach.com/maps3d/australiantoo/