Skip to content

Commit

Permalink
[Feat] Dynamic map lib config (#2678)
Browse files Browse the repository at this point in the history
* Support dynamic map lib using the application config

---------

Signed-off-by: Shan He <heshan0131@gmail.com>
Co-authored-by: Ilya Boyandin <iboyandin@foursquare.com>
  • Loading branch information
heshan0131 and ilyabo authored Oct 10, 2024
1 parent 5764b06 commit 69fc6c6
Show file tree
Hide file tree
Showing 15 changed files with 73 additions and 52 deletions.
4 changes: 1 addition & 3 deletions src/components/src/common/radius-legend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,7 @@ const RadiusLegend: FC<Props> = ({scaleType, width, domain, range}) => {
if (!Array.isArray(domain) || !domain.every(Number.isFinite)) {
return undefined;
}
return scaleSqrt()
.domain(domain)
.range(range);
return scaleSqrt().domain(domain).range(range);
}, [domain, range, scaleType]);

const radiusTicks = useMemo(() => {
Expand Down
43 changes: 23 additions & 20 deletions src/components/src/map-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
// libraries
import React, {Component, createRef, useMemo} from 'react';
import styled, {withTheme} from 'styled-components';
import {Map, MapRef} from 'react-map-gl/maplibre';
import {Map, MapboxMap, MapRef} from 'react-map-gl';
import {PickInfo} from '@deck.gl/core/lib/deck';
import DeckGL from '@deck.gl/react';
import {createSelector, Selector} from 'reselect';
import maplibregl from 'maplibre-gl';
import {useDroppable} from '@dnd-kit/core';
import debounce from 'lodash.debounce';

Expand Down Expand Up @@ -47,7 +46,8 @@ import {
getViewportFromMapState,
normalizeEvent,
rgbToHex,
computeDeckEffects
computeDeckEffects,
getApplicationConfig
} from '@kepler.gl/utils';
import {breakPointValues} from '@kepler.gl/styles';

Expand Down Expand Up @@ -114,7 +114,7 @@ const StyledMap = styled(StyledMapContainer)<StyledMapContainerProps>(
#default-deckgl-overlay {
mix-blend-mode: ${mixBlendMode};
};
*[maplibregl-children] {
*[${getApplicationConfig().mapLibCssClass}-children] {
position: absolute;
}
`
Expand All @@ -126,16 +126,16 @@ const nop = () => {
return;
};

const MapLibreLogo = () => (
const MapLibLogo = () => (
<div className="attrition-logo">
Basemap by:
<a
style={{marginLeft: '5px'}}
className="maplibregl-ctrl-logo"
className={`${getApplicationConfig().mapLibCssClass}-ctrl-logo`}
target="_blank"
rel="noopener noreferrer"
href="https://www.maplibre.org/"
aria-label="MapLibre logo"
href={getApplicationConfig().mapLibUrl}
aria-label={`${getApplicationConfig().mapLibName} logo`}
/>
</div>
);
Expand Down Expand Up @@ -245,11 +245,11 @@ export const Attribution: React.FC<{
<DatasetAttributions datasetAttributions={datasetAttributions} isPalm={isPalm} />
<div className="attrition-link">
{datasetAttributions?.length ? <span className="pipe-separator">|</span> : null}
{isPalm ? <MapLibreLogo /> : null}
{isPalm ? <MapLibLogo /> : null}
<a href="https://kepler.gl/policy/" target="_blank" rel="noopener noreferrer">
© kepler.gl |{' '}
</a>
{!isPalm ? <MapLibreLogo /> : null}
{!isPalm ? <MapLibLogo /> : null}
</div>
</EndHorizontalFlexbox>
</StyledAttrbution>
Expand All @@ -264,6 +264,8 @@ MapContainerFactory.deps = [MapPopoverFactory, MapControlFactory, EditorFactory]
type MapboxStyle = string | object | undefined;
type PropSelector<R> = Selector<MapContainerProps, R>;

type GetMapRef = ReturnType<ReturnType<typeof getApplicationConfig>['getMap']>;

export interface MapContainerProps {
visState: VisState;
mapState: MapState;
Expand All @@ -279,8 +281,9 @@ export interface MapContainerProps {
primary?: boolean; // primary one will be reporting its size to appState
readOnly?: boolean;
isExport?: boolean;
onMapStyleLoaded?: (map: maplibregl.Map | null) => void;
onMapRender?: (map: maplibregl.Map | null) => void;
// onMapStyleLoaded?: (map: maplibregl.Map | ReturnType<MapRef['getMap']> | null) => void;
onMapStyleLoaded?: (map: GetMapRef | null) => void;
onMapRender?: (map: GetMapRef | null) => void;
getMapboxRef?: (mapbox?: MapRef | null, index?: number) => void;
index?: number;
deleteMapLabels?: (containerId: string, layerId: string) => void;
Expand Down Expand Up @@ -361,7 +364,7 @@ export default function MapContainerFactory(
}

_deck: any = null;
_map: maplibregl.Map | null = null;
_map: MapboxMap | null = null;
_ref = createRef<HTMLDivElement>();
_deckGLErrorsElapsed: {[id: string]: number} = {};

Expand Down Expand Up @@ -488,9 +491,9 @@ export default function MapContainerFactory(
}
};

_setMapboxMap: React.Ref<MapRef> = mapbox => {
if (!this._map && mapbox) {
this._map = mapbox.getMap();
_setMapRef = mapRef => {
if (!this._map && mapRef) {
this._map = getApplicationConfig().getMap(mapRef);
// i noticed in certain context we don't access the actual map element
if (!this._map) {
return;
Expand All @@ -509,7 +512,7 @@ export default function MapContainerFactory(
// The parent component can gain access to our MapboxGlMap by
// providing this callback. Note that 'mapbox' will be null when the
// ref is unset (e.g. when a split map is closed).
this.props.getMapboxRef(mapbox, this.props.index);
this.props.getMapboxRef(mapRef, this.props.index);
}
};

Expand Down Expand Up @@ -978,7 +981,7 @@ export default function MapContainerFactory(
preserveDrawingBuffer: true,
mapboxAccessToken: currentStyle?.accessToken || mapboxApiAccessToken,
baseApiUrl: mapboxApiUrl,
mapLib: maplibregl,
mapLib: getApplicationConfig().getMapLib(),
transformRequest: this.props.transformRequest || transformRequest
};

Expand All @@ -994,7 +997,7 @@ export default function MapContainerFactory(
{...mapProps}
mapStyle={mapStyle.bottomMapStyle ?? EMPTY_MAPBOX_STYLE}
{...bottomMapContainerProps}
ref={this._setMapboxMap}
ref={this._setMapRef}
/>
)
});
Expand Down Expand Up @@ -1065,7 +1068,7 @@ export default function MapContainerFactory(
style={MAP_STYLE.top}
mapboxAccessToken={mapProps.mapboxAccessToken}
baseApiUrl={mapProps.baseApiUrl}
mapLib={maplibregl}
mapLib={getApplicationConfig().getMapLib()}
{...topMapContainerProps}
/>
) : null}
Expand Down
11 changes: 5 additions & 6 deletions src/components/src/modals/add-map-style-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import React, {Component} from 'react';
import {polyfill} from 'react-lifecycles-compat';
import classnames from 'classnames';
import styled from 'styled-components';
import MapGLMap, {MapRef} from 'react-map-gl/maplibre';
import {Map, MapboxMap, MapRef} from 'react-map-gl';
import {
StyledModalContent,
InputLight,
Expand All @@ -16,11 +16,10 @@ import {
import {media} from '@kepler.gl/styles';

// Utils
import {transformRequest} from '@kepler.gl/utils';
import {getApplicationConfig, transformRequest} from '@kepler.gl/utils';
import {injectIntl, IntlShape} from 'react-intl';
import {FormattedMessage} from '@kepler.gl/localization';
import {NO_BASEMAP_ICON} from '@kepler.gl/constants';
import maplibregl from 'maplibre-gl';
import {InputStyle, MapState} from '@kepler.gl/types';
import {ActionHandler, inputMapStyle, loadCustomMapStyle} from '@kepler.gl/actions';

Expand Down Expand Up @@ -127,7 +126,7 @@ function AddMapStyleModalFactory() {
}

mapRef: MapRef | null | undefined;
_map: maplibregl.Map | undefined;
_map: MapboxMap | undefined;

componentDidUpdate() {
const map = this.mapRef && this.mapRef.getMap();
Expand Down Expand Up @@ -161,7 +160,7 @@ function AddMapStyleModalFactory() {
...mapState,
baseApiUrl: mapboxApiUrl,
mapboxAccessToken: mapboxApiAccessToken,
mapLib: maplibregl,
mapLib: getApplicationConfig().getMapLib(),
preserveDrawingBuffer: true,
transformRequest
};
Expand Down Expand Up @@ -272,7 +271,7 @@ function AddMapStyleModalFactory() {
<div className="preview-image-spinner" />
) : (
<StyledMapContainer>
<MapGLMap
<Map
{...mapProps}
ref={el => {
this.mapRef = el;
Expand Down
2 changes: 1 addition & 1 deletion src/components/src/plot-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import React, {Component, createRef} from 'react';
import {createSelector} from 'reselect';
import styled from 'styled-components';
import {Map} from 'react-map-gl/maplibre';
import {Map} from 'react-map-gl';
import debounce from 'lodash.debounce';
import {
exportImageError,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ function LayerColumnModeConfigFactory(
);

const renderColumnConfig = useCallback(
({key: columnMode, label, columns: cols}, isSelected) => (
({key: columnMode, columns: cols}, isSelected) => (
<LayerColumnConfig
columnPairs={layer.columnPairs}
columns={cols}
Expand Down
5 changes: 1 addition & 4 deletions src/layers/src/arc-layer/arc-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,10 +304,7 @@ export default class ArcLayer extends Layer {
}

calculateDataAttributeForPoints(
{
dataContainer,
filteredIndex
}: {dataContainer: DataContainerInterface; filteredIndex: number[]},
{filteredIndex}: {dataContainer: DataContainerInterface; filteredIndex: number[]},
getPosition
) {
const data: ArcLayerData[] = [];
Expand Down
2 changes: 2 additions & 0 deletions src/layers/src/base-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,9 @@ class Layer {
getHoverData(
object: any,
dataContainer: DataContainerInterface,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
fields?: Field[],
// eslint-disable-next-line @typescript-eslint/no-unused-vars
animationConfig?: AnimationConfig
): any {
if (!object) {
Expand Down
8 changes: 4 additions & 4 deletions src/layers/src/geojson-layer/geojson-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ export default class GeoJsonLayer extends Layer {
};
}

accessVSFieldValue(field, indexKey) {
accessVSFieldValue() {
if (this.config.columnMode === COLUMN_MODE_GEOJSON) {
return defaultGetFieldValue;
}
Expand Down Expand Up @@ -429,7 +429,7 @@ export default class GeoJsonLayer extends Layer {
return null;
}

calculateDataAttribute(dataset: KeplerTable, getPosition) {
calculateDataAttribute(dataset: KeplerTable) {
const {dataContainer, filteredIndex} = dataset;
if (dataContainer instanceof ArrowDataContainer) {
// TODO add columnMode logic here for ArrowDataContainer?
Expand Down Expand Up @@ -480,10 +480,10 @@ export default class GeoJsonLayer extends Layer {
let dataAccessor;
if (this.config.columnMode === COLUMN_MODE_GEOJSON) {
filterValueAccessor = (dc, d, fieldIndex) => dc.valueAt(d.properties.index, fieldIndex);
dataAccessor = dc => d => ({index: d.properties.index});
dataAccessor = () => d => ({index: d.properties.index});
} else {
filterValueAccessor = getTableModeValueAccessor;
dataAccessor = dc => d => ({index: d.properties.index});
dataAccessor = () => d => ({index: d.properties.index});
}

const indexAccessor = f => f.properties.index;
Expand Down
3 changes: 2 additions & 1 deletion src/layers/src/geojson-layer/geojson-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,8 @@ export function groupColumnsAsGeoJson(
}

const result: Feature[] = Object.entries(groupedById).map(
([id, items]: [string, CoordsType[]], index) => ({
// eslint-disable-next-line @typescript-eslint/no-unused-vars
([_, items]: [string, CoordsType[]], index) => ({
type: 'Feature' as const,
geometry: {
type: 'LineString' as const,
Expand Down
2 changes: 1 addition & 1 deletion src/layers/src/mapboxgl-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ class MapboxLayerGL extends Layer {
{} as {[id: string]: number | number[]}
);
}
: d => ({} as Record<string, number | number[]>);
: () => ({} as Record<string, number | number[]>);

const getProperties = d => ({
...getPropertyFromVisualChanel(d),
Expand Down
8 changes: 4 additions & 4 deletions src/layers/src/trip-layer/trip-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ export default class TripLayer extends Layer {
return this.defaultPointColumnPairs;
}

accessVSFieldValue(field, indexKey) {
accessVSFieldValue() {
if (this.config.columnMode === COLUMN_MODE_GEOJSON) {
return defaultGetFieldValue;
}
Expand Down Expand Up @@ -236,7 +236,7 @@ export default class TripLayer extends Layer {
}

static findDefaultLayerProps(
{label, fieldPairs, fields = [], dataContainer, id}: KeplerTable,
{label, fields = [], dataContainer, id}: KeplerTable,
foundLayers: any[]
) {
const geojsonColumns = fields.filter(f => f.type === 'geojson').map(f => f.name);
Expand Down Expand Up @@ -293,7 +293,7 @@ export default class TripLayer extends Layer {
?.datum;
}

calculateDataAttribute(dataset: KeplerTable, getPosition) {
calculateDataAttribute(dataset: KeplerTable) {
switch (this.config.columnMode) {
case COLUMN_MODE_GEOJSON: {
return (
Expand Down Expand Up @@ -330,7 +330,7 @@ export default class TripLayer extends Layer {
valueAccessor = getTableModeValueAccessor;
}
const indexAccessor = f => f.properties.index;
const dataAccessor = dc => d => ({index: d.properties.index});
const dataAccessor = () => d => ({index: d.properties.index});
const accessors = this.getAttributeAccessors({dataAccessor, dataContainer});
const getFilterValue = gpuFilter.filterValueAccessor(dataContainer)(
indexAccessor,
Expand Down
1 change: 1 addition & 0 deletions src/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"mini-svg-data-uri": "^1.0.3",
"moment-timezone": "^0.5.35",
"react": "^18.2.0",
"react-map-gl": "^7.1.6",
"resize-observer-polyfill": "^1.5.1",
"type-analyzer": "0.4.0"
},
Expand Down
31 changes: 25 additions & 6 deletions src/utils/src/application-config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
// SPDX-License-Identifier: MIT
// Copyright contributors to the kepler.gl project

import {MapLib, MapRef} from 'react-map-gl';

export type MapLibInstance = MapLib<any>;

/**
* A mechanism to override default Kepler values/settings so that we
* without having to make application-specific changes to the kepler repo.
*/
export type KeplerApplicationConfig = {
export type KeplerApplicationConfig<Map> = {
/** Default name of export HTML file, can be overridden by user */
defaultHtmlName?: string;
defaultImageName?: string;
Expand All @@ -11,22 +18,34 @@ export type KeplerApplicationConfig = {
defaultExportJsonSettings?: {
hasData?: boolean;
};
getMapLib?: () => Promise<MapLibInstance>;
getMap?: (ref: any) => Map;
mapLibCssClass?: string;
mapLibName?: string;
mapLibUrl?: string;
};

const DEFAULT_APPLICATION_CONFIG: Required<KeplerApplicationConfig> = {
const DEFAULT_APPLICATION_CONFIG: Required<KeplerApplicationConfig<mapboxgl.Map>> = {
defaultHtmlName: 'kepler.gl.html',
defaultImageName: 'kepler.gl.png',
defaultJsonName: 'kepler.gl.json',
defaultDataName: 'kepler.gl',
defaultExportJsonSettings: {
hasData: true
}
},
getMapLib: () => import('maplibre-gl'),
getMap: (mapRef: MapRef): mapboxgl.Map => mapRef?.getMap(),
mapLibCssClass: 'maplibregl',
mapLibName: 'MapLibre',
mapLibUrl: 'https://www.maplibre.org/'
};

const applicationConfig: Required<KeplerApplicationConfig> = DEFAULT_APPLICATION_CONFIG;
const applicationConfig: Required<KeplerApplicationConfig<mapboxgl.Map>> =
DEFAULT_APPLICATION_CONFIG;

export const getApplicationConfig = (): Required<KeplerApplicationConfig> => applicationConfig;
export const getApplicationConfig = (): Required<KeplerApplicationConfig<mapboxgl.Map>> =>
applicationConfig;

export function initApplicationConfig(appConfig: KeplerApplicationConfig = {}) {
export function initApplicationConfig<M>(appConfig: KeplerApplicationConfig<M> = {}) {
Object.assign(applicationConfig, appConfig);
}
Loading

0 comments on commit 69fc6c6

Please sign in to comment.