Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b575968
Add experimental Google Maps 3D overlay support
charlieforward9 Jun 29, 2026
c4ce2bb
Add Map3D native route demo and camera altitude tests
charlieforward9 Jun 29, 2026
e33978b
Add native Map3D editing demo shell
charlieforward9 Jun 29, 2026
09c776c
Extract Map3D editor operations
charlieforward9 Jun 29, 2026
a78dd17
Promote Map3D editor state to google maps module
charlieforward9 Jun 29, 2026
7406654
Stabilize Map3D deck debug overlay
charlieforward9 Jun 29, 2026
f45ee0d
Add Map3D deck depth mode toggle
charlieforward9 Jun 30, 2026
bc2aa71
Fix Map3D pitched camera alignment
charlieforward9 Jun 30, 2026
4200531
Make Map3D overlay demo visible by default
charlieforward9 Jun 30, 2026
980bd66
Stabilize Map3D fallback camera redraws
charlieforward9 Jun 30, 2026
7f6e663
Show Map3D deck debug by default
charlieforward9 Jun 30, 2026
14d363c
Tighten Map3D draped surface fallback
charlieforward9 Jun 30, 2026
528941b
Show Map3D deck diagnostic by default
charlieforward9 Jun 30, 2026
f27784f
Pause Map3D deck fallback while camera moves
charlieforward9 Jun 30, 2026
5f67668
Keep Map3D deck fallback visible while panning
charlieforward9 Jun 30, 2026
9cbf8e9
Hold Map3D fallback redraws until camera steadies
charlieforward9 Jun 30, 2026
4bccd53
Stabilize Map3D fallback zoom during pan
charlieforward9 Jun 30, 2026
9b9b81e
Add stable Map3D screen fallback mode
charlieforward9 Jun 30, 2026
15fb18a
Stabilize Map3D fallback during pitch
charlieforward9 Jun 30, 2026
b264fc1
Make Map3D deck diagnostic opt-in
charlieforward9 Jun 30, 2026
4a747cb
Avoid hardcoded Map3D prototype dev port
charlieforward9 Jun 30, 2026
9299870
Show Map3D deck diagnostic by default
charlieforward9 Jun 30, 2026
6112209
Default Map3D diagnostic to geospatial fallback
charlieforward9 Jun 30, 2026
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
485 changes: 485 additions & 0 deletions examples/website/google-map-3d-overlay-prototype/app.js

Large diffs are not rendered by default.

