Control the position of the camera
Stay organized with collections
Save and categorize content based on your preferences.
The map view is modeled as a camera looking down on a flat plane. The position
of the camera (and hence the rendering of the map) is specified by the following
properties: latitude, longitude, altitude, heading, tilt, range,
and fov.
The camera target is the location of the center of the map, specified as latitude and longitude coordinates. You can specify the camera's position in two ways:
- 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.
Logically, center and cameraPosition are linked. When you set one, the other
is automatically calculated based on the camera's orientation and distance.
Properties like tilt, heading, and roll control how the camera is aimed,
regardless of which positioning method you use.
The following example lets you toggle between center (camera looking at the
map center) and cameraPosition (camera positioned at the map center).
See the complete example source code
TypeScript
asyncfunctioninit():Promise<void>{ // Import the needed libraries. awaitgoogle.maps.importLibrary('maps3d'); constmap3DElement=document.querySelector('gmp-map-3d')!; constbtn=document.getElementById('switch-mode-btn')asHTMLButtonElement; constinitialCenter={lat:40.7860524,lng:-73.9634983,altitude:0}; letisCenterMode=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; } }); } voidinit();
JavaScript
asyncfunctioninit(){ // Import the needed libraries. awaitgoogle.maps.importLibrary('maps3d'); constmap3DElement=document.querySelector('gmp-map-3d'); constbtn=document.getElementById('switch-mode-btn'); constinitialCenter={lat:40.7860524,lng:-73.9634983,altitude:0}; letisCenterMode=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; } }); } voidinit();
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:1pxsolidrgba(255,255,255,0.1); color:#f8fafc; padding:12px20px; border-radius:8px; cursor:pointer; font-size:0.9rem; font-weight:600; transition:all0.2sease; box-shadow:08px32px0rgba(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>Field of view and range
In a 3D environment, the concept of "zoom" is controlled by two
distinct parameters: range and fov (field of view). While both affect
how large objects appear on the map, they do so using different mechanics:
range: The physical distance between the camera and its center point. Adjusting the range is like moving a camera closer to or further away from a subject.fov: The vertical angle of the camera lens, measured in degrees. Adjusting the field of view is equivalent to changing the lens on a camera. A higher value (up to 80 degrees) acts like a wide-angle lens, showing more of the periphery, while a lower value (down to 5 degrees) acts like a telephoto lens and narrows the focus.
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.
See the complete example source code
TypeScript
asyncfunctioninit():Promise<void>{ // Declare the needed libraries. awaitgoogle.maps.importLibrary('maps3d'); constmap3DElement=document.querySelector('gmp-map-3d')!; // Elements from HTML constheadingSlider=document.getElementById( 'heading' )asHTMLInputElement; consttiltSlider=document.getElementById('tilt')asHTMLInputElement; constrangeSlider=document.getElementById('range')asHTMLInputElement; constlatSlider=document.getElementById('lat')asHTMLInputElement; constlngSlider=document.getElementById('lng')asHTMLInputElement; constfovSlider=document.getElementById('fov')asHTMLInputElement; constrollSlider=document.getElementById('roll')asHTMLInputElement; constheadingVal=document.getElementById('heading-val')!; consttiltVal=document.getElementById('tilt-val')!; constrangeVal=document.getElementById('range-val')!; constaltitudeVal=document.getElementById('altitude-val')!; constfovVal=document.getElementById('fov-val')!; constrollVal=document.getElementById('roll-val')!; constcodeElem=document.getElementById('generated-code')!; constcopyBtn=document.getElementById('copy-btn')asHTMLButtonElement; letcurrentAltitude=30; letisUserInteracting=false; // Update values on UI when the map changes. constupdateUI=()=>{ constheading=map3DElement.heading?.toFixed(0)??'0'; consttilt=map3DElement.tilt?.toFixed(0)??'0'; constrange=map3DElement.range?.toFixed(0)??'0'; constrawFov=parseFloat(map3DElement.fov?.toFixed(0)??'45'); constfovClamped=Math.min(80,Math.max(5,rawFov)); constfov=fovClamped.toString(); constroll=map3DElement.roll?.toFixed(0)??'0'; constcenter=map3DElement.center; constmode=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){ constlat=center.lat.toFixed(4); constlng=center.lng.toFixed(4); constalt=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',()=>{ voidnavigator.clipboard.writeText(codeElem.textContent||''); copyBtn.textContent='Copied!'; setTimeout(()=>{ copyBtn.textContent='Copy HTML'; },2000); }); // Listen to slider changes using event delegation. constpanel=document.querySelector('.panel')!; panel.addEventListener('input',(e)=>{ consttarget=e.targetasHTMLInputElement; if(target.tagName!=='INPUT')return; isUserInteracting=true; constprop=target.name; constval=parseFloat(target.value); if(prop==='lat'){ constcurrentCenter=map3DElement.center; if(currentCenter){ map3DElement.center={ lat:val, lng:currentCenter.lng, altitude:currentCenter.altitude, }; } }elseif(prop==='lng'){ constcurrentCenter=map3DElement.center; if(currentCenter){ map3DElement.center={ lat:currentCenter.lat, lng:val, altitude:currentCenter.altitude, }; } }elseif(prop==='altitude'){ currentAltitude=val; constcurrentCenter=map3DElement.center; if(currentCenter){ map3DElement.center={ lat:currentCenter.lat, lng:currentCenter.lng, altitude:val, }; } }elseif(prop==='tilt'){ map3DElement.tilt=Math.max(0,val); }elseif(prop==='fov'){ map3DElement.fov=Math.min(80,Math.max(5,val)); }else{ // eslint-disable-next-line @typescript-eslint/no-explicit-any (map3DElementasany)[prop]=val; } updateUI(); }); panel.addEventListener('change',(e)=>{ consttarget=e.targetasHTMLInputElement; 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); } voidinit();
JavaScript
asyncfunctioninit(){ // Declare the needed libraries. awaitgoogle.maps.importLibrary('maps3d'); constmap3DElement=document.querySelector('gmp-map-3d'); // Elements from HTML constheadingSlider=document.getElementById('heading'); consttiltSlider=document.getElementById('tilt'); constrangeSlider=document.getElementById('range'); constlatSlider=document.getElementById('lat'); constlngSlider=document.getElementById('lng'); constfovSlider=document.getElementById('fov'); constrollSlider=document.getElementById('roll'); constheadingVal=document.getElementById('heading-val'); consttiltVal=document.getElementById('tilt-val'); constrangeVal=document.getElementById('range-val'); constaltitudeVal=document.getElementById('altitude-val'); constfovVal=document.getElementById('fov-val'); constrollVal=document.getElementById('roll-val'); constcodeElem=document.getElementById('generated-code'); constcopyBtn=document.getElementById('copy-btn'); letcurrentAltitude=30; letisUserInteracting=false; // Update values on UI when the map changes. constupdateUI=()=>{ constheading=map3DElement.heading?.toFixed(0)??'0'; consttilt=map3DElement.tilt?.toFixed(0)??'0'; constrange=map3DElement.range?.toFixed(0)??'0'; constrawFov=parseFloat(map3DElement.fov?.toFixed(0)??'45'); constfovClamped=Math.min(80,Math.max(5,rawFov)); constfov=fovClamped.toString(); constroll=map3DElement.roll?.toFixed(0)??'0'; constcenter=map3DElement.center; constmode=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){ constlat=center.lat.toFixed(4); constlng=center.lng.toFixed(4); constalt=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',()=>{ voidnavigator.clipboard.writeText(codeElem.textContent||''); copyBtn.textContent='Copied!'; setTimeout(()=>{ copyBtn.textContent='Copy HTML'; },2000); }); // Listen to slider changes using event delegation. constpanel=document.querySelector('.panel'); panel.addEventListener('input',(e)=>{ consttarget=e.target; if(target.tagName!=='INPUT')return; isUserInteracting=true; constprop=target.name; constval=parseFloat(target.value); if(prop==='lat'){ constcurrentCenter=map3DElement.center; if(currentCenter){ map3DElement.center={ lat:val, lng:currentCenter.lng, altitude:currentCenter.altitude, }; } }elseif(prop==='lng'){ constcurrentCenter=map3DElement.center; if(currentCenter){ map3DElement.center={ lat:currentCenter.lat, lng:val, altitude:currentCenter.altitude, }; } }elseif(prop==='altitude'){ currentAltitude=val; constcurrentCenter=map3DElement.center; if(currentCenter){ map3DElement.center={ lat:currentCenter.lat, lng:currentCenter.lng, altitude:val, }; } }elseif(prop==='tilt'){ map3DElement.tilt=Math.max(0,val); }elseif(prop==='fov'){ map3DElement.fov=Math.min(80,Math.max(5,val)); }else{ map3DElement[prop]=val; } updateUI(); }); panel.addEventListener('change',(e)=>{ consttarget=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); } voidinit();
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:1pxsolidrgba(255,255,255,0.1); border-radius:16px; padding:24px; margin-top:10px; box-shadow:08px32px0rgba(0,0,0,0.37); } h1{ font-size:1.25rem; font-weight:700; margin:004px0; background:linear-gradient(toright,#38bdf8,#818cf8); -webkit-background-clip:text; -webkit-text-fill-color:transparent; } .sub-title{ font-size:0.85rem; color:#94a3b8; margin:0020px0; } h2{ font-size:0.95rem; font-weight:600; margin:16px08px0; 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:1pxsolidrgba(255,255,255,0.1); color:#f8fafc; padding:4px8px; 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:008pxrgba(56,189,248,0.5); transition:all0.2sease; } 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:1pxsolidrgba(255,255,255,0.05); color:#f8fafc; padding:10px16px; border-radius:8px; cursor:pointer; font-size:0.85rem; font-weight:500; transition:all0.2sease; 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-groupp{ font-size:0.8rem; color:#94a3b8; margin:4px0; background:rgba(30,41,59,0.5); padding:6px10px; border-radius:6px; font-family:monospace; } .code-box{ position:relative; background:rgba(15,23,42,0.9); border-radius:8px; border:1pxsolidrgba(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:all0.2sease; } #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>