Skip to content

Commit

Permalink
[react] MapboxOverlay-ready hooks and basic-basemap example (#288)
Browse files Browse the repository at this point in the history
* Update useHubbleGl hook

* use maplibre in basic basemap example
  • Loading branch information
chrisgervang authored Jan 31, 2025
1 parent c2e8194 commit 294ef5f
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 86 deletions.
60 changes: 42 additions & 18 deletions examples/website/basic-basemap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,7 @@ This is a basic basemap + deck.gl data animation utilizing [react-map-gl](https:

### Usage

Copy the content of this folder to your project.

To see the base map, you need a [Mapbox access token](https://docs.mapbox.com/help/how-mapbox-works/access-tokens/). You can either set an environment variable:

```bash
export MapboxAccessToken=<mapbox_access_token>
```

Or set `MAPBOX_TOKEN` directly in `app.js`.
Copy the content of this folder to your project.

Other options can be found at [using with Mapbox GL](https://deck.gl/docs/developer-guide/base-maps/using-with-mapbox).

Expand Down Expand Up @@ -41,9 +33,9 @@ import {useDeckAnimation, useHubbleGl} from '@hubble.gl/react';

const initialViewState = {...};

function Map() {
function Visualization() {
const deckRef = useRef(null);
const staticMapRef = useRef(null);
const mapRef = useRef(null);
const deckAnimation = useDeckAnimation({
getLayers: a =>
a.applyLayerKeyframes([
Expand All @@ -55,13 +47,13 @@ function Map() {

const {
deckProps,
staticMapProps, // optional, use for basemap
mapProps, // optional, use for basemap
adapter, // optional, use to modify animation at run time
cameraFrame, // optional, use for camera animation
setCameraFrame // optional, use for camera animation
} = useHubbleGl({
deckRef,
staticMapRef, // optional, use for basemap
mapRef, // optional, use for basemap
deckAnimation,
initialViewState // optional, use for camera animation
});
Expand All @@ -88,9 +80,41 @@ const timecode = {
};
```

3. Add to props of the `DeckGl ` and `StaticMap` component
3. Define an interleaved deck.gl MapboxOverlay

```jsx
import {forwardRef} from 'react';
import Map, {useControl} from 'react-map-gl';
import {MapboxOverlay} from '@deck.gl/mapbox';

const DeckGLOverlay = forwardRef((props, ref) => {
// MapboxOverlay handles a variety of props differently than the Deck class.
// https://deck.gl/docs/api-reference/mapbox/mapbox-overlay#constructor
const deck = useControl(() => new MapboxOverlay({...props, interleaved: true}));
deck.setProps(props);
ref.current = deck._deck;
return null;
});
```


3. Add to props of the `DeckGl ` and `Map` component

```jsx
<Map
ref={mapRef}
{...cameraFrame}
style={{width: resolution.width, height: resolution.height}}
{/* add your props before spreading hubble props */}
{...mapProps}
>
<DeckGLOverlay
ref={deckRef}
{/* add your props before spreading hubble props */}
{...deckProps}
/>
</Map>

<DeckGL
ref={deckRef}
viewState={cameraFrame}
Expand All @@ -101,11 +125,11 @@ const timecode = {
{...deckProps}
>
{/* optional base map */}
{staticMapProps.gl && (
<StaticMap
ref={staticMapRef}
{mapProps.gl && (
<Map
ref={mapRef}
{/* add your props before spreading hubble props */}
{...staticMapProps}
{...mapProps}
/>
)}
</DeckGL>
Expand Down
60 changes: 35 additions & 25 deletions examples/website/basic-basemap/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
* Source code: https://github.com/visgl/deck.gl/tree/master/examples/website/trips
*/

import React, {useState, useRef, useEffect} from 'react';
import React, {useState, useRef, useEffect, forwardRef} from 'react';
import {createRoot} from 'react-dom/client';
import DeckGL from '@deck.gl/react';
import {BasicControls, useHubbleGl, useDeckAnimation} from '@hubble.gl/react';
import {StaticMap} from 'react-map-gl';
import {MapboxOverlay} from '@deck.gl/mapbox';
import Map, {useControl} from 'react-map-gl';
import {PolygonLayer} from '@deck.gl/layers';
import {easeInOut} from 'popmotion';
import maplibregl from 'maplibre-gl';
import {setRef} from './set-ref';

// Source data CSV
const BUILDINGS =
Expand Down Expand Up @@ -71,6 +73,18 @@ const timecode = {
framerate: 30
};

const DeckGLOverlay = forwardRef((props, ref) => {
// MapboxOverlay handles a variety of props differently than the Deck class.
// https://deck.gl/docs/api-reference/mapbox/mapbox-overlay#constructor
const deck = useControl(() => new MapboxOverlay({...props, interleaved: true}));

deck.setProps(props);

// @ts-expect-error private property
setRef(ref, deck._deck);
return null;
});

const Container = ({children}) => (
<div
style={{
Expand All @@ -94,9 +108,9 @@ const randomColor = () => [
Math.floor(Math.random() * 255)
];

export default function App({mapStyle = 'mapbox://styles/mapbox/streets-v11'}) {
export default function App({mapStyle = 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json'}) {
const deckRef = useRef(null);
const staticMapRef = useRef(null);
const mapRef = useRef(null);
const [busy, setBusy] = useState(false);

const deckAnimation = useDeckAnimation({
Expand Down Expand Up @@ -133,9 +147,9 @@ export default function App({mapStyle = 'mapbox://styles/mapbox/streets-v11'}) {
]
});

const {deckProps, staticMapProps, adapter, cameraFrame, setCameraFrame} = useHubbleGl({
const {deckProps, mapProps, adapter, cameraFrame, setCameraFrame} = useHubbleGl({
deckRef,
staticMapRef,
mapRef,
deckAnimation,
initialViewState: START
});
Expand All @@ -146,25 +160,21 @@ export default function App({mapStyle = 'mapbox://styles/mapbox/streets-v11'}) {
return (
<Container>
<div style={{position: 'relative'}}>
<DeckGL
ref={deckRef}
style={{position: 'unset'}}
controller={true}
viewState={cameraFrame}
onViewStateChange={onViewStateChange}
width={resolution.width}
height={resolution.height}
{...deckProps}
<Map
ref={mapRef}
mapStyle={mapStyle}
{...mapProps}
{...cameraFrame}
style={{width: resolution.width, height: resolution.height}}
onMove={onViewStateChange}
mapLib={maplibregl}
// Note: 'reuseMap' prop with gatsby and mapbox extension causes stale reference error.
>
{staticMapProps.gl && (
<StaticMap
ref={staticMapRef}
mapStyle={mapStyle}
{...staticMapProps}
// Note: 'reuseMap' prop with gatsby and mapbox extension causes stale reference error.
/>
)}
</DeckGL>
<DeckGLOverlay
ref={deckRef}
{...deckProps}
/>
</Map>
</div>
<BasicControls
adapter={adapter}
Expand Down
11 changes: 3 additions & 8 deletions examples/website/basic-basemap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,15 @@
},
"dependencies": {
"deck.gl": "^8.9",
"hubble.gl": "^1.3.0",
"hubble.gl": "^1.4.0-alpha.0",
"d3-color": "^1.4.1",
"react": "^18.3.0",
"react-dom": "^18.3.0",
"react-map-gl": "^5.0.0",
"react-map-gl": "^7.1.8",
"maplibre-gl": "^3.6.2",
"popmotion": "9.3.1"
},
"devDependencies": {
"vite": "^4.0.0"
},
"resolutions_comments": [
"mapbox-gl: pinned to v1 for open license"
],
"resolutions": {
"mapbox-gl": "^1.13.0"
}
}
11 changes: 11 additions & 0 deletions examples/website/basic-basemap/set-ref.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type {ForwardedRef} from 'react';

// Helper for covering all of the typescript cases for setting a ref
export function setRef<T>(ref: ForwardedRef<T>, value: T) {
if (!ref) return;
if (typeof ref === 'function') {
ref(value);
} else {
ref.current = value;
}
}
5 changes: 0 additions & 5 deletions examples/website/basic-basemap/vite.config.js

This file was deleted.

44 changes: 14 additions & 30 deletions modules/react/src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@

import {useState, useCallback, useMemo, RefObject} from 'react';
import {DeckAdapter, DeckAnimation, DeckAnimationConstructor} from '@hubble.gl/core';
import {MapboxLayer} from '@deck.gl/mapbox/typed';
import type {Layer, MapViewState} from '@deck.gl/core/typed';
import type {DeckGLRef} from '@deck.gl/react/typed';
import type {Layer, MapViewState, Deck} from '@deck.gl/core/typed';
import type {MapRef} from 'react-map-gl';

export function useNextFrame() {
Expand Down Expand Up @@ -39,47 +37,35 @@ export function useDeckAnimation(params: DeckAnimationConstructor) {

export function useHubbleGl({
deckRef,
staticMapRef = undefined,
mapRef = undefined,
deckAnimation,
initialViewState = undefined
}: {
deckRef: RefObject<DeckGLRef>;
staticMapRef?: RefObject<MapRef>;
deckRef: RefObject<Deck>;
mapRef?: RefObject<MapRef>;
deckAnimation: DeckAnimation;
initialViewState?: MapViewState;
}) {
const deck = useMemo(() => deckRef.current && deckRef.current.deck, [deckRef.current]);
const deck = useMemo(() => deckRef.current, [deckRef.current]);
const nextFrame = useNextFrame();
const {adapter, layers, cameraFrame, setCameraFrame} = useDeckAdapter(
deckAnimation,
initialViewState
);

const onStaticMapLoad = useCallback(() => {
if (staticMapRef) {
const map = staticMapRef.current.getMap();
// If there aren't any layers, combine map and deck with a fake layer.
if (!layers.length) {
// @ts-expect-error maplibre and mapbox have different types
map.addLayer(new MapboxLayer({id: '%%blank-layer', deck}));
}
for (let i = 0; i < layers.length; i++) {
// Adds DeckGL layers to Mapbox so Mapbox can be the bottom layer. Removing this clips DeckGL layers
// @ts-expect-error maplibre and mapbox have different types
map.addLayer(new MapboxLayer({id: layers[i].id, deck}));
}
const onMapLoad = useCallback(() => {
if (mapRef) {
const map = mapRef.current.getMap();
map.on('render', () => adapter.onAfterRender(nextFrame, map.areTilesLoaded()));
}
}, [deck]);

const [glContext, setGLContext] = useState<WebGLRenderingContext>();
}, [adapter, nextFrame]);

if (!staticMapRef) {
if (!mapRef) {
return {
adapter,
cameraFrame,
setCameraFrame,
staticMapProps: {},
mapProps: {},
deckProps: adapter.getProps({
deck,
onNextFrame: nextFrame,
Expand All @@ -94,16 +80,14 @@ export function useHubbleGl({
adapter,
cameraFrame,
setCameraFrame,
onStaticMapLoad,
staticMapProps: {
gl: glContext,
onLoad: onStaticMapLoad,
onMapLoad,
mapProps: {
onLoad: onMapLoad,
preventStyleDiffing: true
},
deckProps: adapter.getProps({
deck,
extraProps: {
onWebGLInitialized: setGLContext,
layers
}
})
Expand Down

0 comments on commit 294ef5f

Please sign in to comment.