التحكّم في موضع الكاميرا

يتم تصميم عرض الخريطة على شكل كاميرا تنظر إلى أسفل على مستوى مسطّح. يتم تحديد موضع الكاميرا (وبالتالي عرض الخريطة) من خلال الخصائص التالية: latitude وlongitude وaltitude وheading وtilt وrange وfov.

تمثّل نقطة استهداف الكاميرا الموقع الجغرافي لمركز الخريطة، ويتم تحديدها كإحداثيات خط العرض وخط الطول. يمكنك تحديد موضع الكاميرا بطريقتَين:

  • حسب نقطة الاستهداف: استخدِم السمة center لتحديد إحداثيات على الخريطة يجب أن تتجه إليها الكاميرا. يكون ذلك مفيدًا عندما تريد التأكّد من أنّ معلمًا بارزًا أو منطقة معيّنة محل التركيز.
  • حسب إحداثيات الكاميرا: استخدِم السمة cameraPosition لوضع الكاميرا عند إحداثيات خطوط الطول والعرض والارتفاع المحدّدة. هذا الخيار مثالي لتحديد وجهة نظر دقيقة.

منطقيًا، center وcameraPosition مرتبطان. عند ضبط أحدهما، يتم احتساب الآخر تلقائيًا استنادًا إلى اتجاه الكاميرا ومسافتها. تتحكّم سمات مثل tilt وheading وroll في طريقة توجيه الكاميرا، بغض النظر عن طريقة تحديد الموضع التي تستخدمها.

يتيح لك المثال التالي التبديل بين center (الكاميرا التي تنظر إلى وسط الخريطة) وcameraPosition (الكاميرا الموضوعة في وسط الخريطة).

الاطّلاع على رمز المصدر الكامل للمثال

TypeScript

async function init(): Promise<void> {
    // Import the needed libraries.
    await google.maps.importLibrary('maps3d');

    const map3DElement = document.querySelector('gmp-map-3d')!;
    const btn = document.getElementById('switch-mode-btn') as HTMLButtonElement;

    const initialCenter = { lat: 40.7860524, lng: -73.9634983, altitude: 0 };
    let isCenterMode = true;

    btn.addEventListener('click', () => {
        if (isCenterMode) {
            // Switch to Camera Position Mode.
            // Place the camera at the marker's location, but 50m up in the air
            map3DElement.cameraPosition = { ...initialCenter, altitude: 50 };
            map3DElement.tilt = 80;

            btn.textContent = 'Switch to Center Mode';
            isCenterMode = false;
        } else {
            // Revert back to Center Mode (looking AT the marker)
            map3DElement.center = initialCenter;
            map3DElement.tilt = 70;
            map3DElement.range = 1500; // Restore the original range value.

            btn.textContent = 'Switch to Camera Position';
            isCenterMode = true;
        }
    });
}

void init();

JavaScript

async function init() {
    // Import the needed libraries.
    await google.maps.importLibrary('maps3d');

    const map3DElement = document.querySelector('gmp-map-3d');
    const btn = document.getElementById('switch-mode-btn');

    const initialCenter = { lat: 40.7860524, lng: -73.9634983, altitude: 0 };
    let isCenterMode = true;

    btn.addEventListener('click', () => {
        if (isCenterMode) {
            // Switch to Camera Position Mode.
            // Place the camera at the marker's location, but 50m up in the air
            map3DElement.cameraPosition = { ...initialCenter, altitude: 50 };
            map3DElement.tilt = 80;

            btn.textContent = 'Switch to Center Mode';
            isCenterMode = false;
        } else {
            // Revert back to Center Mode (looking AT the marker)
            map3DElement.center = initialCenter;
            map3DElement.tilt = 70;
            map3DElement.range = 1500; // Restore the original range value.

            btn.textContent = 'Switch to Camera Position';
            isCenterMode = true;
        }
    });
}

void init();

CSS

html,
body {
    height: 100%;
    margin: 0;
    padding: 0;
}

#ui-container {
    position: absolute;
    top: 20px;
    left: 20px;
    z-index: 10;
}

