#4184 Virtual Park Visitor App
#4184 Virtual Park Visitor
#4184 Describe an online application where users can tour any National Park or other scenic wonders, probably applying Google Earth
If you’re looking to explore the world’s natural wonders from your couch, there isn't just one app—there is a suite of Google-powered experiences that turn satellite data into an immersive digital playground.
The primary platform for this is Google Earth, specifically its Voyager feature. Here is how it works and where you can find these "virtual hikes."
1. Google Earth: Voyager
Google Earth is the core "engine." While you can manually fly to any coordinate, the Voyager tab (indicated by a ship’s wheel icon) contains curated, interactive tours.
The Experience: You aren't just looking at a flat map. Voyager uses Street View imagery to place you on the actual trails, 3D terrain modeling to show the scale of mountains, and high-resolution satellite imagery for a bird's-eye view.
National Park Tours: There is a specific collection titled "National Parks of the United States." It allows you to jump between over 30 parks (like Zion, Acadia, and the Grand Canyon), offering "Postcard" views and suggested "hikes" through 360-degree imagery.
Global Wonders: Beyond the US, it features "Natural Treasures" from BBC Earth, allowing you to tour the Amazon Rainforest, the Himalayas, or the Swiss Alps.
2. Google Arts & Culture: "The Hidden Worlds"
If Google Earth is the "map," The Hidden Worlds of the National Parks is the "documentary." This is a high-production collaboration between Google and the National Park Service.
Ranger-Led Tours: Unlike standard Google Earth, these are guided by actual park rangers via 360-degree video.
Special Access: It takes you to places a normal tourist can't go—like kayaking through icebergs in Kenai Fjords, flying over an active volcano in Hawai’i, or diving into a shipwreck in Dry Tortugas.
Where to find it: It is accessible via the Google Arts & Culture website or mobile app.
3. The Official NPS App
The National Park Service (NPS) has its own official app that integrates Google Maps technology with ranger expertise.
Virtual Postcards: You can take "virtual tours" of specific sites within the parks.
Self-Guided Audio: Even if you aren't there, you can listen to the audio tours intended for hikers, which provides context to the scenery you see on Google Earth.
Comparison at a Glance
| Feature | Google Earth (Voyager) | Google Arts & Culture |
| Best For | "Hiking" trails & 3D geography | Cinematic, narrated storytelling |
| Interactivity | High (Move anywhere) | Guided (Point-and-click) |
| Visuals | Satellite & Street View | 360° Video & 4K Photography |
| Scope | Global (Any scenic wonder) | 5-6 featured US National Parks |
Would you like me to find a direct link to a specific park's virtual tour, or perhaps suggest a "hidden gem" location to start your exploration?
Yes, Yellowstone
Yellowstone is one of the best-mapped locations for a virtual tour, offering several ways to explore its geysers and canyons using Google technology.
Since you're interested in the "tour" aspect specifically, here are the three best ways to experience Yellowstone virtually right now:
1. Google Earth: The "Voyager" Tour
Google Earth has a dedicated interactive tour called "National Parks of the United States" that features Yellowstone prominently.
How to access: Open
, click the Voyager icon (the ship's wheel), and search for "Yellowstone."Google Earth The Experience: It will "fly" you directly to the Grand Prismatic Spring. From there, you can click through curated locations like the Grand Canyon of the Yellowstone and Old Faithful.
Pro Tip: Use the "Pegman" (the yellow person icon) to drop onto the boardwalks. Because it's Yellowstone, Google has captured high-resolution 360-degree imagery of nearly every major boardwalk, allowing you to "walk" right past the bubbling paint pots.
2. Google Arts & Culture: Yellowstone Interactive
While not a "flight" engine like Earth, this is a more cinematic experience.
The Experience: It provides a scrolling 3D journey through the park’s history and geology. It features a unique "Helicopter View" and high-resolution captures of Thomas Moran’s famous 1872 paintings of the park, which you can compare to the modern Google Street View of the same spots.
Direct Link:
Explore Yellowstone on Google Arts & Culture
3. NPS Official "Virtual Tours"
The National Park Service has built its own interactive maps that often leverage Google’s API but add specific "Ranger" context.
Unique Feature: They offer a "Yellowstone in 3D" map where landmarks change as you scroll, providing a chronological or thematic tour (e.g., focusing specifically on the Northern Range or the Geyser Basins).
Webcams: They also host the famous Old Faithful Live Stream, which is the ultimate "real-time" virtual tour.
Key Landmarks to "Fly" to in Google Earth:
If you want to manually explore, copy/paste these coordinates into the Google Earth search bar for the best 3D views:
Grand Prismatic Spring:
44.5250° N, 110.8382° W(The colors are stunning in satellite view).Lower Falls of the Yellowstone:
44.7129° N, 110.4960° W(Switch to 3D mode to see the depth of the canyon).Mammoth Hot Springs:
44.9762° N, 110.7011° W(The travertine terraces look like a frozen waterfall in 3D).
Would you like me to find the specific "Voyager" link for a different park, or perhaps help you find a 360-degree VR video of a specific geyser eruption?
Provide an HTML, JavaScript, CSS app to let the visitor "walk thru" or "fly thru" a selected location
To create a "fly-thru" or "walk-thru" application using modern web standards, the best tool is the Google Maps Photorealistic 3D Maps (a part of the Maps JavaScript API).
Below is a complete, single-file HTML/JS/CSS application. It uses the Map3DElement web component to render a 3D view of Yellowstone's Grand Prismatic Spring and includes a "Fly-Thru" button that orbits the camera around the location.
Prerequisites
To run this, you will need a Google Maps API Key with the following APIs enabled:
Maps JavaScript API
Map Tiles API (required for Photorealistic 3D)
The "Yellowstone Fly-Thru" App
<!DOCTYPE html>
<html>
<head>
<title>Yellowstone 3D Fly-Thru</title>
<style>
/* CSS: Basic layout and UI styling */
html, body {
height: 100%;
margin: 0;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
overflow: hidden;
}
#map-container {
height: 100%;
width: 100%;
}
.overlay-ui {
position: absolute;
top: 20px;
left: 20px;
z-index: 10;
background: rgba(255, 255, 255, 0.9);
padding: 15px;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
max-width: 300px;
}
button {
background: #0078d4;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
width: 100%;
transition: background 0.2s;
}
button:hover {
background: #005a9e;
}
h2 { margin-top: 0; font-size: 1.2rem; color: #333; }
p { font-size: 0.9rem; color: #666; }
</style>
</head>
<body>
<div class="overlay-ui">
<h2>Yellowstone Tour</h2>
<p>Location: Grand Prismatic Spring</p>
<button id="fly-button">Start Fly-Thru Orbit</button>
<p id="status" style="margin-top:10px; font-style: italic;"></p>
</div>
<div id="map-container"></div>
<script>
// JS: Initialize the 3D Map and handle the animation
(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: "YOUR_API_KEY_HERE", // Replace with your actual API Key
v: "alpha"
});
async function initApp() {
// Import the 3D library
const { Map3DElement } = await google.maps.importLibrary("maps3d");
// Create the 3D Map centered on Grand Prismatic Spring
const map3D = new Map3DElement({
center: { lat: 44.5250, lng: -110.8382, altitude: 200 },
tilt: 55,
range: 800,
heading: 0
});
document.getElementById("map-container").appendChild(map3D);
const flyButton = document.getElementById("fly-button");
const statusText = document.getElementById("status");
// Animation logic
flyButton.addEventListener("click", () => {
statusText.innerText = "Flying...";
// .flyCameraAround is a built-in method for cinematic orbits
map3D.flyCameraAround({
camera: {
center: { lat: 44.5250, lng: -110.8382, altitude: 100 },
tilt: 65,
range: 600
},
durationMillis: 20000, // 20 seconds for a full loop
rounds: 1
});
});
// Listen for when the animation ends
map3D.addEventListener("gmp-animationend", () => {
statusText.innerText = "Fly-thru complete.";
});
}
initApp();
</script>
</body>
</html>
How to use this:
Replace
YOUR_API_KEY_HEREwith your actual key in the<script>tag.Save the code as an
.htmlfile (e.g.,yellowstone.html).Open it in a modern web browser (Chrome or Edge recommended).
Key Features of this App:
3D Geometry: It automatically renders the terrain and thermal features of Yellowstone in 3D using Google’s photorealistic tiles.
Fly-Thru Logic: The
flyCameraAroundmethod handles the complex math of rotating the camera while keeping it locked on the geyser's center.User Controls: Users can still click and drag to "walk" or manually fly around the scene using their mouse or touch gestures.
Would you like me to add a search bar so you can fly to other locations (like the Grand Canyon or Yosemite) instantly?
Yes
To add search functionality, we integrate the Google Places Autocomplete service. When a user selects a location from the search bar, the app uses the flyCameraTo method to transition the camera smoothly to the new coordinates in 3D.
The "Explorer" App (3D Search + Fly-Thru)
<!DOCTYPE html>
<html>
<head>
<title>3D Scenic Explorer</title>
<style>
html, body { height: 100%; margin: 0; padding: 0; font-family: 'Segoe UI', sans-serif; overflow: hidden; }
#map-container { height: 100%; width: 100%; }
/* UI Panel Styling */
.overlay-ui {
position: absolute; top: 20px; left: 20px; z-index: 10;
background: rgba(255, 255, 255, 0.95); padding: 20px;
border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.3);
width: 320px; backdrop-filter: blur(5px);
}
/* Search Bar Styling */
#pac-input {
width: 100%; padding: 12px; box-sizing: border-box;
border: 2px solid #ddd; border-radius: 6px; font-size: 14px;
margin-bottom: 15px; outline: none; transition: border-color 0.3s;
}
#pac-input:focus { border-color: #0078d4; }
button {
background: #0078d4; color: white; border: none;
padding: 12px; border-radius: 6px; cursor: pointer;
font-weight: bold; width: 100%; font-size: 14px;
}
button:hover { background: #005a9e; }
.controls-info { margin-top: 15px; font-size: 0.85rem; color: #555; line-height: 1.4; }
h2 { margin: 0 0 10px 0; font-size: 1.3rem; color: #222; }
</style>
</head>
<body>
<div class="overlay-ui">
<h2>3D Explorer</h2>
<input id="pac-input" type="text" placeholder="Search for a Park or Wonder...">
<button id="fly-button">Start Orbit Animation</button>
<div class="controls-info">
<strong>Controls:</strong><br>
• Right-click + Drag to Pan<br>
• Left-click + Drag to Orbit<br>
• Scroll to Zoom
</div>
</div>
<div id="map-container"></div>
<script>
// API Loader
(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: "YOUR_API_KEY_HERE",
v: "alpha"
});
let map3D;
async function initApp() {
// Import necessary libraries
const [{ Map3DElement }, { Autocomplete }] = await Promise.all([
google.maps.importLibrary("maps3d"),
google.maps.importLibrary("places")
]);
// 1. Initialize 3D Map (Starting at Yellowstone)
map3D = new Map3DElement({
center: { lat: 44.428, lng: -110.588, altitude: 2000 },
tilt: 45,
range: 5000
});
document.getElementById("map-container").appendChild(map3D);
// 2. Setup Search Autocomplete
const input = document.getElementById("pac-input");
const autocomplete = new Autocomplete(input, {
fields: ["geometry", "name"],
types: ["establishment", "geocode"]
});
// 3. Handle Search Selection
autocomplete.addListener("place_changed", () => {
const place = autocomplete.getPlace();
if (!place.geometry || !place.geometry.location) return;
const newLat = place.geometry.location.lat();
const newLng = place.geometry.location.lng();
// Smooth "Fly-To" transition to the searched location
map3D.flyCameraTo({
endCamera: {
center: { lat: newLat, lng: newLng, altitude: 500 },
tilt: 55,
range: 1500
},
durationMillis: 4000
});
});
// 4. Handle Orbit Button
document.getElementById("fly-button").addEventListener("click", () => {
map3D.flyCameraAround({
camera: {
center: map3D.center,
tilt: 60,
range: map3D.range || 1500
},
durationMillis: 15000,
rounds: 1
});
});
}
initApp();
</script>
</body>
</html>
Enhancements Made:
Google Places Autocomplete: Included the
placeslibrary so users get real-time suggestions as they type "Grand Canyon" or "Mount Everest."flyCameraTo: This method provides that cinematic "swooping" effect when moving between different parts of the globe.Dynamic Center: The "Start Orbit" button now uses
map3D.center, meaning it will orbit whatever location you have searched for and flown to.
How to customize:
Altitude/Range: In the
place_changedlistener, you can decrease thealtitudeorrangevalues if you want the "landing" to be closer to the ground (more of a "walk-thru") or higher (a "fly-over").Tilt: A higher
tiltvalue (e.g., 75) provides a more dramatic horizon view, perfect for mountainous parks.
Would you like me to add markers that display "Ranger Facts" when you click on specific spots in the park?
Yes
To add "Ranger Facts," we will use Marker3DElement and the standard InfoWindow. Since 3D markers are a newer part of the API, we anchor a standard info window to them to display the facts.
The following code adds three interactive markers to Yellowstone. When you click one, the camera "flies" to that spot, and a fact box appears.
3D Explorer with Ranger Facts
<!DOCTYPE html>
<html>
<head>
<title>3D Ranger Tour</title>
<style>
html, body { height: 100%; margin: 0; padding: 0; font-family: 'Segoe UI', sans-serif; overflow: hidden; }
#map-container { height: 100%; width: 100%; }
.overlay-ui {
position: absolute; top: 20px; left: 20px; z-index: 10;
background: rgba(255, 255, 255, 0.95); padding: 20px;
border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.3);
width: 300px;
}
#pac-input {
width: 100%; padding: 10px; border: 1px solid #ccc;
border-radius: 4px; margin-bottom: 10px;
}
.fact-box { padding: 10px; max-width: 200px; }
.fact-title { font-weight: bold; color: #0078d4; margin-bottom: 5px; }
</style>
</head>
<body>
<div class="overlay-ui">
<h2>Yellowstone Ranger Tour</h2>
<input id="pac-input" type="text" placeholder="Search for wonders...">
<p style="font-size: 0.9rem; color: #666;">Click the red pins to see Ranger Facts!</p>
</div>
<div id="map-container"></div>
<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: "YOUR_API_KEY_HERE",
v: "alpha"
});
const rangerFacts = [
{
name: "Grand Prismatic Spring",
pos: { lat: 44.5250, lng: -110.8382, altitude: 50 },
fact: "The bright colors come from different species of thermophilic (heat-loving) bacteria living in the cooler water around the edges!"
},
{
name: "Old Faithful",
pos: { lat: 44.4605, lng: -110.8281, altitude: 50 },
fact: "It was named in 1870 for its consistency. It erupts roughly 20 times a day with heights up to 180 feet."
},
{
name: "Lower Falls",
pos: { lat: 44.7129, lng: -110.4960, altitude: 100 },
fact: "At 308 feet, these falls are nearly twice as high as Niagara Falls!"
}
];
async function initApp() {
const [{ Map3DElement, Marker3DElement }, { InfoWindow }, { Autocomplete }] = await Promise.all([
google.maps.importLibrary("maps3d"),
google.maps.importLibrary("maps"),
google.maps.importLibrary("places")
]);
const map3D = new Map3DElement({
center: { lat: 44.58, lng: -110.65, altitude: 15000 },
tilt: 45
});
document.getElementById("map-container").appendChild(map3D);
const infoWindow = new InfoWindow();
// Create markers for each fact
rangerFacts.forEach(point => {
const marker = new Marker3DElement({
position: point.pos,
label: point.name,
extruded: true // Draws a line to the ground
});
// Interaction: Fly to spot and show fact
marker.addEventListener('gmp-click', () => {
map3D.flyCameraTo({
endCamera: {
center: point.pos,
tilt: 65,
range: 1000
},
durationMillis: 3000
});
infoWindow.setContent(`
<div class="fact-box">
<div class="fact-title">${point.name}</div>
<div>${point.fact}</div>
</div>
`);
// Note: InfoWindow position is 2D, so we use lat/lng
infoWindow.setPosition({ lat: point.pos.lat, lng: point.pos.lng });
infoWindow.open(map3D);
});
map3D.append(marker);
});
// Search Bar Integration
const input = document.getElementById("pac-input");
const autocomplete = new Autocomplete(input, { fields: ["geometry"] });
autocomplete.addListener("place_changed", () => {
const place = autocomplete.getPlace();
if (place.geometry) {
map3D.flyCameraTo({
endCamera: {
center: {
lat: place.geometry.location.lat(),
lng: place.geometry.location.lng(),
altitude: 1000
},
tilt: 55, range: 2000
},
durationMillis: 4000
});
}
});
}
initApp();
</script>
</body>
</html>
New Features:
Marker3DElementwithextruded: true: This creates a visual "pin" with a line tethering it to the ground, which helps users see exactly where the landmark is located in a 3D landscape.gmp-clickEvent: This is the specific event listener for 3D markers. It triggers the "Fly-To" camera move and opens the info window simultaneously.Content Injection: The info window is dynamically populated with the "Ranger Fact" from the
rangerFactsarray.
Would you like me to add a "Tour Mode" button that automatically flies the user from one fact-marker to the next in a sequence?
Yes, I assume this would be the way to implement a tour
This is exactly how professional virtual tours are structured—orchestrating a sequence of camera movements and UI updates. To implement this, we'll create an Async Tour Controller.
This controller will loop through our rangerFacts array, "awaiting" each camera flight to ensure the user arrives at the destination before the next fact is displayed.
The "Auto-Tour" App
<!DOCTYPE html>
<html>
<head>
<title>3D Guided Ranger Tour</title>
<style>
html, body { height: 100%; margin: 0; padding: 0; font-family: 'Segoe UI', sans-serif; overflow: hidden; }
#map-container { height: 100%; width: 100%; }
.overlay-ui {
position: absolute; top: 20px; left: 20px; z-index: 10;
background: rgba(255, 255, 255, 0.95); padding: 20px;
border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.3);
width: 300px;
}
button {
background: #28a745; color: white; border: none;
padding: 12px; border-radius: 6px; cursor: pointer;
font-weight: bold; width: 100%; font-size: 14px; margin-top: 10px;
}
button:hover { background: #218838; }
button:disabled { background: #ccc; cursor: not-allowed; }
.tour-status { margin-top: 10px; font-size: 0.85rem; color: #d9534f; font-weight: bold; display: none; }
.fact-box { padding: 10px; max-width: 220px; line-height: 1.4; }
.fact-title { font-weight: bold; color: #0078d4; font-size: 1.1rem; margin-bottom: 5px; }
</style>
</head>
<body>
<div class="overlay-ui">
<h2>Yellowstone Tour</h2>
<p style="font-size: 0.9rem; margin-bottom:15px;">Explore the park's wonders with a guided 3D fly-thru.</p>
<button id="start-tour">Start Guided Tour</button>
<div id="status" class="tour-status">Tour in progress...</div>
</div>
<div id="map-container"></div>
<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: "YOUR_API_KEY_HERE",
v: "alpha"
});
const rangerFacts = [
{
name: "Grand Prismatic Spring",
pos: { lat: 44.5250, lng: -110.8382, altitude: 0 },
cam: { center: { lat: 44.5250, lng: -110.8382, altitude: 0 }, tilt: 60, range: 800 },
fact: "Look at the vibrant rings! These are formed by different temperatures of water supporting different bacteria."
},
{
name: "Old Faithful Geyser",
pos: { lat: 44.4605, lng: -110.8281, altitude: 0 },
cam: { center: { lat: 44.4605, lng: -110.8281, altitude: 0 }, tilt: 70, range: 1000 },
fact: "We've arrived at the most famous geyser in the world. It shoots up to 8,400 gallons of boiling water per eruption!"
},
{
name: "The Grand Canyon of Yellowstone",
pos: { lat: 44.7129, lng: -110.4960, altitude: 0 },
cam: { center: { lat: 44.7129, lng: -110.4960, altitude: 0 }, tilt: 50, range: 2500 },
fact: "Notice the yellow rocks—this is where the park gets its name. The canyon is roughly 20 miles long."
}
];
async function initApp() {
const [{ Map3DElement, Marker3DElement }, { InfoWindow }] = await Promise.all([
google.maps.importLibrary("maps3d"),
google.maps.importLibrary("maps")
]);
const map3D = new Map3DElement({
center: { lat: 44.58, lng: -110.65, altitude: 10000 },
tilt: 45
});
document.getElementById("map-container").appendChild(map3D);
const infoWindow = new InfoWindow();
const tourBtn = document.getElementById("start-tour");
const statusDiv = document.getElementById("status");
// Function to wait for camera animation to finish
const waitForArrival = () => new Promise(resolve => {
map3D.addEventListener('gmp-animationend', resolve, { once: true });
});
// The Tour Controller
async function runTour() {
tourBtn.disabled = true;
statusDiv.style.display = "block";
for (const step of rangerFacts) {
// 1. Close previous window
infoWindow.close();
// 2. Fly to the next destination
map3D.flyCameraTo({
endCamera: step.cam,
durationMillis: 5000
});
// 3. Wait for the 'gmp-animationend' event
await waitForArrival();
// 4. Show the fact
infoWindow.setContent(`
<div class="fact-box">
<div class="fact-title">${step.name}</div>
<div>${step.fact}</div>
</div>
`);
infoWindow.setPosition({ lat: step.pos.lat, lng: step.pos.lng });
infoWindow.open(map3D);
// 5. Pause for the user to read
await new Promise(r => setTimeout(r, 4000));
}
statusDiv.innerText = "Tour Complete!";
tourBtn.disabled = false;
}
tourBtn.addEventListener("click", runTour);
// Add markers to the map initially
rangerFacts.forEach(point => {
const marker = new Marker3DElement({
position: point.pos,
label: point.name,
extruded: true
});
map3D.append(marker);
});
}
initApp();
</script>
</body>
</html>
How the Tour Logic Works:
waitForArrivalPromise: This is the "brain" of the tour. It listens for thegmp-animationendevent from the Google 3D engine. Without this, the code would try to show all facts at once.for...ofLoop: Unlike a standardforEach, anasyncfor-loop allows us to useawait, creating a scripted sequence (Move -> Wait -> Speak -> Pause -> Move).UI State: The button is disabled during the tour to prevent overlapping animations, and a status message keeps the user informed.
Automatic Cleanup: The
infoWindow.close()at the start of each step ensures the screen doesn't get cluttered with old boxes.
Pro-Tip for your App:
Since you’ve worked with Mathematica and Knowledge Graphs, you could eventually populate the rangerFacts array dynamically. Instead of hardcoding them, you could pull data from a JSON file or a graph database to make this a "Global Tour Guide" for any coordinate on Earth.
Would you like me to show you how to add a background "ambient music" toggle or voice-to-speech for the Ranger Facts?
From <https://gemini.google.com/app/> Google Gemini 3.0 (Fast)
Comments
Post a Comment