Skip to content

Commit 22ddcea

Browse files
committed
Refactor DeckGL globe with modular architecture
Extract monolithic DeckGL index.tsx into focused, reusable modules for better maintainability and code organization: Modular Structure: - Create layers.tsx with backgroundLayers and dataLayers factory functions - Add lighting.ts for centralized lighting effect configuration - Extract tooltip.ts with configurable tooltip generation utility - Add types.ts for shared TypeScript interfaces and type definitions - Consolidate INITIAL_VIEW_STATE constant in GlobeContext for consistency - Improve import organization and reduce file complexity from 280+ to 150 lines Globe Optimizations: - Improve cursor handling with isDragging state - Increase rotation speed and revert direction This refactoring maintains all existing functionality while establishing a cleaner foundation for future DeckGL globe enhancements and reduces cognitive load for developers working with the codebase.
1 parent 19ae584 commit 22ddcea

File tree

7 files changed

+210
-178
lines changed

7 files changed

+210
-178
lines changed

src/deckgl/contexts/GlobeContext.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ import type { GlobeViewState } from '@deck.gl/core';
33

44
export type RotationState = 'rotating' | 'paused' | 'stopped';
55

6+
export const INITIAL_VIEW_STATE: GlobeViewState = {
7+
longitude: -70,
8+
latitude: 20,
9+
zoom: 2
10+
};
11+
612
interface DeckGLGlobeContextType {
713
rotationState: RotationState;
814
setRotationState: (state: RotationState) => void;
@@ -15,7 +21,7 @@ interface DeckGLGlobeContextType {
1521
export const DeckGLGlobeContext = createContext<DeckGLGlobeContextType>({
1622
rotationState: 'rotating',
1723
setRotationState: () => {},
18-
viewState: { longitude: 0, latitude: 20, zoom: 2 },
24+
viewState: INITIAL_VIEW_STATE,
1925
setViewState: () => {},
2026
isInteracting: false,
2127
setIsInteracting: () => {}

src/deckgl/contexts/GlobeProvider.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import React, { useState, useCallback, type ReactNode } from 'react';
22
import type { GlobeViewState } from '@deck.gl/core';
3-
import { DeckGLGlobeContext, type RotationState } from './GlobeContext';
4-
5-
const INITIAL_VIEW_STATE: GlobeViewState = {
6-
longitude: 0,
7-
latitude: 20,
8-
zoom: 2
9-
};
3+
import {
4+
DeckGLGlobeContext,
5+
INITIAL_VIEW_STATE,
6+
type RotationState
7+
} from './GlobeContext';
108

119
export const DeckGLGlobeProvider: React.FC<{ children: ReactNode }> = ({
1210
children

src/deckgl/index.tsx

Lines changed: 16 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -8,47 +8,16 @@ import {
88
} from 'react';
99

1010
import { DeckGL } from '@deck.gl/react';
11-
import {
12-
COORDINATE_SYSTEM,
13-
_GlobeView as GlobeView,
14-
LightingEffect,
15-
AmbientLight,
16-
_SunLight as SunLight
17-
} from '@deck.gl/core';
18-
import { GeoJsonLayer, ScatterplotLayer } from '@deck.gl/layers';
19-
import { SimpleMeshLayer } from '@deck.gl/mesh-layers';
20-
import { TileLayer } from '@deck.gl/geo-layers';
21-
import { BitmapLayer } from '@deck.gl/layers';
22-
import { SphereGeometry } from '@luma.gl/engine';
11+
import { _GlobeView as GlobeView } from '@deck.gl/core';
2312

24-
import type { GlobeViewState, LayerProps, PickingInfo } from '@deck.gl/core';
13+
import type { GlobeViewState, PickingInfo } from '@deck.gl/core';
2514
import { DeckGLGlobeContext } from './contexts/GlobeContext';
2615
import { DeckGLGlobeProvider } from './contexts/GlobeProvider';
2716
import { RotationControlButton } from '../components/RotationControlButton';
28-
import { markers } from '../data/markers';
29-
import { getTileUrl } from '../r3f/utils/tiles';
30-
31-
// Type definitions for our data
32-
interface MarkerData {
33-
id: string;
34-
name: string;
35-
lat: number;
36-
lon: number;
37-
}
38-
39-
const EARTH_RADIUS_METERS = 6.3e6;
40-
41-
const ambientLight = new AmbientLight({
42-
color: [255, 255, 255],
43-
intensity: 0.5
44-
});
45-
const sunLight = new SunLight({
46-
color: [255, 255, 255],
47-
intensity: 2.0,
48-
timestamp: 0
49-
});
50-
// create lighting effect with light sources
51-
const lightingEffect = new LightingEffect({ ambientLight, sunLight });
17+
import { backgroundLayers, dataLayers } from './layers';
18+
import { lightingEffect } from './lighting';
19+
import type { MarkerData } from './types';
20+
import { getTooltip } from './tooltip';
5221

5322
function DeckGLGlobeInner() {
5423
const {
@@ -75,7 +44,7 @@ function DeckGLGlobeInner() {
7544
// ~60fps
7645
setViewState({
7746
...viewState,
78-
longitude: (viewState.longitude + 0.1) % 360
47+
longitude: (viewState.longitude - 0.3) % 360
7948
});
8049
lastTimeRef.current = time;
8150
}
@@ -155,112 +124,11 @@ function DeckGLGlobeInner() {
155124
[rotationState, setRotationState, setIsInteracting]
156125
);
157126

158-
const backgroundLayers = useMemo(
159-
() => [
160-
new SimpleMeshLayer({
161-
id: 'earth-sphere',
162-
data: [0],
163-
mesh: new SphereGeometry({
164-
radius: EARTH_RADIUS_METERS,
165-
nlat: 18,
166-
nlong: 36
167-
}),
168-
coordinateSystem: COORDINATE_SYSTEM.CARTESIAN,
169-
getPosition: [0, 0, 0],
170-
getColor: [255, 255, 255]
171-
}),
172-
173-
// Earth coastline layer
174-
new GeoJsonLayer({
175-
id: 'coastline',
176-
data: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_50m_coastline.geojson',
177-
// Styles
178-
stroked: true,
179-
filled: false,
180-
opacity: 1.0,
181-
getLineColor: [30, 80, 120, 255],
182-
getLineWidth: 1,
183-
lineWidthUnits: 'pixels',
184-
lineWidthMinPixels: 1,
185-
lineWidthMaxPixels: 3
186-
})
187-
],
188-
[]
189-
);
190-
191-
const dataLayers = useMemo(
192-
() => [
193-
// Tile layer with VEDA data
194-
new TileLayer({
195-
id: 'veda-tile-layer',
196-
getTileData: ({
197-
index
198-
}: {
199-
index: { x: number; y: number; z: number };
200-
}) => {
201-
const { x, y, z } = index;
202-
const url = getTileUrl(x, y, z);
203-
return fetch(url)
204-
.then((response) => response.blob())
205-
.then((blob) => {
206-
return new Promise<HTMLImageElement>((resolve) => {
207-
const img = new Image();
208-
img.onload = () => resolve(img);
209-
img.src = URL.createObjectURL(blob);
210-
});
211-
});
212-
},
213-
maxZoom: 4,
214-
minZoom: 0,
215-
tileSize: 256,
216-
renderSubLayers: (
217-
props: LayerProps & {
218-
tile: { boundingBox: [number[], number[]] };
219-
data: HTMLImageElement;
220-
}
221-
) => {
222-
const { tile, data } = props;
223-
const { boundingBox } = tile;
224-
return new BitmapLayer({
225-
...props,
226-
data: undefined,
227-
image: data,
228-
bounds: [
229-
boundingBox[0][0], // west
230-
boundingBox[0][1], // south
231-
boundingBox[1][0], // east
232-
boundingBox[1][1] // north
233-
]
234-
});
235-
}
236-
}),
127+
const memoizedBackgroundLayers = useMemo(() => backgroundLayers, []);
237128

238-
// Markers layer
239-
new ScatterplotLayer({
240-
id: 'markers-layer',
241-
data: markers,
242-
getPosition: (marker: MarkerData) => [marker.lon, marker.lat, 0],
243-
getRadius: 50000, // radius in meters
244-
getFillColor: [255, 100, 100, 200],
245-
getLineColor: [255, 255, 255],
246-
getLineWidth: 10000,
247-
radiusMinPixels: 5,
248-
radiusMaxPixels: 15,
249-
lineWidthMinPixels: 1,
250-
lineWidthMaxPixels: 3,
251-
stroked: true,
252-
filled: true,
253-
pickable: true,
254-
onClick: handleMarkerClick,
255-
onHover: (info) => {
256-
setIsMarkerHovered(!!info.object);
257-
},
258-
updateTriggers: {
259-
getRadius: rotationState // Re-render when rotation state changes for hover effects
260-
}
261-
})
262-
],
263-
[handleMarkerClick, rotationState]
129+
const memoizedDataLayers = useMemo(
130+
() => dataLayers(handleMarkerClick, rotationState, setIsMarkerHovered),
131+
[handleMarkerClick, rotationState, setIsMarkerHovered]
264132
);
265133

266134
return (
@@ -272,33 +140,11 @@ function DeckGLGlobeInner() {
272140
onInteractionStateChange={handleInteractionStateChange}
273141
controller={true}
274142
effects={[lightingEffect]}
275-
layers={[...backgroundLayers, ...dataLayers]}
276-
getCursor={({ isHovering }) => {
277-
if (isMarkerHovered) {
278-
return 'pointer';
279-
}
280-
return isHovering ? 'grab' : 'default';
281-
}}
282-
getTooltip={(info) => {
283-
const { object, layer } = info as {
284-
object: MarkerData | null;
285-
layer: { id: string } | null;
286-
};
287-
if (layer?.id === 'markers-layer' && object) {
288-
return {
289-
html: `<div style="background: rgba(0,0,0,0.8); color: white; padding: 8px; border-radius: 4px;">
290-
<strong>${object.name}</strong><br/>
291-
Lat: ${object.lat.toFixed(4)}<br/>
292-
Lon: ${object.lon.toFixed(4)}
293-
</div>`,
294-
style: {
295-
fontSize: '12px',
296-
pointerEvents: 'none'
297-
}
298-
};
299-
}
300-
return null;
301-
}}
143+
layers={[...memoizedBackgroundLayers, ...memoizedDataLayers]}
144+
getCursor={({ isDragging }) =>
145+
isMarkerHovered ? 'pointer' : isDragging ? 'grabbing' : 'grab'
146+
}
147+
getTooltip={getTooltip('markers-layer')}
302148
/>
303149
<RotationControlButton
304150
rotationState={rotationState}

src/deckgl/layers.tsx

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { COORDINATE_SYSTEM } from '@deck.gl/core';
2+
import { GeoJsonLayer, ScatterplotLayer } from '@deck.gl/layers';
3+
import { SimpleMeshLayer } from '@deck.gl/mesh-layers';
4+
import { TileLayer } from '@deck.gl/geo-layers';
5+
import { BitmapLayer } from '@deck.gl/layers';
6+
import { SphereGeometry } from '@luma.gl/engine';
7+
8+
import { markers } from '../data/markers';
9+
import { getTileUrl } from '../r3f/utils/tiles';
10+
11+
import type {
12+
DataLayersProps,
13+
MarkerData,
14+
RenderSubLayersProps,
15+
TileLayerProps
16+
} from './types';
17+
18+
export const EARTH_RADIUS_METERS = 6.3e6;
19+
20+
export const backgroundLayers = [
21+
new SimpleMeshLayer({
22+
id: 'earth-sphere',
23+
data: [0],
24+
mesh: new SphereGeometry({
25+
radius: EARTH_RADIUS_METERS,
26+
nlat: 18,
27+
nlong: 36
28+
}),
29+
coordinateSystem: COORDINATE_SYSTEM.CARTESIAN,
30+
getPosition: [0, 0, 0],
31+
getColor: [255, 255, 255]
32+
}),
33+
34+
// Earth coastline layer
35+
new GeoJsonLayer({
36+
id: 'coastline',
37+
data: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_50m_coastline.geojson',
38+
// Styles
39+
stroked: true,
40+
filled: false,
41+
opacity: 1.0,
42+
getLineColor: [30, 80, 120, 255],
43+
getLineWidth: 1,
44+
lineWidthUnits: 'pixels',
45+
lineWidthMinPixels: 1,
46+
lineWidthMaxPixels: 3
47+
})
48+
];
49+
50+
export const dataLayers = (
51+
handleMarkerClick: DataLayersProps['handleMarkerClick'],
52+
rotationState: DataLayersProps['rotationState'],
53+
setIsMarkerHovered: DataLayersProps['setIsMarkerHovered']
54+
) => [
55+
// Tile layer with VEDA data
56+
new TileLayer({
57+
id: 'veda-tile-layer',
58+
getTileData: ({ index }: TileLayerProps) => {
59+
const { x, y, z } = index;
60+
const url = getTileUrl(x, y, z);
61+
return fetch(url)
62+
.then((response) => response.blob())
63+
.then((blob) => {
64+
return new Promise<HTMLImageElement>((resolve) => {
65+
const img = new Image();
66+
img.onload = () => resolve(img);
67+
img.src = URL.createObjectURL(blob);
68+
});
69+
});
70+
},
71+
maxZoom: 4,
72+
minZoom: 0,
73+
tileSize: 256,
74+
renderSubLayers: (props: RenderSubLayersProps) => {
75+
const { tile, data } = props;
76+
const { boundingBox } = tile;
77+
return new BitmapLayer({
78+
...props,
79+
data: undefined,
80+
image: data,
81+
bounds: [
82+
boundingBox[0][0], // west
83+
boundingBox[0][1], // south
84+
boundingBox[1][0], // east
85+
boundingBox[1][1] // north
86+
]
87+
});
88+
}
89+
}),
90+
91+
// Markers layer
92+
new ScatterplotLayer({
93+
id: 'markers-layer',
94+
data: markers,
95+
getPosition: (marker: MarkerData) => [marker.lon, marker.lat, 0],
96+
getRadius: 50000, // radius in meters
97+
getFillColor: [255, 100, 100, 200],
98+
getLineColor: [255, 255, 255],
99+
getLineWidth: 10000,
100+
radiusMinPixels: 5,
101+
radiusMaxPixels: 15,
102+
lineWidthMinPixels: 1,
103+
lineWidthMaxPixels: 3,
104+
stroked: true,
105+
filled: true,
106+
pickable: true,
107+
onClick: handleMarkerClick,
108+
onHover: (info) => {
109+
setIsMarkerHovered(!!info.object);
110+
},
111+
updateTriggers: {
112+
getRadius: rotationState // Re-render when rotation state changes for hover effects
113+
}
114+
})
115+
];

src/deckgl/lighting.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import {
2+
LightingEffect,
3+
AmbientLight,
4+
_SunLight as SunLight
5+
} from '@deck.gl/core';
6+
7+
const ambientLight = new AmbientLight({
8+
color: [255, 255, 255],
9+
intensity: 0.5
10+
});
11+
const sunLight = new SunLight({
12+
color: [255, 255, 255],
13+
intensity: 2.0,
14+
timestamp: 0
15+
});
16+
17+
// create lighting effect with light sources
18+
export const lightingEffect = new LightingEffect({ ambientLight, sunLight });

0 commit comments

Comments
 (0)