Skip to content
Open
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
42 changes: 42 additions & 0 deletions examples/map-3d-markers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# 3D Maps with Markers Example

This example implements a new `Map3D` component that renders
a 3D Globe based on the new experimental [`Map3DElement`][gmp-map3d-overview]
web-component.

The map contains basic [`Marker3DElements`][gmp-map3d-marker-add] as well as markers with a custom pin and a 3D model.
Comment on lines +3 to +7
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This example implements a new `Map3D` component that renders
a 3D Globe based on the new experimental [`Map3DElement`][gmp-map3d-overview]
web-component.
The map contains basic [`Marker3DElements`][gmp-map3d-marker-add] as well as markers with a custom pin and a 3D model.
This example demonstrates the `Map3D`, `Marker3D`, and `Pin` components for
rendering 3D maps based on the [Google Maps 3D][gmp-map3d-overview] web components.
The map showcases various marker types including basic markers, extruded markers,
markers with custom pins, SVG/image markers, and 3D models. Learn more about
[adding markers to 3D maps][gmp-map3d-marker-add].


[gmp-map3d-overview]: https://developers.google.com/maps/documentation/javascript/3d-maps-overview
[gmp-map3d-marker-add]: https://developers.google.com/maps/documentation/javascript/3d/marker-add

## Google Maps API key

This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform.
See [the official documentation][get-api-key] on how to create and configure your own key.

The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a
file named `.env` in the example directory with the following content:

```shell title=".env"
GOOGLE_MAPS_API_KEY="<YOUR API KEY HERE>"
```

If you are on the CodeSandbox playground you can also choose to [provide the API key like this](https://codesandbox.io/docs/learn/environment/secrets)

## Development

Go into the example-directory and run

```shell
npm install
```

To start the example with the local library run

```shell
npm run start-local
```

The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example)

[get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key
Binary file added examples/map-3d-markers/data/balloon-red.glb
Binary file not shown.
31 changes: 31 additions & 0 deletions examples/map-3d-markers/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, user-scalable=no" />
<title>Example: Photorealistic 3D Map with Markers</title>

<style>
body {
margin: 0;
font-family: sans-serif;
}
#app {
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module">
import '@vis.gl/react-google-maps/examples.css';
import '@vis.gl/react-google-maps/examples.js';
import {renderToDom} from './src/app';

renderToDom(document.querySelector('#app'));
</script>
</body>
</html>
15 changes: 15 additions & 0 deletions examples/map-3d-markers/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"type": "module",
"dependencies": {
"@vis.gl/react-google-maps": "^1.8.0-rc.8",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"typescript": "^5.4.5",
"vite": "^6.0.11"
},
"scripts": {
"start": "vite",
"start-local": "vite --config ../vite.config.local.js",
"build": "vite build"
}
}
174 changes: 174 additions & 0 deletions examples/map-3d-markers/src/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/* eslint-disable no-console */
import React, {useEffect, useState} from 'react';
import {createRoot} from 'react-dom/client';

import {
AltitudeMode,
APIProvider,
Map3D,
MapMode,
Marker3D,
Pin,
Popover3D
} from '@vis.gl/react-google-maps';

import {Model3D, Model3DProps} from './model-3d';
import ControlPanel from './control-panel';

const API_KEY =
globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string);

const INITIAL_VIEW_PROPS = {
defaultCenter: {lat: 40.7093, lng: -73.9968, altitude: 32},
defaultRange: 1733,
defaultHeading: 5,
defaultTilt: 70,
defaultRoll: 0
};

/**
* AnimatedModel3D wraps the Model3D component with rotation animation.
* Demonstrates how to animate 3D models using requestAnimationFrame.
*/
const AnimatedModel3D = (modelProps: Model3DProps) => {
const rotationSpeed = 10;
const [modelHeading, setModelHeading] = useState(0);

// Animate the model rotation using requestAnimationFrame
useEffect(() => {
let animationFrameId: number;
let lastTimestamp = 0;

const animate = (timestamp: number) => {
if (lastTimestamp === 0) lastTimestamp = timestamp;

const deltaTime = (timestamp - lastTimestamp) / 1000; // Convert to seconds
lastTimestamp = timestamp;

setModelHeading(prev => (prev + rotationSpeed * deltaTime) % 360);

animationFrameId = requestAnimationFrame(animate);
};

animationFrameId = requestAnimationFrame(animate);

return () => cancelAnimationFrame(animationFrameId);
}, [rotationSpeed]);

return (
<Model3D
{...modelProps}
orientation={{heading: modelHeading, tilt: 0, roll: 0}}
/>
);
};

