نمای نقشه بهصورت دوربینی که به یک صفحه مسطح نگاه میکند مدلسازی شده است. موقعیت
دوربین (و درنتیجه رندر کردن نقشه) با ویژگیهای زیر
مشخص میشود: 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: "GOOGLE_MAPS_API_KEY"
});
</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: زاویه عمودی لنز دوربین، اندازهگیریشده به درجه. تنظیم میدان دید معادل تغییر لنز در دوربین است. مقدار بالاتر (تا ۸۰ درجه) مانند لنز زاویه باز عمل میکند و بخش بیشتری از محیط را نشان میدهد، درحالیکه مقدار پایینتر (تا ۵ درجه) مانند لنز تلهفوتو عمل میکند و کانون را باریک میکند.
مثال زیر به شما امکان میدهد آزمایش کنید و ببینید گزینههای مختلف موقعیتیابی دوربین و نقشه چگونه با هم کار میکنند. پارامترها را بااستفاده از لغزندههای میانای کاربری تنظیم کنید، یا
مستقیماً با نقشه و کنترلهای نقشه تعامل برقرار کنید. پارامترهای حاصل به عنصر gmp-map-3d اضافه میشوند که میتوانید آن را کپی و مجدداً استفاده کنید.
مشاهده کد منبع نمونه کامل
TypeScript
async function init(): 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 init();
JavaScript
async function init() { // 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 { 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 init();
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: "GOOGLE_MAPS_API_KEY"
});
</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>°
</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>°
</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>°
</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>°
</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>