Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ <h1>Maps JSAPI Samples</h1>
<li><a href='/samples/3d-accessibility-features/dist'>3d-accessibility-features</a></li>
<li><a href='/samples/3d-camera-boundary/dist'>3d-camera-boundary</a></li>
<li><a href='/samples/3d-camera-center/dist'>3d-camera-center</a></li>
<li><a href='/samples/3d-camera-position/dist'>3d-camera-position</a></li>
<li><a href='/samples/3d-camera-to-around/dist'>3d-camera-to-around</a></li>
<li><a href='/samples/3d-clamp-mode/dist'>3d-clamp-mode</a></li>
<li><a href='/samples/3d-coverage-map/dist'>3d-coverage-map</a></li>
Expand Down
13 changes: 13 additions & 0 deletions dist/samples/3d-camera-position/app/.eslintsrc.json
Original file line number Diff line number Diff line change
@@ -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
}
}
48 changes: 48 additions & 0 deletions dist/samples/3d-camera-position/app/README.md
Original file line number Diff line number Diff line change
@@ -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 `<gmp-map-3d>` 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).
147 changes: 147 additions & 0 deletions dist/samples/3d-camera-position/app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<!doctype html>
<!--
@license
Copyright 2026 Google LLC. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
-->
<!-- [START maps_3d_camera_position] -->
<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>&deg;</label
>
<input
type="range"
id="heading"
name="heading"
min="-180"
max="180"
value="0"
step="1" />
</div>

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

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

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

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

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

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

<div class="status-group">
<div class="code-box">
<pre><code id="generated-code"></code></pre>
</div>
<button id="copy-btn">Copy HTML</button>
</div>
</div>
</div>
</body>
</html>
<!-- [END maps_3d_camera_position] -->
147 changes: 147 additions & 0 deletions dist/samples/3d-camera-position/app/index.ts
Original file line number Diff line number Diff line change
@@ -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<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();
// [END maps_3d_camera_position]
12 changes: 12 additions & 0 deletions dist/samples/3d-camera-position/app/package.json
Original file line number Diff line number Diff line change
@@ -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": {}
}
Loading
Loading