90 changes: 90 additions & 0 deletions examples/website/google-map-3d-overlay-prototype/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Google Map3D deck.gl overlay prototype</title>
<style>
html,
body,
#app {
width: 100%;
height: 100%;
margin: 0;
}
body {
overflow: hidden;
font:
13px/1.4 -apple-system,
BlinkMacSystemFont,
'Segoe UI',
sans-serif;
background: #111;
}
gmp-map-3d {
display: block;
width: 100%;
height: 100%;
}
.panel {
position: absolute;
left: 12px;
top: 12px;
z-index: 10;
max-width: 360px;
color: #f8fafc;
background: rgba(15, 23, 42, 0.86);
border: 1px solid rgba(148, 163, 184, 0.35);
border-radius: 6px;
padding: 10px;
backdrop-filter: blur(10px);
}
.panel strong {
display: block;
margin-bottom: 4px;
}
.panel code {
color: #7dd3fc;
}
.toolbar {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 10px;
}
button {
appearance: none;
border: 1px solid rgba(148, 163, 184, 0.5);
border-radius: 6px;
color: #f8fafc;
background: rgba(30, 41, 59, 0.9);
padding: 6px 8px;
cursor: pointer;
}
button:hover {
background: rgba(51, 65, 85, 0.95);
}
button.active {
border-color: #38bdf8;
color: #e0f2fe;
background: rgba(8, 47, 73, 0.95);
}
.error {
position: absolute;
inset: 0;
display: grid;
place-items: center;
padding: 32px;
color: #fee2e2;
background: #111827;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module">
import {renderToDOM} from './app.js';
renderToDOM(document.getElementById('app'));
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

/* global navigator */

import {createMap3DEditorState, normalizeMap3DCoordinate} from '@deck.gl/google-maps';

const PATH_STYLE = {
drawsOccludedSegments: true,
strokeColor: '#ff7a00',
strokeWidth: 8
};

const POLYGON_STYLE = {
drawsOccludedSegments: true,
fillColor: '#0ea5e955',
strokeColor: '#38bdf8',
strokeWidth: 4
};

export function createNativeMap3DEditor({map, maps3d, path, polygon, points, onChange}) {
const constructors = getMap3DConstructors(maps3d);
const editorState = createMap3DEditorState({path, points, polygon});
const {routeElement, polygonElement} = createGeometryElements(constructors);
const handles = [];
let moveSelectedOnNextClick = false;

addGeometryEventListeners(routeElement, polygonElement, insertPathVertex, insertPolygonVertex);
const mapClick = installMapElements(map, routeElement, polygonElement, handleMapPosition);

render();

return {
get mode() {
return getSnapshot().mode;
},
setMode,
deleteSelected,
destroy,
moveSelectedToNextClick,
reset,
undoLast,
getSnapshot,
copyGeoJSON
};

function setMode(mode) {
moveSelectedOnNextClick = false;
editorState.setMode(mode);
render();
}

function handleMapPosition(position) {
if (moveSelectedOnNextClick) {
const {changed} = editorState.moveSelected(position);
moveSelectedOnNextClick = false;
render();
return changed;
}
appendPosition(position);
return true;
}

function appendPosition(position) {
editorState.appendPosition(position);
render();
}

function insertPathVertex(position) {
editorState.insertPathVertex(position);
render();
}

function insertPolygonVertex(position) {
editorState.insertPolygonVertex(position);
render();
}

function deleteSelected() {
const {changed} = editorState.deleteSelected();
moveSelectedOnNextClick = false;
render();
return changed;
}

function undoLast() {
const {changed} = editorState.undoLast();
render();
return changed;
}

function moveSelectedToNextClick() {
const hasSelection = Boolean(getSnapshot().selected);
moveSelectedOnNextClick = hasSelection;
render();
return hasSelection;
}

function reset() {
moveSelectedOnNextClick = false;
editorState.reset();
render();
}

function destroy() {
map.removeEventListener('gmp-click', mapClick);
routeElement.remove();
polygonElement.remove();
clearHandles();
}

async function copyGeoJSON() {
const text = JSON.stringify(getSnapshot().geojson, null, 2);
try {
await navigator.clipboard?.writeText(text);
} catch {
// Clipboard is best-effort on local HTTP/dev origins.
}
return text;
}

function getSnapshot() {
return {...editorState.getSnapshot(), moveSelectedOnNextClick};
}

function render() {
const snapshot = getSnapshot();
setElementPath(routeElement, snapshot.path);
setElementPath(polygonElement, snapshot.polygon.length >= 3 ? snapshot.polygon : []);
renderHandles();
onChange?.(snapshot);
}

function renderHandles() {
const snapshot = getSnapshot();
clearHandles();
for (const [type, coordinates] of [
['path', snapshot.path],
['polygon', snapshot.polygon],
['point', snapshot.points]
]) {
const isActiveMode = snapshot.mode === type;
coordinates.forEach((position, index) => {
const selected = snapshot.selected?.type === type && snapshot.selected.index === index;
const marker = new constructors.MarkerElement({
altitudeMode: constructors.AltitudeMode.CLAMP_TO_GROUND,
collisionPriority: selected ? 1000 : 10,
drawsWhenOccluded: true,
label: getHandleLabel(type, index, selected),
position,
sizePreserved: true,
zIndex: isActiveMode || selected ? 20 : 5
});
marker.addEventListener('gmp-click', event => {
stopEvent(event);
editorState.select({type, index});
render();
});
map.appendChild(marker);
handles.push(marker);
});
}
}

function clearHandles() {
while (handles.length) {
handles.pop().remove();
}
}
}

function installMapElements(map, routeElement, polygonElement, handleMapPosition) {
const mapClick = createMapClickHandler(handleMapPosition);
map.addEventListener('gmp-click', mapClick);
map.append(routeElement, polygonElement);
return mapClick;
}

function addGeometryEventListeners(
routeElement,
polygonElement,
insertPathVertex,
insertPolygonVertex
) {
routeElement.addEventListener('gmp-click', event => {
const position = getEventPosition(event);
stopEvent(event);
if (position) {
insertPathVertex(position);
}
});
polygonElement.addEventListener('gmp-click', event => {
const position = getEventPosition(event);
stopEvent(event);
if (position) {
insertPolygonVertex(position);
}
});
}

function createMapClickHandler(appendPosition) {
return event => {
const position = getEventPosition(event);
if (!position) {
return;
}
event.preventDefault?.();
appendPosition(position);
};
}

function getMap3DConstructors(maps3d) {
const {
AltitudeMode,
Marker3DInteractiveElement,
Marker3DElement,
Polyline3DInteractiveElement,
Polyline3DElement,
Polygon3DInteractiveElement,
Polygon3DElement
} = maps3d;

return {
AltitudeMode,
MarkerElement: Marker3DInteractiveElement || Marker3DElement,
PolylineElement: Polyline3DInteractiveElement || Polyline3DElement,
PolygonElement: Polygon3DInteractiveElement || Polygon3DElement
};
}

function createGeometryElements({AltitudeMode, PolylineElement, PolygonElement}) {
return {
routeElement: new PolylineElement({
altitudeMode: AltitudeMode.CLAMP_TO_GROUND,
...PATH_STYLE
}),
polygonElement: new PolygonElement({
altitudeMode: AltitudeMode.CLAMP_TO_GROUND,
...POLYGON_STYLE
})
};
}

function setElementPath(element, coordinates) {
element.path = coordinates.map(toLatLngAltitudeLiteral);
element.setAttribute('path', coordinates.map(toPathToken).join(' '));
}

function getEventPosition(event) {
return normalizeMap3DCoordinate(event.position);
}

function getHandleLabel(type, index, selected) {
const prefix = type === 'point' ? 'P' : String(index + 1);
return selected ? `*${prefix}` : prefix;
}

function stopEvent(event) {
event.preventDefault?.();
event.stopPropagation?.();
}

function toLatLngAltitudeLiteral({lat, lng, altitude = 0}) {
return {lat, lng, altitude};
}

function toPathToken({lat, lng, altitude = 0}) {
return `${lat},${lng},${altitude}`;
}
19 changes: 19 additions & 0 deletions examples/website/google-map-3d-overlay-prototype/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "deckgl-example-google-map-3d-overlay-prototype",
"version": "0.0.0",
"private": true,
"license": "MIT",
"scripts": {
"start": "vite --open",
"start-local": "vite --config ../../vite.config.local.mjs",
"build": "vite build"
},
"dependencies": {
"@deck.gl/core": "^9.0.0",
"@deck.gl/google-maps": "^9.0.0",
"@deck.gl/layers": "^9.0.0",
"@deck.gl/widgets": "^9.0.0",
"vite": "^7.3.3"
},
"devDependencies": {}
}
Loading
Loading