button {
    background: rgba(15, 23, 42, 0.75);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border: 1px solid rgba(255, 255, 255, 0.1);
    color: #f8fafc;
    padding: 12px 20px;
    border-radius: 8px;
    cursor: pointer;
    font-size: 0.9rem;
    font-weight: 600;
    transition: all 0.2s ease;
    box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
}

button:hover {
    background: rgba(56, 189, 248, 0.2);
    border-color: rgba(56, 189, 248, 0.4);
    transform: translateY(-1px);
}

button:active {
    transform: translateY(0);
}

HTML

<html>
    <head>
        <title>3D Camera Position</title>

        <link rel="stylesheet" type="text/css" href="./style.css" />
        <script type="module" src="./index.js"></script>
        <script>
            // prettier-ignore
            (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: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8" 
            });
        </script>
    </head>

    <body>
        <gmp-map-3d
            center="40.7860524,-73.9634983"
            range="1500"
            tilt="70"
            heading="-150"
            mode="satellite">
            <gmp-marker
                position="40.7860524,-73.9634983"
                altitude-mode="clamp-to-ground"></gmp-marker>
        </gmp-map-3d>

        <div id="ui-container">
            <button id="switch-mode-btn" type="button">
                Switch to Camera Position
            </button>
        </div>
    </body>
</html>

تجربة عيّنة

مجال الرؤية والمدى

في بيئة ثلاثية الأبعاد، يتم التحكّم في مفهوم "التكبير" من خلال مَعلمتَين مختلفتَين: range وfov (مجال الرؤية). على الرغم من أنّ كليهما يؤثران في حجم ظهور العناصر الكبيرة على الخريطة، إلا أنّهما يستخدمان آليات مختلفة:

  • range: المسافة الفعلية بين الكاميرا ونقطة مركزها تعديل النطاق يشبه تقريب الكاميرا من الهدف أو إبعادها عنه.
  • fov: هي الزاوية العمودية لعدسة الكاميرا، ويتم قياسها بالدرجات. ويشبه تعديل مجال الرؤية تغيير العدسة في الكاميرا. تعمل القيمة الأعلى (حتى 80 درجة) مثل عدسة بزاوية عريضة، حيث تعرض المزيد من المحيط، بينما تعمل القيمة الأقل (حتى 5 درجات) مثل عدسة مقرِّبة (تيليفوتو) وتضيّق التركيز.

يتيح لك المثال التالي إجراء تجربة لمعرفة كيفية عمل خيارات تحديد المواقع المختلفة للخريطة والكاميرا معًا. اضبط المَعلمات باستخدام أشرطة التمرير في واجهة المستخدم، أو تفاعَل مباشرةً مع الخريطة وعناصر التحكّم في الخريطة. تتم إضافة المَعلمات الناتجة إلى عنصر gmp-map-3d يمكنك نسخه وإعادة استخدامه.

الاطّلاع على رمز المصدر الكامل للمثال

TypeScript