const App = () => {
const [openPopoverId, setOpenPopoverId] = useState<string | null>(null);
const [interactiveMarker, setInteractiveMarker] =
useState<google.maps.maps3d.Marker3DInteractiveElement | null>(null);

return (
<APIProvider apiKey={API_KEY} libraries={['maps3d', 'marker']}>
<Map3D
mode={MapMode.SATELLITE}
{...INITIAL_VIEW_PROPS}
defaultLabelsDisabled>
{/* Basic marker with popover (non-interactive) */}
<Marker3D
position={{lat: 40.704876, lng: -73.995379, altitude: 50}}
altitudeMode={AltitudeMode.RELATIVE_TO_GROUND}
/>

{/* Basic extruded marker */}
<Marker3D
position={{lat: 40.704118, lng: -73.994371, altitude: 150}}
altitudeMode={AltitudeMode.RELATIVE_TO_GROUND}
extruded
/>

{/* Interactive marker with colored pin and popover */}
<Marker3D
ref={setInteractiveMarker}
position={{lat: 40.705666, lng: -73.996382, altitude: 50}}
altitudeMode={AltitudeMode.RELATIVE_TO_GROUND}
onClick={() => {
console.log('Interactive marker clicked!');
setOpenPopoverId('colored-pin');
}}
title="Click to see details">
<Pin borderColor="#0D652D" background="#34A853" glyphColor="white" />
</Marker3D>

{openPopoverId === 'colored-pin' && (
<Popover3D
open
anchor={interactiveMarker}
onClose={() => {
setOpenPopoverId(null);
console.log('close');
}}>
<div style={{padding: '12px', maxWidth: '200px'}}>
<h3 style={{margin: '0 0 8px 0', fontSize: '14px'}}>
Custom Pin Marker
</h3>
<p style={{margin: 0, fontSize: '12px'}}>
An interactive marker with custom pin colors. Click the marker
to toggle this popover.
</p>
</div>
</Popover3D>
)}

{/* Marker with custom logo pin */}
<Marker3D
position={{lat: 40.706461, lng: -73.997409, altitude: 50}}
altitudeMode={AltitudeMode.RELATIVE_TO_GROUND}>
<Pin
borderColor="white"
background="white"
glyph="https://www.gstatic.com/images/branding/productlogos/maps/v7/192px.svg"
/>
</Marker3D>

{/* Marker with SVG image */}
<Marker3D
position={{lat: 40.707275, lng: -73.998332, altitude: 80}}
altitudeMode={AltitudeMode.RELATIVE_TO_GROUND}>
<img
src="https://www.gstatic.com/images/branding/productlogos/maps/v7/192px.svg"
width={64}
height={64}
/>
</Marker3D>

{/* Animated 3D Model */}
<AnimatedModel3D
position={{
lat: 40.708804,
lng: -74.000229,
altitude: 150
}}
altitudeMode={
AltitudeMode.RELATIVE_TO_GROUND as google.maps.maps3d.AltitudeMode
}
src={new URL('../data/balloon-red.glb', import.meta.url)}
scale={10}
/>
</Map3D>

<ControlPanel />
</APIProvider>
);
};
export default App;

export function renderToDom(container: HTMLElement) {
const root = createRoot(container);

root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
}
49 changes: 49 additions & 0 deletions examples/map-3d-markers/src/control-panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import * as React from 'react';

const GMP_3D_MAPS_OVERVIEW_URL =
'https://developers.google.com/maps/documentation/javascript/3d-maps-overview';

const GMP_3D_MAPS_MARKER_ADD_URL =
'https://developers.google.com/maps/documentation/javascript/3d/marker-add';

function ControlPanel() {
return (
<div className="control-panel">
<h3>3D Maps with Markers</h3>
<p>
This example demonstrates the <code>Map3D</code>, <code>Marker3D</code>,
and <code>Pin</code> components for rendering 3D maps based on the{' '}
<a href={GMP_3D_MAPS_OVERVIEW_URL} target={'_blank'}>
Google Maps 3D
</a>{' '}
web components.
</p>

<p>
The map showcases various marker types including basic markers, extruded
markers, markers with custom pins, SVG/image markers, and 3D models.
Learn more about{' '}
<a href={GMP_3D_MAPS_MARKER_ADD_URL} target={'_blank'}>
adding markers to 3D maps
</a>
.
</p>

<div className="links">
<a
href="https://codesandbox.io/s/github/visgl/react-google-maps/tree/main/examples/map-3d-markers"
target="_new">
Try on CodeSandbox ↗
</a>

<a
href="https://github.com/visgl/react-google-maps/tree/main/examples/map-3d-markers"
target="_new">
View Code ↗
</a>
</div>
</div>
);
}

export default React.memo(ControlPanel);
42 changes: 42 additions & 0 deletions examples/map-3d-markers/src/model-3d.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type {PropsWithChildren, Ref} from 'react';
import React, {forwardRef} from 'react';

/**
* Props for the Model3D component.
*/
export type Model3DProps =
PropsWithChildren<google.maps.maps3d.Model3DElementOptions>;

/**
* Model3D component for displaying 3D models on a Map3D.
*
* @example
* ```tsx
* <Model3D
* position={{ lat: 37.7749, lng: -122.4194, altitude: 150 }}
* altitudeMode="RELATIVE_TO_GROUND"
* src={new URL('./model.glb', import.meta.url)}
* scale={10}
* orientation={{ heading: 0, tilt: 0, roll: 0 }}
* />
* ```
*/
export const Model3D = forwardRef(function Model3D(
props: Model3DProps,
ref: Ref<google.maps.maps3d.Model3DElement>
) {
const {position, altitudeMode, src, orientation, scale} = props;

return (
<gmp-model-3d
ref={ref}
position={position}
altitude-mode={altitudeMode}
src={src}
orientation={orientation}
scale={scale}
/>
);
});

Model3D.displayName = 'Model3D';
18 changes: 18 additions & 0 deletions examples/map-3d-markers/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"typeRoots": ["./types", "../../types", "./node_modules/@types"],
"strict": true,
"sourceMap": true,
"noEmit": true,
"noImplicitAny": true,
"module": "ESNext",
"moduleResolution": "bundler",
"esModuleInterop": true,
"target": "ES2020",
"lib": ["es2020", "dom"],
"jsx": "react",
"skipLibCheck": true
},
"exclude": ["./dist", "./node_modules"],
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*.ts"]
}
7 changes: 7 additions & 0 deletions examples/map-3d-markers/types/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export declare global {
// const or let does not work in this case, it has to be var
// eslint-disable-next-line no-var
var GOOGLE_MAPS_API_KEY: string | undefined;
// eslint-disable-next-line no-var, @typescript-eslint/no-explicit-any
var process: any;
}
Loading