Skip to content

Commit

Permalink
fix(kepler) 3.1 integration (#302)
Browse files Browse the repository at this point in the history
* fix(react) kepler layers should be visible

* reset yarn.lock

* remove kepler to avoid downstream issues

* clean kepler-layers

* fix(kepler) layers should update on every react render

* chore(kepler) component should configure which map to render

* change(kepler) switch png and jpeg encoder to use zip archives

* fix(kepler) filters should render enlarged filters using latest schema

* fix(kepler) all defined filters should render

* Use kepler constants

* Add missing mapbox token to kepler build
  • Loading branch information
chrisgervang authored Feb 7, 2025
1 parent bc513f1 commit 6b43afa
Show file tree
Hide file tree
Showing 7 changed files with 307 additions and 49 deletions.
5 changes: 5 additions & 0 deletions examples/kepler/vite.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
define: {
'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken)
}
};
2 changes: 1 addition & 1 deletion modules/core/src/animations/kepler-animation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export default class KeplerAnimation extends Animation {
if (filterKeyframes.length > 0) {
this.filterKeyframes = filterKeyframes.reduce((acc, filterKeyframe) => {
const filterIdx = findFilterIdx({filters, filterKeyframe});
const filter = filterIdx && filters[filterIdx];
const filter = Number.isFinite(filterIdx) && filters[filterIdx];
if (filter) {
if (acc[filter.id]) {
acc[filter.id].set({filter, ...filterKeyframe});
Expand Down
3 changes: 2 additions & 1 deletion modules/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@
],
"sideEffects": false,
"dependencies": {
"@kepler.gl/constants": "3.1.0",
"@loaders.gl/zip": "^3.0.12",
"@turf/helpers": "^5.1.5",
"@turf/transform-translate": "^5.1.5",
"classnames": "^2.2.6",
"classnames": "^2.3.1",
"fuzzy": "^0.1.3",
"global": "^4.4.0",
"lodash.get": "^4.4.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {parseSetCameraType, scaleToVideoExport} from './utils';
import {DEFAULT_FILENAME, getResolutionSetting} from './constants';
import type {MapProps} from 'react-map-gl';
import type {DeckProps, MapViewState} from '@deck.gl/core/typed';
import {FILTER_VIEW_TYPES} from '@kepler.gl/constants';

const ENCODERS = {
gif: GifEncoder,
Expand Down Expand Up @@ -166,8 +167,12 @@ export class ExportVideoPanelContainer extends Component<
quality: 0.8
},
jpeg: {
archive: 'zip',
quality: 0.8
},
png: {
archive: 'zip'
},
gif: {
sampleInterval: 1000,
width,
Expand Down Expand Up @@ -225,7 +230,7 @@ export class ExportVideoPanelContainer extends Component<
Array.isArray(animatableFilters) && animatableFilters.length
? animatableFilters
: // only animate an enlarged time filter if animatable filters aren't specified.
filters.filter(f => f.type === 'timeRange' && f.enlarged)
filters.filter(f => f.type === 'timeRange' && f.view === FILTER_VIEW_TYPES.enlarged)
).map(f => ({
id: f.id,
timings: [0, this.state.durationMs]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import React, {Component, ForwardedRef, RefObject, forwardRef} from 'react';
import DeckGL from '@deck.gl/react/typed';
import ReactMapGL, {type MapProps, type MapRef, useControl} from 'react-map-gl';
import {MapboxOverlay, MapboxOverlayProps} from '@deck.gl/mapbox/typed';
import type {Deck, DeckProps, Layer, MapViewState} from '@deck.gl/core/typed';
import type {Deck, DeckProps, MapViewState} from '@deck.gl/core/typed';
import isEqual from 'lodash.isequal';

import {deckStyle, DeckCanvas} from './styled-components';
Expand Down Expand Up @@ -36,7 +36,6 @@ type ExportVideoPanelPreviewState = {
memoDevicePixelRatio: number;
mapStyle: string;
mapboxAccessToken?: string;
deckLayers: Layer[];
};

const DeckGLOverlay = forwardRef<Deck, MapboxOverlayProps>(
Expand Down Expand Up @@ -71,8 +70,7 @@ export class ExportVideoPanelPreview extends Component<
this.state = {
mapStyle: url, // Unsure if mapStyle would ever change but allowing it just in case
mapboxAccessToken: accessToken,
memoDevicePixelRatio: 1,
deckLayers: []
memoDevicePixelRatio: 1
};

this._onMapLoad = this._onMapLoad.bind(this);
Expand Down Expand Up @@ -150,7 +148,8 @@ export class ExportVideoPanelPreview extends Component<
if (deckProps && deckProps.layers) {
return deckProps.layers;
}
return createKeplerLayers(mapData, viewState, beforeId);
const mapIndex = 0; // TODO: select mapIndex from redux
return createKeplerLayers(mapData, viewState, mapIndex, beforeId);
}

_onAfterRender() {
Expand All @@ -162,12 +161,6 @@ export class ExportVideoPanelPreview extends Component<
_onMapLoad() {
// Adds mapbox layer to modal
const map = this.mapRef.current.getMap();

const beforeId = this.props.mapboxLayerBeforeId;
const keplerLayers = this.createLayers(beforeId);

this.setState({deckLayers: keplerLayers});

map.on('render', this._onAfterRender);
}

Expand All @@ -182,22 +175,23 @@ export class ExportVideoPanelPreview extends Component<
resolution,
deckProps,
mapProps,
disableBaseMap
disableBaseMap,
mapboxLayerBeforeId
} = this.props;
const {mapStyle, deckLayers, mapboxAccessToken} = this.state;
const {mapStyle, mapboxAccessToken} = this.state;
const deck = this.deckRef.current;
const {width, height} = this._getContainer();
const doubleResolution = {width: resolution[0] * 2, height: resolution[1] * 2};
const keplerLayers = this.createLayers(mapboxLayerBeforeId);
return (
<>
<DeckCanvas id="deck-canvas" $width={width} $height={height}>
{disableBaseMap ? (
<DeckGL
ref={ref => setRef(this.deckRef, ref?.deck)}
{...adapter.getProps({deck, extraProps: {...deckProps, layers: deckLayers}})}
{...adapter.getProps({deck, extraProps: {...deckProps, layers: keplerLayers}})}
// {...doubleResolution}
{...this._getContainer()}
layers={deckLayers}
/>
) : (
<ReactMapGL
Expand All @@ -216,7 +210,7 @@ export class ExportVideoPanelPreview extends Component<
<DeckGLOverlay
ref={this.deckRef}
glOptions={{stencil: true}}
{...adapter.getProps({deck, extraProps: {...deckProps, layers: deckLayers}})}
{...adapter.getProps({deck, extraProps: {...deckProps, layers: keplerLayers}})}
/>
</ReactMapGL>
)}
Expand Down
82 changes: 59 additions & 23 deletions modules/react/src/kepler-layers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,59 @@

import {createSelector} from 'reselect';
import type {Layer, MapViewState} from '@deck.gl/core/typed';
// Note: kepler type imports are commented out to avoid issues when hubble is installed by kepler.
// import type {KeplerGlState} from '@kepler.gl/reducers';
// import type {Layer as KeplerLayer} from '@kepler.gl/layers';
// import type {SplitMap, SplitMapLayers} from '@kepler.gl/types';

/**
* Kepler Layer Creation
* Forked from kepler.gl
* https://github.com/keplergl/kepler.gl/blob/master/src/components/map-container.js
*/
const layersSelector = (state: any) => state.visState.layers;
const layerDataSelector = (state: any) => state.visState.layerData;
const mapLayersSelector = (state: any) => state.visState.mapLayers;
const layersSelector = (state: any /* KeplerGlState*/) => state.visState.layers;
const layerDataSelector = (state: any /* KeplerGlState*/) => state.visState.layerData;
const getMapLayersFromSplitMaps = (
splitMaps: any /* SplitMap[]*/,
mapIndex?: number
): any[] /* SplitMapLayers*/ | undefined | null => {
return splitMaps[mapIndex || 0]?.layers;
};

const splitMapSelector = (state: any /* KeplerGlState*/) => state.visState.splitMaps;
const splitMapIndexSelector = (_: any /* KeplerGlState*/, mapIndex: number | undefined) => mapIndex;
const mapLayersSelector = createSelector(
splitMapSelector,
splitMapIndexSelector,
getMapLayersFromSplitMaps
);
// const layerOrderSelector = state => state.visState.layerOrder;
const layersToRenderSelector = createSelector(
layersSelector,
layerDataSelector,
mapLayersSelector,
// {[id]: true \ false}
(layers, layerData, mapLayers) =>
(layers, layerData, splitMapLayers) =>
layers.reduce(
(accu: object, layer: any, idx: number) => ({
(accu: object, layer: any /* KeplerLayer*/, idx: number) => ({
...accu,
[layer.id]: layer.shouldRenderLayer(layerData[idx]) && _isVisibleMapLayer(layer, mapLayers) // eslint-disable-line
[layer.id]:
layer.config.isVisible &&
layer.shouldRenderLayer(layerData[idx]) &&
_isVisibleSplitMapLayer(layer, splitMapLayers) &&
layer.overlayType === 'deckgl'
}),
{}
)
);
/* component private functions */
function _isVisibleMapLayer(layer: any, mapLayers: any) {
// if layer.id is not in mapLayers, don't render it
return !mapLayers || (mapLayers && mapLayers[layer.id]);

function _isVisibleSplitMapLayer(
layer: any /* KeplerLayer*/,
splitMapLayers?: any[] /* SplitMapLayers*/
) {
// Undefined splitMapLayers means there isn't a split map, so don't refer to it for layer visibility.
// If splitMapLayers is defined, it means there is a split map and the upstream caller has selected the map to render in video.
return !splitMapLayers || (splitMapLayers && splitMapLayers[layer.id]);
}

function _onLayerSetDomain(idx: number, colorDomain: any) {
Expand All @@ -44,8 +69,9 @@ function _onLayerSetDomain(idx: number, colorDomain: any) {
function renderLayer(
overlays: any,
idx: number,
map: any,
map: any /* KeplerGlState*/,
viewState: MapViewState,
isVisible: boolean,
beforeId?: string
) {
const {
Expand All @@ -62,7 +88,11 @@ function renderLayer(
onSetLayerDomain: (val: any) => _onLayerSetDomain(idx, val)
};

// Layer is Layer class
// Skip layers that aren't supposed to be visible
if (!isVisible) {
return overlays;
}

const layerOverlay = layer
.renderLayer({
data,
Expand All @@ -78,27 +108,33 @@ function renderLayer(
deckLayer.clone({
pickable: false,
// @ts-expect-error MapboxOverlay layers are extended to include beforeId
beforeId
beforeId,
visible: true
})
);
return overlays.concat(layerOverlay || []);
}

export function createKeplerLayers(map: any, viewState: MapViewState, beforeId?: string) {
const layersToRender = layersToRenderSelector(map);
export function createKeplerLayers(
map: any /* KeplerGlState*/,
viewState: MapViewState,
mapIndex: number = undefined,
beforeId?: string
) {
const layersToRender = layersToRenderSelector(map, mapIndex);
// returns an arr of DeckGL layer objects
const {layerOrder, layerData, layers} = map.visState;

if (layerData && layerData.length) {
return layerOrder
.slice()
.reverse()
.filter(
(_, idx: number) => layers[idx].overlayType === 'deckgl' && layersToRender[layers[idx].id]
)
// Create same layer order as Kepler
const overlays = [...layerOrder]
.map(layerId => ({layerId, visible: layersToRender[layerId]}))
.reduce(
(overlays: any, _, idx: number) => renderLayer(overlays, idx, map, viewState, beforeId),
(overlays: any, layerMeta, idx) =>
renderLayer(overlays, idx, map, viewState, layerMeta.visible, beforeId),
[]
); // Slicing & reversing to create same layer order as Kepler
);
return overlays;
}
return [];
}
Loading

0 comments on commit 6b43afa

Please sign in to comment.