async function initMap(): Promise<void> {
    // Declare the needed libraries.
    await google.maps.importLibrary('maps3d');

    const map3DElement = document.querySelector('gmp-map-3d')!;

    // Elements from HTML
    const headingSlider = document.getElementById(
        'heading'
    ) as HTMLInputElement;
    const tiltSlider = document.getElementById('tilt') as HTMLInputElement;
    const rangeSlider = document.getElementById('range') as HTMLInputElement;
    const latSlider = document.getElementById('lat') as HTMLInputElement;
    const lngSlider = document.getElementById('lng') as HTMLInputElement;
    const fovSlider = document.getElementById('fov') as HTMLInputElement;
    const rollSlider = document.getElementById('roll') as HTMLInputElement;

    const headingVal = document.getElementById('heading-val')!;
    const tiltVal = document.getElementById('tilt-val')!;
    const rangeVal = document.getElementById('range-val')!;
    const altitudeVal = document.getElementById('altitude-val')!;
    const fovVal = document.getElementById('fov-val')!;
    const rollVal = document.getElementById('roll-val')!;
    const codeElem = document.getElementById('generated-code')!;
    const copyBtn = document.getElementById('copy-btn') as HTMLButtonElement;

    let currentAltitude = 30;
    let isUserInteracting = false;

    // Update values on UI when the map changes.
    const updateUI = () => {
        const heading = map3DElement.heading?.toFixed(0) ?? '0';
        const tilt = map3DElement.tilt?.toFixed(0) ?? '0';
        const range = map3DElement.range?.toFixed(0) ?? '0';
        const rawFov = parseFloat(map3DElement.fov?.toFixed(0) ?? '45');
        const fovClamped = Math.min(80, Math.max(5, rawFov));
        const fov = fovClamped.toString();
        const roll = map3DElement.roll?.toFixed(0) ?? '0';
        const center = map3DElement.center;
        const mode = map3DElement.mode;

        headingVal.textContent = heading;
        tiltVal.textContent = tilt;
        rangeVal.textContent = range;
        fovVal.textContent = fov;
        rollVal.textContent = roll;

        if (!isUserInteracting) {
            fovSlider.value = fov;
            headingSlider.value = heading;
            tiltSlider.value = tilt;
            rangeSlider.value = Math.min(10000, parseFloat(range)).toString();
            rollSlider.value = roll;
        }

        if (center) {
            const lat = center.lat.toFixed(4);
            const lng = center.lng.toFixed(4);
            const alt = currentAltitude.toFixed(0);

            latSlider.value = lat;
            lngSlider.value = lng;
            altitudeVal.textContent = alt;

            codeElem.textContent = `<gmp-map-3d center="${lat},${lng},${alt}" mode="${mode}" tilt="${tilt}" range="${range}" heading="${heading}" fov="${fov}" roll="${roll}"></gmp-map-3d>`;
        }
    };

    // Copy generated HTML to clipboard.
    copyBtn.addEventListener('click', () => {
        void navigator.clipboard.writeText(codeElem.textContent || '');
        copyBtn.textContent = 'Copied!';
        setTimeout(() => {
            copyBtn.textContent = 'Copy HTML';
        }, 2000);
    });

    // Listen to slider changes using event delegation.
    const panel = document.querySelector('.panel')!;

    panel.addEventListener('input', (e) => {
        const target = e.target as HTMLInputElement;
        if (target.tagName !== 'INPUT') return;

        isUserInteracting = true;
        const prop = target.name;
        const val = parseFloat(target.value);

        if (prop === 'lat') {
            const currentCenter = map3DElement.center;
            if (currentCenter) {
                map3DElement.center = {
                    lat: val,
                    lng: currentCenter.lng,
                    altitude: currentCenter.altitude,
                };
            }
        } else if (prop === 'lng') {
            const currentCenter = map3DElement.center;
            if (currentCenter) {
                map3DElement.center = {
                    lat: currentCenter.lat,
                    lng: val,
                    altitude: currentCenter.altitude,
                };
            }
        } else if (prop === 'altitude') {
            currentAltitude = val;
            const currentCenter = map3DElement.center;
            if (currentCenter) {
                map3DElement.center = {
                    lat: currentCenter.lat,
                    lng: currentCenter.lng,
                    altitude: val,
                };
            }
        } else if (prop === 'tilt') {
            map3DElement.tilt = Math.max(0, val);
        } else if (prop === 'fov') {
            map3DElement.fov = Math.min(80, Math.max(5, val));
        } else {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            (map3DElement as any)[prop] = val;
        }
        updateUI();
    });

    panel.addEventListener('change', (e) => {
        const target = e.target as HTMLInputElement;
        if (target.tagName === 'INPUT') {
            isUserInteracting = false;
        }
    });

    // Update UI on camera change events.
    map3DElement.addEventListener('gmp-headingchange', updateUI);
    map3DElement.addEventListener('gmp-tiltchange', updateUI);
    map3DElement.addEventListener('gmp-rangechange', updateUI);
    map3DElement.addEventListener('gmp-fovchange', updateUI);

    // Initial UI sync
    setTimeout(updateUI, 500);
}

