diff --git a/dist/index.html b/dist/index.html index 02b817c0f..dd5dacffd 100644 --- a/dist/index.html +++ b/dist/index.html @@ -10,6 +10,7 @@

Maps JSAPI Samples

  • 3d-accessibility-features
  • 3d-camera-boundary
  • 3d-camera-center
  • +
  • 3d-camera-position
  • 3d-camera-to-around
  • 3d-clamp-mode
  • 3d-coverage-map
  • diff --git a/dist/samples/3d-camera-position/app/.eslintsrc.json b/dist/samples/3d-camera-position/app/.eslintsrc.json new file mode 100644 index 000000000..4c44dab04 --- /dev/null +++ b/dist/samples/3d-camera-position/app/.eslintsrc.json @@ -0,0 +1,13 @@ +{ + "extends": [ + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "rules": { + "@typescript-eslint/ban-ts-comment": 0, + "@typescript-eslint/no-this-alias": 1, + "@typescript-eslint/no-empty-function": 1, + "@typescript-eslint/explicit-module-boundary-types": 1, + "@typescript-eslint/no-unused-vars": 1 + } +} diff --git a/dist/samples/3d-camera-position/app/README.md b/dist/samples/3d-camera-position/app/README.md new file mode 100644 index 000000000..7ff252ae8 --- /dev/null +++ b/dist/samples/3d-camera-position/app/README.md @@ -0,0 +1,48 @@ +# Google Maps JavaScript Sample + +## 3d-camera-position + +An interactive playground designed to help developers understand and experiment with camera positioning in Google Maps 3D. + +## Features + +- **Live Camera Controls**: Real-time sliders for adjusting `heading`, `tilt`, `range`, and `fov` properties. +- **Coordinate Mapping**: Direct controls to set the camera's focal point via `Latitude`, `Longitude`, and `Altitude`. +- **Code Generator**: Dynamically generates the resulting `` HTML tag with mapped properties for easy copying and pasting. +- **Continuous Event Syncing**: Listens to map interaction events (like dragging and panning) to keep UI readouts strictly synchronized with live map state. + +## Setup + +### Before starting run: + +`npm i` + +### Run an example on a local web server + +`cd samples/3d-camera-position` +`npm start` + +### Build an individual example + +`cd samples/3d-camera-position` +`npm run build` + +From 'samples': + +`npm run build --workspace=3d-camera-position/` + +### Build all of the examples. + +From 'samples': + +`npm run build-all` + +### Run lint to check for problems + +`cd samples/3d-camera-position` +`npx eslint index.ts` + +## Feedback + +For feedback related to this sample, please open a new issue on +[GitHub](https://github.com/googlemaps-samples/js-api-samples/issues). diff --git a/dist/samples/3d-camera-position/app/index.html b/dist/samples/3d-camera-position/app/index.html new file mode 100644 index 000000000..2d083ed10 --- /dev/null +++ b/dist/samples/3d-camera-position/app/index.html @@ -0,0 +1,147 @@ + + + + + + ` + Google Maps 3D - Camera Position Controller + + + + + + + + +
    +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    +
    + + +
    +
    + + +
    +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    +
    +
    +
    + +
    +
    +
    + + + diff --git a/dist/samples/3d-camera-position/app/index.ts b/dist/samples/3d-camera-position/app/index.ts new file mode 100644 index 000000000..fc84e3e24 --- /dev/null +++ b/dist/samples/3d-camera-position/app/index.ts @@ -0,0 +1,147 @@ +/* + * @license + * Copyright 2026 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +// [START maps_3d_camera_position] +async function initMap(): Promise { + // 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 = ``; + } + }; + + // 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(); +// [END maps_3d_camera_position] diff --git a/dist/samples/3d-camera-position/app/package.json b/dist/samples/3d-camera-position/app/package.json new file mode 100644 index 000000000..94e7c0c98 --- /dev/null +++ b/dist/samples/3d-camera-position/app/package.json @@ -0,0 +1,12 @@ +{ + "name": "@js-api-samples/3d-camera-position", + "version": "1.0.0", + "scripts": { + "build": "tsc && bash ../jsfiddle.sh 3d-camera-position && bash ../app.sh 3d-camera-position && bash ../docs.sh 3d-camera-position && npm run build:vite --workspace=. && bash ../dist.sh 3d-camera-position", + "test": "tsc && npm run build:vite --workspace=.", + "start": "tsc && vite build --base './' && vite", + "build:vite": "vite build --base './'", + "preview": "vite preview" + }, + "dependencies": {} +} diff --git a/dist/samples/3d-camera-position/app/style.css b/dist/samples/3d-camera-position/app/style.css new file mode 100644 index 000000000..bf6fbbcfe --- /dev/null +++ b/dist/samples/3d-camera-position/app/style.css @@ -0,0 +1,215 @@ +/* + * @license + * Copyright 2026 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +/* [START maps_3d_camera_position] */ + +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; + 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; +} + +/* [END maps_3d_camera_position] */ diff --git a/dist/samples/3d-camera-position/app/tsconfig.json b/dist/samples/3d-camera-position/app/tsconfig.json new file mode 100644 index 000000000..976bcc6ef --- /dev/null +++ b/dist/samples/3d-camera-position/app/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "." + }, + "include": ["./*.ts"] +} diff --git a/dist/samples/3d-camera-position/dist/assets/index-FbE_-4e_.css b/dist/samples/3d-camera-position/dist/assets/index-FbE_-4e_.css new file mode 100644 index 000000000..fa2e9e337 --- /dev/null +++ b/dist/samples/3d-camera-position/dist/assets/index-FbE_-4e_.css @@ -0,0 +1 @@ +html,body{color:#e2e8f0;background-color:#0f172a;height:100%;margin:0;padding:0;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Open Sans,Helvetica Neue,sans-serif}gmp-map-3d{width:100%;height:100%}#ui-container{z-index:10;width:320px;position:absolute;top:20px;left:20px}.panel{-webkit-backdrop-filter:blur(12px);background:#0f172abf;border:1px solid #ffffff1a;border-radius:16px;padding:24px;box-shadow:0 8px 32px #0000005e}h1{background:linear-gradient(90deg,#38bdf8,#818cf8);-webkit-text-fill-color:transparent;-webkit-background-clip:text;margin:0 0 4px;font-size:1.25rem;font-weight:700}.sub-title{color:#94a3b8;margin:0 0 20px;font-size:.85rem}h2{color:#f8fafc;margin:16px 0 8px;font-size:.95rem;font-weight:600}.control-group{margin-bottom:16px}label{color:#cbd5e1;margin-bottom:6px;font-size:.85rem;display:block}span{color:#38bdf8;font-weight:600}.row{gap:12px;display:flex}.col{flex:1}input[type=number]{color:#f8fafc;background:#0f172a80;border:1px solid #ffffff1a;border-radius:6px;outline:none;padding:4px 8px;font-family:Fira Code,monospace}input[type=range]{-webkit-appearance:none;background:#334155;border-radius:2px;outline:none;width:100%;height:4px}input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;cursor:pointer;background:#38bdf8;border-radius:50%;width:16px;height:16px;transition:all .2s;box-shadow:0 0 8px #38bdf880}input[type=range]::-webkit-slider-thumb:hover{background:#60a5fa;transform:scale(1.2)}.buttons{flex-direction:column;gap:8px;display:flex}button{color:#f8fafc;cursor:pointer;text-align:left;background:#33415580;border:1px solid #ffffff0d;border-radius:8px;padding:10px 16px;font-size:.85rem;font-weight:500;transition:all .2s}button:hover{background:#38bdf833;border-color:#38bdf866;transform:translateY(-1px)}button:active{transform:translateY(0)}.status-group p{color:#94a3b8;background:#1e293b80;border-radius:6px;margin:4px 0;padding:6px 10px;font-family:monospace;font-size:.8rem}.code-box{background:#0f172ae6;border:1px solid #ffffff0d;border-radius:8px;margin-top:8px;position:relative}pre{margin:0;padding:12px;overflow-x:auto}code{color:#38bdf8;font-family:Fira Code,monospace;font-size:.75rem}#copy-btn{color:#f8fafc;text-align:center;cursor:pointer;background:#334155;border:none;border-radius:6px;width:100%;margin-top:8px;padding:8px;font-size:.85rem;font-weight:600;transition:all .2s;display:block}#copy-btn:hover{color:#0f172a;background:#38bdf8} diff --git a/dist/samples/3d-camera-position/dist/assets/index-HwMXX0CU.js b/dist/samples/3d-camera-position/dist/assets/index-HwMXX0CU.js new file mode 100644 index 000000000..142ebc72d --- /dev/null +++ b/dist/samples/3d-camera-position/dist/assets/index-HwMXX0CU.js @@ -0,0 +1 @@ +(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();async function e(){await google.maps.importLibrary(`maps3d`);let e=document.querySelector(`gmp-map-3d`),t=document.getElementById(`heading`),n=document.getElementById(`tilt`),r=document.getElementById(`range`),i=document.getElementById(`lat`),a=document.getElementById(`lng`),o=document.getElementById(`fov`),s=document.getElementById(`roll`),c=document.getElementById(`heading-val`),l=document.getElementById(`tilt-val`),u=document.getElementById(`range-val`),d=document.getElementById(`altitude-val`),f=document.getElementById(`fov-val`),p=document.getElementById(`roll-val`),m=document.getElementById(`generated-code`),h=document.getElementById(`copy-btn`),g=30,_=!1,v=()=>{let h=e.heading?.toFixed(0)??`0`,v=e.tilt?.toFixed(0)??`0`,y=e.range?.toFixed(0)??`0`,b=parseFloat(e.fov?.toFixed(0)??`45`),x=Math.min(80,Math.max(5,b)).toString(),S=e.roll?.toFixed(0)??`0`,C=e.center,w=e.mode;if(c.textContent=h,l.textContent=v,u.textContent=y,f.textContent=x,p.textContent=S,_||(o.value=x,t.value=h,n.value=v,r.value=Math.min(1e4,parseFloat(y)).toString(),s.value=S),C){let e=C.lat.toFixed(4),t=C.lng.toFixed(4),n=g.toFixed(0);i.value=e,a.value=t,d.textContent=n,m.textContent=``}};h.addEventListener(`click`,()=>{navigator.clipboard.writeText(m.textContent||``),h.textContent=`Copied!`,setTimeout(()=>{h.textContent=`Copy HTML`},2e3)});let y=document.querySelector(`.panel`);y.addEventListener(`input`,t=>{let n=t.target;if(n.tagName!==`INPUT`)return;_=!0;let r=n.name,i=parseFloat(n.value);if(r===`lat`){let t=e.center;t&&(e.center={lat:i,lng:t.lng,altitude:t.altitude})}else if(r===`lng`){let t=e.center;t&&(e.center={lat:t.lat,lng:i,altitude:t.altitude})}else if(r===`altitude`){g=i;let t=e.center;t&&(e.center={lat:t.lat,lng:t.lng,altitude:i})}else e[r]=i;v()}),y.addEventListener(`change`,e=>{e.target.tagName===`INPUT`&&(_=!1)}),e.addEventListener(`gmp-headingchange`,v),e.addEventListener(`gmp-tiltchange`,v),e.addEventListener(`gmp-rangechange`,v),e.addEventListener(`gmp-fovchange`,v),setTimeout(v,500)}e(); \ No newline at end of file diff --git a/dist/samples/3d-camera-position/dist/index.html b/dist/samples/3d-camera-position/dist/index.html new file mode 100644 index 000000000..5f45e6a0a --- /dev/null +++ b/dist/samples/3d-camera-position/dist/index.html @@ -0,0 +1,147 @@ + + + + + + ` + Google Maps 3D - Camera Position Controller + + + + + + + + +
    +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    +
    + + +
    +
    + + +
    +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    +
    +
    +
    + +
    +
    +
    + + + diff --git a/dist/samples/3d-camera-position/docs/index.html b/dist/samples/3d-camera-position/docs/index.html new file mode 100644 index 000000000..2d083ed10 --- /dev/null +++ b/dist/samples/3d-camera-position/docs/index.html @@ -0,0 +1,147 @@ + + + + + + ` + Google Maps 3D - Camera Position Controller + + + + + + + + +
    +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    +
    + + +
    +
    + + +
    +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    +
    +
    +
    + +
    +
    +
    + + + diff --git a/dist/samples/3d-camera-position/docs/index.js b/dist/samples/3d-camera-position/docs/index.js new file mode 100644 index 000000000..a7b9edfe7 --- /dev/null +++ b/dist/samples/3d-camera-position/docs/index.js @@ -0,0 +1,131 @@ +"use strict"; +/* + * @license + * Copyright 2026 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +// [START maps_3d_camera_position] +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 = ``; + } + }; + // 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(); +// [END maps_3d_camera_position] diff --git a/dist/samples/3d-camera-position/docs/index.ts b/dist/samples/3d-camera-position/docs/index.ts new file mode 100644 index 000000000..fc84e3e24 --- /dev/null +++ b/dist/samples/3d-camera-position/docs/index.ts @@ -0,0 +1,147 @@ +/* + * @license + * Copyright 2026 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +// [START maps_3d_camera_position] +async function initMap(): Promise { + // 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 = ``; + } + }; + + // 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(); +// [END maps_3d_camera_position] diff --git a/dist/samples/3d-camera-position/docs/style.css b/dist/samples/3d-camera-position/docs/style.css new file mode 100644 index 000000000..bf6fbbcfe --- /dev/null +++ b/dist/samples/3d-camera-position/docs/style.css @@ -0,0 +1,215 @@ +/* + * @license + * Copyright 2026 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +/* [START maps_3d_camera_position] */ + +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; + 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; +} + +/* [END maps_3d_camera_position] */ diff --git a/dist/samples/3d-camera-position/jsfiddle/demo.css b/dist/samples/3d-camera-position/jsfiddle/demo.css new file mode 100644 index 000000000..d2bf395dc --- /dev/null +++ b/dist/samples/3d-camera-position/jsfiddle/demo.css @@ -0,0 +1,212 @@ +/* + * @license + * Copyright 2026 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +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; + 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; +} diff --git a/dist/samples/3d-camera-position/jsfiddle/demo.details b/dist/samples/3d-camera-position/jsfiddle/demo.details new file mode 100644 index 000000000..46e2ba897 --- /dev/null +++ b/dist/samples/3d-camera-position/jsfiddle/demo.details @@ -0,0 +1,7 @@ +name: 3d-camera-position +authors: + - Geo Developer IX Documentation Team +tags: + - google maps +load_type: h +description: Sample code supporting Google Maps Platform JavaScript API documentation. diff --git a/dist/samples/3d-camera-position/jsfiddle/demo.html b/dist/samples/3d-camera-position/jsfiddle/demo.html new file mode 100644 index 000000000..123faa0fb --- /dev/null +++ b/dist/samples/3d-camera-position/jsfiddle/demo.html @@ -0,0 +1,146 @@ + + + + + + ` + Google Maps 3D - Camera Position Controller + + + + + + + + +
    +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    +
    + + +
    +
    + + +
    +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    +
    +
    +
    + +
    +
    +
    + + diff --git a/dist/samples/3d-camera-position/jsfiddle/demo.js b/dist/samples/3d-camera-position/jsfiddle/demo.js new file mode 100644 index 000000000..3ef53de3c --- /dev/null +++ b/dist/samples/3d-camera-position/jsfiddle/demo.js @@ -0,0 +1,126 @@ +'use strict'; +/* + * @license + * Copyright 2026 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +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 = ``; + } + }; + // 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(); diff --git a/index.html b/index.html index 02b817c0f..dd5dacffd 100644 --- a/index.html +++ b/index.html @@ -10,6 +10,7 @@

    Maps JSAPI Samples

  • 3d-accessibility-features
  • 3d-camera-boundary
  • 3d-camera-center
  • +
  • 3d-camera-position
  • 3d-camera-to-around
  • 3d-clamp-mode
  • 3d-coverage-map
  • diff --git a/package-lock.json b/package-lock.json index e73506db6..6f561b287 100644 --- a/package-lock.json +++ b/package-lock.json @@ -464,6 +464,10 @@ "resolved": "samples/3d-camera-center", "link": true }, + "node_modules/@js-api-samples/3d-camera-position": { + "resolved": "samples/3d-camera-position", + "link": true + }, "node_modules/@js-api-samples/3d-camera-to-around": { "resolved": "samples/3d-camera-to-around", "link": true @@ -5054,6 +5058,10 @@ "name": "@js-api-samples/3d-camera-center", "version": "1.0.0" }, + "samples/3d-camera-position": { + "name": "@js-api-samples/3d-camera-position", + "version": "1.0.0" + }, "samples/3d-camera-to-around": { "name": "@js-api-samples/3d-camera-to-around", "version": "1.0.0"