Skip to content

Commit eadf5ae

Browse files
igorDykhtaIhor Dykhta
andauthored
feat: add optional higher pitch option (#3384)
* feat: add optional higher pitch option Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local> * tests, lint Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local> * fall back to default - 60 for mapbox Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local> --------- Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local> Co-authored-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>
1 parent 72ea461 commit eadf5ae

8 files changed

Lines changed: 35 additions & 6 deletions

File tree

src/components/src/map-container.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ import {
7373
THROTTLE_NOTIFICATION_TIME,
7474
DEFAULT_PICKING_RADIUS,
7575
NO_MAP_ID,
76-
EMPTY_MAPBOX_STYLE
76+
EMPTY_MAPBOX_STYLE,
77+
MAPBOX_MAX_PITCH,
78+
MAP_LIB_OPTIONS
7779
} from '@kepler.gl/constants';
7880

7981
import {DROPPABLE_MAP_CONTAINER_TYPE} from './common/dnd-layer-items';
@@ -1011,7 +1013,9 @@ export default function MapContainerFactory(
10111013
isInteractive
10121014
? {
10131015
doubleClickZoom: !isEditorDrawingMode,
1014-
dragRotate: this.props.mapState.dragRotate
1016+
dragRotate: this.props.mapState.dragRotate,
1017+
maxPitch:
1018+
this.props.mapState.maxPitch ?? getApplicationConfig().maxPitch
10151019
}
10161020
: false
10171021
}
@@ -1177,8 +1181,13 @@ export default function MapContainerFactory(
11771181
getApplicationConfig().baseMapLibraryConfig?.[baseMapLibraryName];
11781182

11791183
const internalViewState = this.context?.getInternalViewState(index);
1184+
const configMaxPitch = mapState.maxPitch ?? getApplicationConfig().maxPitch;
1185+
const effectiveMaxPitch = baseMapLibraryName === MAP_LIB_OPTIONS.MAPBOX
1186+
? Math.min(configMaxPitch, MAPBOX_MAX_PITCH)
1187+
: configMaxPitch;
11801188
const mapProps = {
11811189
...internalViewState,
1190+
maxPitch: effectiveMaxPitch,
11821191
preserveDrawingBuffer: this.props.isExport ?? false,
11831192
mapboxAccessToken: currentStyle?.accessToken || mapboxApiAccessToken,
11841193
// baseApiUrl: mapboxApiUrl,
@@ -1272,6 +1281,7 @@ export default function MapContainerFactory(
12721281
<MapComponent
12731282
key={`top-${baseMapLibraryName}`}
12741283
viewState={internalViewState}
1284+
maxPitch={effectiveMaxPitch}
12751285
mapStyle={mapStyle.topMapStyle}
12761286
style={MAP_STYLE.top}
12771287
mapboxAccessToken={mapProps.mapboxAccessToken}

src/components/src/map-view-state-context.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ type MapViewState = {
1010
zoom: number;
1111
bearing?: number;
1212
pitch?: number;
13+
maxPitch?: number;
1314
};
1415
import {pickViewportPropsFromMapState} from '@kepler.gl/reducers';
1516

src/constants/src/default-settings.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,9 @@ export const MAP_LIB_OPTIONS = {
294294
MAPLIBRE: 'maplibre' as const
295295
};
296296

297+
/** Mapbox GL JS does not support pitch above 60 degrees */
298+
export const MAPBOX_MAX_PITCH = 60;
299+
297300
export type BaseMapLibraryType = 'mapbox' | 'maplibre';
298301

299302
export const NO_BASEMAP_ICON = `${BASEMAP_ICON_PREFIX}/NO_BASEMAP.png`;

src/reducers/src/map-state-updaters.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ const mapStateUpdaters = null;
7171
* @property height Default: `800`
7272
* @property minZoom: `undefined`,
7373
* @property maxZoom: `undefined`,
74+
* @property maxPitch: `undefined` (defaults to application config value, deck.gl default is 60)
7475
* @property maxBounds: `undefined`,
7576
* @property isSplit: `false`,
7677
* @property isViewportSynced: `true`,
@@ -89,6 +90,7 @@ export const INITIAL_MAP_STATE: MapState = {
8990
height: 800,
9091
minZoom: undefined,
9192
maxZoom: undefined,
93+
maxPitch: undefined,
9294
maxBounds: undefined,
9395
isSplit: false,
9496
isViewportSynced: true,
@@ -466,6 +468,7 @@ export function pickViewportPropsFromMapState(state: MapState): Viewport {
466468
'dragRotate',
467469
'minZoom',
468470
'maxZoom',
471+
'maxPitch',
469472
'maxBounds'
470473
]);
471474
}

src/schemas/src/map-state-schema.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ export const propertiesV1 = {
1919
isSplit: null,
2020
isViewportSynced: null,
2121
isZoomLocked: null,
22-
splitMapViewports: null
22+
splitMapViewports: null,
23+
maxPitch: null
2324
};
2425

2526
const mapStateSchema = {

src/types/reducers.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export type MapState = {
2424
height: number;
2525
minZoom?: number;
2626
maxZoom?: number;
27+
maxPitch?: number;
2728
maxBounds?: Bounds;
2829
initialState?: any;
2930
scale?: number;
@@ -530,6 +531,8 @@ export type Viewport = {
530531
minZoom?: number;
531532
/** Maximum allowed viewport zoom */
532533
maxZoom?: number;
534+
/** Maximum pitch angle in degrees */
535+
maxPitch?: number;
533536
/** Maximum geographical bounds, pan/zoom operations are constrained within those bounds */
534537
maxBounds?: Bounds;
535538
/** viewport transition duration use by geocoder panel **/

src/utils/src/application-config.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ export type KeplerApplicationConfig = {
8989
// Image export config
9090
/** Whether to apply fix for uglify error in dom-to-image (should be true for webpack builds, false for Vite) */
9191
escapeXhtmlForWebpack?: boolean;
92+
93+
/** Maximum pitch angle in degrees. deck.gl defaults to 60; set higher (up to 85) for elevated perspectives.
94+
* Note: values above 60 may cause rendering artifacts with some basemap tile providers. */
95+
maxPitch?: number;
9296
};
9397

9498
const DEFAULT_APPLICATION_CONFIG: Required<KeplerApplicationConfig> = {
@@ -147,7 +151,9 @@ const DEFAULT_APPLICATION_CONFIG: Required<KeplerApplicationConfig> = {
147151

148152
// Image export config
149153
// Default to true for webpack builds, false for other build tools (e.g., Vite)
150-
escapeXhtmlForWebpack: isWebpackBuild()
154+
escapeXhtmlForWebpack: isWebpackBuild(),
155+
156+
maxPitch: 60
151157
};
152158

153159
const applicationConfig: Required<KeplerApplicationConfig> = DEFAULT_APPLICATION_CONFIG;

test/node/schemas/map-state-schema-test.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ test('#mapStateSchema -> v1 -> save load mapState', t => {
2626
'isSplit',
2727
'isViewportSynced',
2828
'isZoomLocked',
29-
'splitMapViewports'
29+
'splitMapViewports',
30+
'maxPitch'
3031
],
3132
'mapState should have all 6 entries'
3233
);
@@ -41,7 +42,8 @@ test('#mapStateSchema -> v1 -> save load mapState', t => {
4142
isSplit: false,
4243
isViewportSynced: true,
4344
isZoomLocked: false,
44-
splitMapViewports: []
45+
splitMapViewports: [],
46+
maxPitch: undefined
4547
};
4648

4749
t.deepEqual(msToSave, expected, 'save mapState should be current');

0 commit comments

Comments
 (0)