void initMap();

JavaScript

async function initMap() {
    // Declare the needed libraries.
    await google.maps.importLibrary('maps3d');
    const map3DElement = document.querySelector('gmp-map-3d');
    // Elements from HTML
    const headingSlider = document.getElementById('heading');
    const tiltSlider = document.getElementById('tilt');
    const rangeSlider = document.getElementById('range');
    const latSlider = document.getElementById('lat');
    const lngSlider = document.getElementById('lng');
    const fovSlider = document.getElementById('fov');
    const rollSlider = document.getElementById('roll');
    const headingVal = document.getElementById('heading-val');
    const tiltVal = document.getElementById('tilt-val');
    const rangeVal = document.getElementById('range-val');
    const altitudeVal = document.getElementById('altitude-val');
    const fovVal = document.getElementById('fov-val');
    const rollVal = document.getElementById('roll-val');
    const codeElem = document.getElementById('generated-code');
    const copyBtn = document.getElementById('copy-btn');
    let currentAltitude = 30;
    let isUserInteracting = false;
    // Update values on UI when the map changes.
    const updateUI = () => {
        const heading = map3DElement.heading?.toFixed(0) ?? '0';
        const tilt = map3DElement.tilt?.toFixed(0) ?? '0';
        const range = map3DElement.range?.toFixed(0) ?? '0';
        const rawFov = parseFloat(map3DElement.fov?.toFixed(0) ?? '45');
        const fovClamped = Math.min(80, Math.max(5, rawFov));
        const fov = fovClamped.toString();
        const roll = map3DElement.roll?.toFixed(0) ?? '0';
        const center = map3DElement.center;
        const mode = map3DElement.mode;
        headingVal.textContent = heading;
        tiltVal.textContent = tilt;
        rangeVal.textContent = range;
        fovVal.textContent = fov;
        rollVal.textContent = roll;
        if (!isUserInteracting) {
            fovSlider.value = fov;
            headingSlider.value = heading;
            tiltSlider.value = tilt;
            rangeSlider.value = Math.min(10000, parseFloat(range)).toString();
            rollSlider.value = roll;
        }
        if (center) {
            const lat = center.lat.toFixed(4);
            const lng = center.lng.toFixed(4);
            const alt = currentAltitude.toFixed(0);
            latSlider.value = lat;
            lngSlider.value = lng;
            altitudeVal.textContent = alt;
            codeElem.textContent = `<gmp-map-3d center="${lat},${lng},${alt}" mode="${mode}" tilt="${tilt}" range="${range}" heading="${heading}" fov="${fov}" roll="${roll}"></gmp-map-3d>`;
        }
    };
    // Copy generated HTML to clipboard.
    copyBtn.addEventListener('click', () => {
        void navigator.clipboard.writeText(codeElem.textContent || '');
        copyBtn.textContent = 'Copied!';
        setTimeout(() => {
            copyBtn.textContent = 'Copy HTML';
        }, 2000);
    });
    // Listen to slider changes using event delegation.
    const panel = document.querySelector('.panel');
    panel.addEventListener('input', (e) => {
        const target = e.target;
        if (target.tagName !== 'INPUT')
            return;
        isUserInteracting = true;
        const prop = target.name;
        const val = parseFloat(target.value);
        if (prop === 'lat') {
            const currentCenter = map3DElement.center;
            if (currentCenter) {
                map3DElement.center = {
                    lat: val,
                    lng: currentCenter.lng,
                    altitude: currentCenter.altitude,
                };
            }
        }
        else if (prop === 'lng') {
            const currentCenter = map3DElement.center;
            if (currentCenter) {
                map3DElement.center = {
                    lat: currentCenter.lat,
                    lng: val,
                    altitude: currentCenter.altitude,
                };
            }
        }
        else if (prop === 'altitude') {
            currentAltitude = val;
            const currentCenter = map3DElement.center;
            if (currentCenter) {
                map3DElement.center = {
                    lat: currentCenter.lat,
                    lng: currentCenter.lng,
                    altitude: val,
                };
            }
        }
        else if (prop === 'tilt') {
            map3DElement.tilt = Math.max(0, val);
        }
        else if (prop === 'fov') {
            map3DElement.fov = Math.min(80, Math.max(5, val));
        }
        else {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            map3DElement[prop] = val;
        }
        updateUI();
    });
    panel.addEventListener('change', (e) => {
        const target = e.target;
        if (target.tagName === 'INPUT') {
            isUserInteracting = false;
        }
    });
    // Update UI on camera change events.
    map3DElement.addEventListener('gmp-headingchange', updateUI);
    map3DElement.addEventListener('gmp-tiltchange', updateUI);
    map3DElement.addEventListener('gmp-rangechange', updateUI);
    map3DElement.addEventListener('gmp-fovchange', updateUI);
    // Initial UI sync
    setTimeout(updateUI, 500);
}
void initMap();

