Карта отображается в виде камеры, направленной вниз на плоскую поверхность. Положение камеры (и, следовательно, отображение карты) задается следующими свойствами: latitude , longitude , altitude , heading , tilt , range и fov .
Целью камеры является местоположение центра карты, заданное в виде координат широты и долготы. Положение камеры можно указать двумя способами:
- By target point : Use the
centerproperty to specify a coordinate on the map that the camera should face. This is best when you want to ensure a landmark or area is in focus. - By camera coordinates : Use the
cameraPositionproperty to place the camera at specific latitude, longitude, and altitude coordinates. This is ideal for defining a precise viewpoint.
Логически, center и cameraPosition связаны. При установке одного из них другое автоматически рассчитывается на основе ориентации и расстояния камеры. Такие свойства, как tilt , heading и roll управляют наведением камеры независимо от используемого метода позиционирования.
В следующем примере вы можете переключаться между center (камера направлена на центр карты) и cameraPosition (камера расположена в центре карты).
См. полный пример исходного кода.
Машинопись
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 градусов) действует как телеобъектив и сужает фокус.
The following example lets you experiment to see how the various map and camera positioning options work together. Set parameters using the UI sliders, or interact directly with the map and map controls. The resulting parameters are added to a gmp-map-3d element that you can copy and reuse.
См. полный пример исходного кода.
Машинопись
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') as HTMLElement; const tiltVal = document.getElementById('tilt-val') as HTMLElement; const rangeVal = document.getElementById('range-val') as HTMLElement; const altitudeVal = document.getElementById('altitude-val') as HTMLElement; const fovVal = document.getElementById('fov-val') as HTMLElement; const rollVal = document.getElementById('roll-val') as HTMLElement; const codeElem = document.getElementById('generated-code') as HTMLElement; 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') as HTMLElement; 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 { map3DElement[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 { 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>
<!-- prettier-ignore -->
<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: "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>°</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>