-
Notifications
You must be signed in to change notification settings - Fork 39
feat: Support two render modes: Standard/deck.gl-first and MapboxOverlay #921
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -2,11 +2,9 @@ import * as React from "react"; | |||||||||||
import { useEffect, useCallback, useState, useRef } from "react"; | ||||||||||||
import { createRender, useModelState, useModel } from "@anywidget/react"; | ||||||||||||
import type { Initialize, Render } from "@anywidget/types"; | ||||||||||||
import Map from "react-map-gl/maplibre"; | ||||||||||||
import DeckGL from "@deck.gl/react"; | ||||||||||||
import { MapViewState, PickingInfo, type Layer } from "@deck.gl/core"; | ||||||||||||
import { BaseLayerModel, initializeLayer } from "./model/index.js"; | ||||||||||||
import type { WidgetModel } from "@jupyter-widgets/base"; | ||||||||||||
import type { IWidgetManager, WidgetModel } from "@jupyter-widgets/base"; | ||||||||||||
import { initParquetWasm } from "./parquet.js"; | ||||||||||||
import { isDefined, loadChildModels } from "./util.js"; | ||||||||||||
import { v4 as uuidv4 } from "uuid"; | ||||||||||||
|
@@ -25,6 +23,9 @@ import throttle from "lodash.throttle"; | |||||||||||
import SidePanel from "./sidepanel/index"; | ||||||||||||
import { getTooltip } from "./tooltip/index.js"; | ||||||||||||
import { DeckGLRef } from "@deck.gl/react"; | ||||||||||||
import OverlayRenderer from "./renderers/overlay.js"; | ||||||||||||
import { MapRendererProps } from "./renderers/types.js"; | ||||||||||||
import DeckFirstRenderer from "./renderers/deck-first.js"; | ||||||||||||
|
||||||||||||
await initParquetWasm(); | ||||||||||||
|
||||||||||||
|
@@ -116,6 +117,7 @@ function App() { | |||||||||||
); | ||||||||||||
const [parameters] = useModelState<object>("parameters"); | ||||||||||||
const [customAttribution] = useModelState<string>("custom_attribution"); | ||||||||||||
const [renderMode] = useModelState<string>("render_mode"); | ||||||||||||
|
||||||||||||
// initialViewState is the value of view_state on the Python side. This is | ||||||||||||
// called `initial` here because it gets passed in to deck's | ||||||||||||
|
@@ -156,7 +158,7 @@ function App() { | |||||||||||
const loadAndUpdateLayers = async () => { | ||||||||||||
try { | ||||||||||||
const childModels = await loadChildModels( | ||||||||||||
model.widget_manager, | ||||||||||||
model.widget_manager as IWidgetManager, | ||||||||||||
childLayerIds, | ||||||||||||
); | ||||||||||||
|
||||||||||||
|
@@ -229,6 +231,45 @@ function App() { | |||||||||||
[isOnMapHoverEventEnabled, justClicked], | ||||||||||||
); | ||||||||||||
|
||||||||||||
const mapRenderProps: MapRendererProps = { | ||||||||||||
mapStyle: mapStyle || DEFAULT_MAP_STYLE, | ||||||||||||
customAttribution, | ||||||||||||
deckRef, | ||||||||||||
initialViewState: ["longitude", "latitude", "zoom"].every((key) => | ||||||||||||
Object.keys(initialViewState).includes(key), | ||||||||||||
) | ||||||||||||
? initialViewState | ||||||||||||
: DEFAULT_INITIAL_VIEW_STATE, | ||||||||||||
layers: bboxSelectPolygonLayer | ||||||||||||
? layers.concat(bboxSelectPolygonLayer) | ||||||||||||
: layers, | ||||||||||||
getTooltip: (showTooltip && getTooltip) || undefined, | ||||||||||||
getCursor: () => (isDrawingBBoxSelection ? "crosshair" : "grab"), | ||||||||||||
pickingRadius: pickingRadius, | ||||||||||||
onClick: onMapClickHandler, | ||||||||||||
onHover: onMapHoverHandler, | ||||||||||||
// @ts-expect-error useDevicePixels should allow number | ||||||||||||
// https://github.com/visgl/deck.gl/pull/9826 | ||||||||||||
useDevicePixels: isDefined(useDevicePixels) ? useDevicePixels : true, | ||||||||||||
Comment on lines
+251
to
+253
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nitpick] Consider using a more specific type assertion instead of @ts-expect-error to make the intent clearer and safer.
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||||||||
onViewStateChange: (event) => { | ||||||||||||
const { viewState } = event; | ||||||||||||
|
||||||||||||
// This condition is necessary to confirm that the viewState is | ||||||||||||
// of type MapViewState. | ||||||||||||
if ("latitude" in viewState) { | ||||||||||||
const { longitude, latitude, zoom, pitch, bearing } = viewState; | ||||||||||||
setViewState({ | ||||||||||||
longitude, | ||||||||||||
latitude, | ||||||||||||
zoom, | ||||||||||||
pitch, | ||||||||||||
bearing, | ||||||||||||
}); | ||||||||||||
} | ||||||||||||
}, | ||||||||||||
parameters: parameters || {}, | ||||||||||||
}; | ||||||||||||
|
||||||||||||
return ( | ||||||||||||
<div | ||||||||||||
className="lonboard" | ||||||||||||
|
@@ -252,58 +293,11 @@ function App() { | |||||||||||
/> | ||||||||||||
)} | ||||||||||||
<div className="bg-red-800 h-full w-full relative"> | ||||||||||||
<DeckGL | ||||||||||||
ref={deckRef} | ||||||||||||
style={{ width: "100%", height: "100%" }} | ||||||||||||
initialViewState={ | ||||||||||||
["longitude", "latitude", "zoom"].every((key) => | ||||||||||||
Object.keys(initialViewState).includes(key), | ||||||||||||
) | ||||||||||||
? initialViewState | ||||||||||||
: DEFAULT_INITIAL_VIEW_STATE | ||||||||||||
} | ||||||||||||
controller={true} | ||||||||||||
layers={ | ||||||||||||
bboxSelectPolygonLayer | ||||||||||||
? layers.concat(bboxSelectPolygonLayer) | ||||||||||||
: layers | ||||||||||||
} | ||||||||||||
getTooltip={(showTooltip && getTooltip) || undefined} | ||||||||||||
getCursor={() => (isDrawingBBoxSelection ? "crosshair" : "grab")} | ||||||||||||
pickingRadius={pickingRadius} | ||||||||||||
onClick={onMapClickHandler} | ||||||||||||
onHover={onMapHoverHandler} | ||||||||||||
useDevicePixels={ | ||||||||||||
isDefined(useDevicePixels) ? useDevicePixels : true | ||||||||||||
} | ||||||||||||
// https://deck.gl/docs/api-reference/core/deck#_typedarraymanagerprops | ||||||||||||
_typedArrayManagerProps={{ | ||||||||||||
overAlloc: 1, | ||||||||||||
poolSize: 0, | ||||||||||||
}} | ||||||||||||
onViewStateChange={(event) => { | ||||||||||||
const { viewState } = event; | ||||||||||||
|
||||||||||||
// This condition is necessary to confirm that the viewState is | ||||||||||||
// of type MapViewState. | ||||||||||||
if ("latitude" in viewState) { | ||||||||||||
const { longitude, latitude, zoom, pitch, bearing } = viewState; | ||||||||||||
setViewState({ | ||||||||||||
longitude, | ||||||||||||
latitude, | ||||||||||||
zoom, | ||||||||||||
pitch, | ||||||||||||
bearing, | ||||||||||||
}); | ||||||||||||
} | ||||||||||||
}} | ||||||||||||
parameters={parameters || {}} | ||||||||||||
> | ||||||||||||
<Map | ||||||||||||
mapStyle={mapStyle || DEFAULT_MAP_STYLE} | ||||||||||||
customAttribution={customAttribution} | ||||||||||||
></Map> | ||||||||||||
</DeckGL> | ||||||||||||
{renderMode === "overlay" ? ( | ||||||||||||
<OverlayRenderer {...mapRenderProps} /> | ||||||||||||
) : ( | ||||||||||||
<DeckFirstRenderer {...mapRenderProps} /> | ||||||||||||
)} | ||||||||||||
</div> | ||||||||||||
</div> | ||||||||||||
</div> | ||||||||||||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import DeckGL from "@deck.gl/react"; | ||
import React from "react"; | ||
import Map from "react-map-gl/maplibre"; | ||
import type { MapRendererProps } from "./types"; | ||
|
||
/** | ||
* DeckFirst renderer: DeckGL wraps Map component | ||
* | ||
* In this rendering mode, deck.gl is the parent component that manages | ||
* the canvas and view state, with the map rendered as a child component. | ||
* This is the traditional approach where deck.gl has full control over | ||
* the rendering pipeline. | ||
*/ | ||
const DeckFirstRenderer: React.FC<MapRendererProps> = (mapProps) => { | ||
// Remove maplibre-specific props before passing to DeckGL | ||
const { mapStyle, customAttribution, deckRef, ...deckProps } = mapProps; | ||
return ( | ||
<DeckGL | ||
ref={deckRef} | ||
style={{ width: "100%", height: "100%" }} | ||
controller={true} | ||
// https://deck.gl/docs/api-reference/core/deck#_typedarraymanagerprops | ||
_typedArrayManagerProps={{ | ||
overAlloc: 1, | ||
poolSize: 0, | ||
}} | ||
{...deckProps} | ||
> | ||
<Map mapStyle={mapStyle} customAttribution={customAttribution}></Map> | ||
</DeckGL> | ||
); | ||
}; | ||
|
||
export default DeckFirstRenderer; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export { default as DeckFirst } from "./deck-first"; | ||
export { default as Overlay } from "./overlay"; | ||
export type { MapRendererProps } from "./types"; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import React from "react"; | ||
import Map, { useControl } from "react-map-gl/maplibre"; | ||
import { MapboxOverlay, MapboxOverlayProps } from "@deck.gl/mapbox"; | ||
import type { MapRendererProps } from "./types"; | ||
|
||
/** | ||
* DeckGLOverlay component that integrates deck.gl with react-map-gl | ||
* | ||
* Uses the useControl hook to create a MapboxOverlay instance that | ||
* renders deck.gl layers on top of the base map. | ||
*/ | ||
function DeckGLOverlay(props: MapboxOverlayProps) { | ||
const overlay = useControl(() => new MapboxOverlay(props)); | ||
overlay.setProps(props); | ||
return null; | ||
} | ||
|
||
/** | ||
* Overlay renderer: Map wraps DeckGLOverlay component | ||
* | ||
* In this rendering mode, the map is the parent component that controls | ||
* the view state, with deck.gl layers rendered as an overlay using the | ||
* MapboxOverlay. This approach gives the base map more control and can | ||
* enable features like interleaved rendering between map and deck layers. | ||
*/ | ||
const OverlayRenderer: React.FC<MapRendererProps> = (mapProps) => { | ||
// Remove maplibre-specific props before passing to DeckGL | ||
const { mapStyle, customAttribution, initialViewState, ...deckProps } = | ||
mapProps; | ||
return ( | ||
<Map | ||
reuseMaps | ||
initialViewState={initialViewState} | ||
mapStyle={mapStyle} | ||
attributionControl={{ customAttribution }} | ||
style={{ width: "100%", height: "100%" }} | ||
> | ||
<DeckGLOverlay | ||
// https://deck.gl/docs/api-reference/core/deck#_typedarraymanagerprops | ||
_typedArrayManagerProps={{ | ||
overAlloc: 1, | ||
poolSize: 0, | ||
}} | ||
{...deckProps} | ||
/> | ||
</Map> | ||
); | ||
}; | ||
|
||
export default OverlayRenderer; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import type { DeckProps, View } from "@deck.gl/core"; | ||
import type { DeckGLRef } from "@deck.gl/react"; | ||
import type { RefObject } from "react"; | ||
|
||
type ViewOrViews = View | View[] | null; | ||
export type MapRendererProps<ViewsT extends ViewOrViews = null> = Pick< | ||
DeckProps<ViewsT>, | ||
| "getCursor" | ||
| "getTooltip" | ||
| "initialViewState" | ||
| "layers" | ||
| "onClick" | ||
| "onHover" | ||
| "onViewStateChange" | ||
| "parameters" | ||
| "pickingRadius" | ||
| "useDevicePixels" | ||
> & { | ||
mapStyle: string; | ||
customAttribution: string; | ||
deckRef?: RefObject<DeckGLRef | null>; | ||
}; |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be further improved on from the Python side to connect with #908