CSS

html,
body {
    height: 100%;
    margin: 0;
    padding: 0;
    font-family:
        'Inter',
        -apple-system,
        BlinkMacSystemFont,
        'Segoe UI',
        Roboto,
        Oxygen,
        Ubuntu,
        Cantarell,
        'Open Sans',
        'Helvetica Neue',
        sans-serif;
    background-color: #0f172a;
    color: #e2e8f0;
}

gmp-map-3d {
    height: 100%;
    width: 100%;
}

/* Glassmorphism UI Overlay */
#ui-container {
    position: absolute;
    top: 20px;
    left: 20px;
    width: 320px;
    z-index: 10;
}

.panel {
    background: rgba(15, 23, 42, 0.75);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 16px;
    padding: 24px;
    margin-top: 10px;
    box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
}

h1 {
    font-size: 1.25rem;
    font-weight: 700;
    margin: 0 0 4px 0;
    background: linear-gradient(to right, #38bdf8, #818cf8);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
}

.sub-title {
    font-size: 0.85rem;
    color: #94a3b8;
    margin: 0 0 20px 0;
}

h2 {
    font-size: 0.95rem;
    font-weight: 600;
    margin: 16px 0 8px 0;
    color: #f8fafc;
}

.control-group {
    margin-bottom: 16px;
}

label {
    display: block;
    font-size: 0.85rem;
    margin-bottom: 6px;
    color: #cbd5e1;
}

span {
    font-weight: 600;
    color: #38bdf8;
}

.row {
    display: flex;
    gap: 12px;
}

.col {
    flex: 1;
}

input[type='number'] {
    font-family: 'Fira Code', monospace;
    background: rgba(15, 23, 42, 0.5);
    border: 1px solid rgba(255, 255, 255, 0.1);
    color: #f8fafc;
    padding: 4px 8px;
    border-radius: 6px;
    outline: none;
}

input[type='range'] {
    width: 100%;
    height: 4px;
    background: #334155;
    border-radius: 2px;
    outline: none;
    -webkit-appearance: none;
}

input[type='range']::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: #38bdf8;
    cursor: pointer;
    box-shadow: 0 0 8px rgba(56, 189, 248, 0.5);
    transition: all 0.2s ease;
}

input[type='range']::-webkit-slider-thumb:hover {
    transform: scale(1.2);
    background: #60a5fa;
}

