#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

FeatureGoogle Earth (Voyager)Google Arts & Culture
Best For"Hiking" trails & 3D geographyCinematic, narrated storytelling
InteractivityHigh (Move anywhere)Guided (Point-and-click)
VisualsSatellite & Street View360° Video & 4K Photography
ScopeGlobal (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?


You said

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 Google Earth, click the Voyager icon (the ship's wheel), and search for "Yellowstone."

  • 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:

  1. Maps JavaScript API

  2. 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:

  1. Replace YOUR_API_KEY_HERE with your actual key in the <script> tag.

  2. Save the code as an .html file (e.g., yellowstone.html).

  3. 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 flyCameraAround method 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:

  1. Google Places Autocomplete: Included the places library so users get real-time suggestions as they type "Grand Canyon" or "Mount Everest."

  2. flyCameraTo: This method provides that cinematic "swooping" effect when moving between different parts of the globe.

  3. 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_changed listener, you can decrease the altitude or range values if you want the "landing" to be closer to the ground (more of a "walk-thru") or higher (a "fly-over").

  • Tilt: A higher tilt value (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:

  1. Marker3DElement with extruded: 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.

  2. gmp-click Event: This is the specific event listener for 3D markers. It triggers the "Fly-To" camera move and opens the info window simultaneously.

  3. Content Injection: The info window is dynamically populated with the "Ranger Fact" from the rangerFacts array.

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?


You said

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:

  1. waitForArrival Promise: This is the "brain" of the tour. It listens for the gmp-animationend event from the Google 3D engine. Without this, the code would try to show all facts at once.

  2. for...of Loop: Unlike a standard forEach, an async for-loop allows us to use await, creating a scripted sequence (Move -> Wait -> Speak -> Pause -> Move).

  3. UI State: The button is disabled during the tour to prevent overlapping animations, and a status message keeps the user informed.

  4. 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

Popular posts from this blog

#2892 Example of ChatGPT o1 Prompt

#3105 Federal and State Spending Idiocy