.buttons {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

button {
    background: rgba(51, 65, 85, 0.5);
    border: 1px solid rgba(255, 255, 255, 0.05);
    color: #f8fafc;
    padding: 10px 16px;
    border-radius: 8px;
    cursor: pointer;
    font-size: 0.85rem;
    font-weight: 500;
    transition: all 0.2s ease;
    text-align: left;
}

button:hover {
    background: rgba(56, 189, 248, 0.2);
    border-color: rgba(56, 189, 248, 0.4);
    transform: translateY(-1px);
}

button:active {
    transform: translateY(0);
}

.status-group p {
    font-size: 0.8rem;
    color: #94a3b8;
    margin: 4px 0;
    background: rgba(30, 41, 59, 0.5);
    padding: 6px 10px;
    border-radius: 6px;
    font-family: monospace;
}

.code-box {
    position: relative;
    background: rgba(15, 23, 42, 0.9);
    border-radius: 8px;
    border: 1px solid rgba(255, 255, 255, 0.05);
    margin-top: 8px;
}

pre {
    margin: 0;
    padding: 12px;
    overflow-x: auto;
}

code {
    font-family: 'Fira Code', monospace;
    font-size: 0.75rem;
    color: #38bdf8;
}

#copy-btn {
    display: block;
    width: 100%;
    margin-top: 8px;
    padding: 8px;
    font-size: 0.85rem;
    font-weight: 600;
    background: #334155;
    color: #f8fafc;
    border: none;
    border-radius: 6px;
    text-align: center;
    cursor: pointer;
    transition: all 0.2s ease;
}

#copy-btn:hover {
    background: #38bdf8;
    color: #0f172a;
}

HTML

<html>
    <head>
        <title>Google Maps 3D - Camera Position Controller</title>
        <link rel="stylesheet" type="text/css" href="./style.css" />
        <script type="module" src="./index.js"></script>
        <script>
            // prettier-ignore
            (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: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8"
            });
        </script>
    </head>

    <body>
        <gmp-map-3d
            center="40.7811,-73.9599,0"
            mode="HYBRID"
            tilt="76"
            range="3270"
            heading="-154"></gmp-map-3d>
        <div id="ui-container">
            <div class="panel">
                <div class="control-group">
                    <label for="heading">
                        Heading: <span id="heading-val">0</span>&deg;
                    </label>
                    <input
                        type="range"
                        id="heading"
                        name="heading"
                        min="-180"
                        max="180"
                        value="0"
                        step="1" />
                </div>

                <div class="control-group">
                    <label for="tilt">
                        Tilt: <span id="tilt-val">45</span>&deg;
                    </label>
                    <input
                        type="range"
                        id="tilt"
                        name="tilt"
                        min="0"
                        max="90"
                        value="45"
                        step="1" />
                </div>

                <div class="control-group">
                    <label for="range">
                        Range: <span id="range-val">1000</span>m
                    </label>
                    <input
                        type="range"
                        id="range"
                        name="range"
                        min="100"
                        max="10000"
                        value="1000"
                        step="100" />
                </div>

                <div class="control-group row">
                    <div class="col">
                        <label for="lat">Latitude</label>
                        <input
                            type="number"
                            id="lat"
                            name="lat"
                            min="-90"
                            max="90"
                            value="40.7040"
                            step="0.0001" />
                    </div>
                    <div class="col">
                        <label for="lng">Longitude</label>
                        <input
                            type="number"
                            id="lng"
                            name="lng"
                            min="-180"
                            max="180"
                            value="-74.0180"
                            step="0.0001" />
                    </div>
                </div>

                <div class="control-group">
                    <label for="altitude">
                        Altitude: <span id="altitude-val">30</span>m
                    </label>
                    <input
                        type="range"
                        id="altitude"
                        name="altitude"
                        min="0"
                        max="5000"
                        value="30"
                        step="10" />
                </div>

                <div class="control-group">
                    <label for="fov">
                        FOV: <span id="fov-val">35</span>&deg;
                    </label>
                    <input
                        type="range"
                        id="fov"
                        name="fov"
                        min="5"
                        max="80"
                        value="35"
                        step="1" />
                </div>

                <div class="control-group">
                    <label for="roll">
                        Roll: <span id="roll-val">0</span>&deg;
                    </label>
                    <input
                        type="range"
                        id="roll"
                        name="roll"
                        min="-180"
                        max="180"
                        value="0"
                        step="1" />
                </div>

                <div class="status-group">
                    <div class="code-box">
                        <pre><code id="generated-code"></code></pre>
                    </div>
                    <button id="copy-btn">Copy HTML</button>
                </div>
            </div>
        </div>
    </body>
</html>

تجربة عيّنة