• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

keplergl / kepler.gl / 22361650031

24 Feb 2026 05:09PM UTC coverage: 61.612% (-0.2%) from 61.806%
22361650031

Pull #3219

github

web-flow
Merge 1d9b34cb5 into cc33b0c8f
Pull Request #3219: Update kepler-jupyter to use kepler.gl v3.2.0

6382 of 12288 branches covered (51.94%)

Branch coverage included in aggregate %.

13078 of 19297 relevant lines covered (67.77%)

81.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

44.38
/src/components/src/map-container.tsx
1
// SPDX-License-Identifier: MIT
2
// Copyright contributors to the kepler.gl project
3

4
// libraries
5
import React, {Component, createRef, useMemo} from 'react';
6
import styled, {withTheme} from 'styled-components';
7
import {Map, MapRef} from 'react-map-gl';
8
import {PickInfo} from '@deck.gl/core/lib/deck';
9
import DeckGL from '@deck.gl/react';
10
import {createSelector, Selector} from 'reselect';
11
import {useDroppable} from '@dnd-kit/core';
12
import debounce from 'lodash/debounce';
13

14
import {VisStateActions, MapStateActions, UIStateActions} from '@kepler.gl/actions';
15

16
// components
17
import MapPopoverFactory from './map/map-popover';
18
import MapControlFactory from './map/map-control';
19
import {
20
  StyledMapContainer,
21
  StyledAttribution,
22
  EndHorizontalFlexbox
23
} from './common/styled-components';
24

25
import EditorFactory from './editor/editor';
26

27
// utils
28
import {
29
  generateMapboxLayers,
30
  updateMapboxLayers,
31
  LayerBaseConfig,
32
  VisualChannelDomain,
33
  EditorLayerUtils,
34
  AggregatedBin
35
} from '@kepler.gl/layers';
36
import {
37
  DatasetAttribution,
38
  MapState,
39
  MapControls,
40
  Viewport,
41
  SplitMap,
42
  SplitMapLayers
43
} from '@kepler.gl/types';
44
import {
45
  errorNotification,
46
  setLayerBlending,
47
  isStyleUsingMapboxTiles,
48
  isStyleUsingOpenStreetMapTiles,
49
  getBaseMapLibrary,
50
  BaseMapLibraryConfig,
51
  transformRequest,
52
  observeDimensions,
53
  unobserveDimensions,
54
  hasMobileWidth,
55
  getMapLayersFromSplitMaps,
56
  onViewPortChange,
57
  getViewportFromMapState,
58
  normalizeEvent,
59
  rgbToHex,
60
  computeDeckEffects,
61
  getApplicationConfig,
62
  GetMapRef
63
} from '@kepler.gl/utils';
64
import {breakPointValues} from '@kepler.gl/styles';
65

66
// default-settings
67
import {
68
  FILTER_TYPES,
69
  GEOCODER_LAYER_ID,
70
  THROTTLE_NOTIFICATION_TIME,
71
  DEFAULT_PICKING_RADIUS,
72
  NO_MAP_ID,
73
  EMPTY_MAPBOX_STYLE
74
} from '@kepler.gl/constants';
75

76
import {DROPPABLE_MAP_CONTAINER_TYPE} from './common/dnd-layer-items';
77
// Contexts
78
import {MapViewStateContext} from './map-view-state-context';
79

80
import ErrorBoundary from './common/error-boundary';
81
import {LOCALE_CODES} from '@kepler.gl/localization';
82
import {MapView} from '@deck.gl/core';
83
import {
84
  MapStyle,
85
  areAnyDeckLayersLoading,
86
  computeDeckLayers,
87
  getLayerHoverProp,
88
  LayerHoverProp,
89
  prepareLayersForDeck,
90
  prepareLayersToRender,
91
  LayersToRender
92
} from '@kepler.gl/reducers';
93
import {VisState} from '@kepler.gl/schemas';
94

95
import LoadingIndicator from './loading-indicator';
96

97
// Debounce the propagation of viewport change and mouse moves to redux store.
98
// This is to avoid too many renders of other components when the map is
99
// being panned/zoomed (leading to laggy basemap/deck syncing).
100
const DEBOUNCE_VIEWPORT_PROPAGATE = 10;
7✔
101
const DEBOUNCE_MOUSE_MOVE_PROPAGATE = 10;
7✔
102

103
// How long should we wait between layer loading state changes before triggering a UI update
104
const DEBOUNCE_LOADING_STATE_PROPAGATE = 100;
7✔
105

106
const MAP_STYLE: {[key: string]: React.CSSProperties} = {
7✔
107
  container: {
108
    display: 'inline-block',
109
    position: 'relative',
110
    width: '100%',
111
    height: '100%'
112
  },
113
  top: {
114
    position: 'absolute',
115
    top: 0,
116
    width: '100%',
117
    height: '100%',
118
    pointerEvents: 'none'
119
  }
120
};
121

122
const LOCALE_CODES_ARRAY = Object.keys(LOCALE_CODES);
7✔
123

124
interface StyledMapContainerProps {
125
  $mixBlendMode?: string;
126
  $mapLibCssClass: string;
127
}
128

129
const StyledMap = styled(StyledMapContainer)<StyledMapContainerProps>(
7✔
130
  ({$mixBlendMode = 'normal', $mapLibCssClass}) => `
29!
131
  #default-deckgl-overlay {
132
    mix-blend-mode: ${$mixBlendMode};
133
  };
134
  *[${$mapLibCssClass}-children] {
135
    position: absolute;
136
  }
137
`
138
);
139

140
const MAPBOXGL_STYLE_UPDATE = 'style.load';
7✔
141
const MAPBOXGL_RENDER = 'render';
7✔
142
const nop = () => {
7✔
143
  return;
×
144
};
145

146
type MapLibLogoProps = {
147
  baseMapLibraryConfig: BaseMapLibraryConfig;
148
};
149

150
const MapLibLogo = ({baseMapLibraryConfig}: MapLibLogoProps) => (
7✔
151
  <div className="attrition-logo">
25✔
152
    Basemap by:
153
    <a
154
      style={{marginLeft: '5px'}}
155
      className={`${baseMapLibraryConfig.mapLibCssClass}-ctrl-logo`}
156
      target="_blank"
157
      rel="noopener noreferrer"
158
      href={baseMapLibraryConfig.mapLibUrl}
159
      aria-label={`${baseMapLibraryConfig.mapLibName} logo`}
160
    />
161
  </div>
162
);
163

164
interface StyledDroppableProps {
165
  isOver: boolean;
166
}
167

168
const StyledDroppable = styled.div<StyledDroppableProps>`
7✔
169
  background-color: ${props => (props.isOver ? props.theme.dndOverBackgroundColor : 'none')};
9!
170
  width: 100%;
171
  height: 100%;
172
  position: absolute;
173
  pointer-events: none;
174
  z-index: 1;
175
`;
176

177
export const isSplitSelector = props =>
7✔
178
  props.visState.splitMaps && props.visState.splitMaps.length > 1;
29✔
179

180
export const Droppable = ({containerId}) => {
7✔
181
  const {isOver, setNodeRef} = useDroppable({
9✔
182
    id: containerId,
183
    data: {type: DROPPABLE_MAP_CONTAINER_TYPE, index: containerId},
184
    disabled: !containerId
185
  });
186

187
  return <StyledDroppable ref={setNodeRef} isOver={isOver} />;
9✔
188
};
189

190
interface StyledDatasetAttributionsContainerProps {
191
  isPalm: boolean;
192
}
193

194
const StyledDatasetAttributionsContainer = styled.div<StyledDatasetAttributionsContainerProps>`
7✔
195
  max-width: ${props => (props.isPalm ? '200px' : '300px')};
×
196
  text-overflow: ellipsis;
197
  white-space: nowrap;
198
  overflow: hidden;
199
  color: ${props => props.theme.labelColor};
×
200
  margin-right: 2px;
201
  margin-bottom: 1px;
202
  line-height: ${props => (props.isPalm ? '1em' : '1.4em')};
×
203

204
  &:hover {
205
    white-space: inherit;
206
  }
207
`;
208

209
const DatasetAttributions = ({
7✔
210
  datasetAttributions,
211
  isPalm
212
}: {
213
  datasetAttributions: DatasetAttribution[];
214
  isPalm: boolean;
215
}) => (
216
  <>
25✔
217
    {datasetAttributions?.length ? (
25!
218
      <StyledDatasetAttributionsContainer isPalm={isPalm}>
219
        {datasetAttributions.map((ds, idx) => (
220
          <a
×
221
            {...(ds.url ? {href: ds.url} : null)}
×
222
            target="_blank"
223
            rel="noopener noreferrer"
224
            key={`${ds.title}_${idx}`}
225
          >
226
            {ds.title}
227
            {idx !== datasetAttributions.length - 1 ? ', ' : null}
×
228
          </a>
229
        ))}
230
      </StyledDatasetAttributionsContainer>
231
    ) : null}
232
  </>
233
);
234

235
type AttributionProps = {
236
  showBaseMapLibLogo: boolean;
237
  showOsmBasemapAttribution: boolean;
238
  datasetAttributions: DatasetAttribution[];
239
  baseMapLibraryConfig: BaseMapLibraryConfig;
240
};
241

242
export const Attribution: React.FC<AttributionProps> = ({
7✔
243
  showBaseMapLibLogo = true,
×
244
  showOsmBasemapAttribution = false,
×
245
  datasetAttributions,
246
  baseMapLibraryConfig
247
}: AttributionProps) => {
248
  const isPalm = hasMobileWidth(breakPointValues);
28✔
249

250
  const memoizedComponents = useMemo(() => {
28✔
251
    if (!showBaseMapLibLogo) {
25!
252
      return (
×
253
        <StyledAttribution
254
          mapLibCssClass={baseMapLibraryConfig.mapLibCssClass}
255
          mapLibAttributionCssClass={baseMapLibraryConfig.mapLibAttributionCssClass}
256
        >
257
          <EndHorizontalFlexbox>
258
            <DatasetAttributions datasetAttributions={datasetAttributions} isPalm={isPalm} />
259
            {showOsmBasemapAttribution ? (
×
260
              <div className="attrition-link">
261
                {datasetAttributions?.length ? <span className="pipe-separator">|</span> : null}
×
262
                <a
263
                  href="http://www.openstreetmap.org/copyright"
264
                  target="_blank"
265
                  rel="noopener noreferrer"
266
                >
267
                  © OpenStreetMap
268
                </a>
269
              </div>
270
            ) : null}
271
          </EndHorizontalFlexbox>
272
        </StyledAttribution>
273
      );
274
    }
275

276
    return (
25✔
277
      <StyledAttribution
278
        mapLibCssClass={baseMapLibraryConfig.mapLibCssClass}
279
        mapLibAttributionCssClass={baseMapLibraryConfig.mapLibAttributionCssClass}
280
      >
281
        <EndHorizontalFlexbox>
282
          <DatasetAttributions datasetAttributions={datasetAttributions} isPalm={isPalm} />
283
          <div className="attrition-link">
284
            {datasetAttributions?.length ? <span className="pipe-separator">|</span> : null}
25!
285
            {isPalm ? <MapLibLogo baseMapLibraryConfig={baseMapLibraryConfig} /> : null}
25!
286
            <a href="https://kepler.gl/policy/" target="_blank" rel="noopener noreferrer">
287
              © kepler.gl |{' '}
288
            </a>
289
            {!isPalm ? <MapLibLogo baseMapLibraryConfig={baseMapLibraryConfig} /> : null}
25!
290
          </div>
291
        </EndHorizontalFlexbox>
292
      </StyledAttribution>
293
    );
294
  }, [
295
    showBaseMapLibLogo,
296
    showOsmBasemapAttribution,
297
    datasetAttributions,
298
    isPalm,
299
    baseMapLibraryConfig
300
  ]);
301

302
  return memoizedComponents;
28✔
303
};
304

305
MapContainerFactory.deps = [MapPopoverFactory, MapControlFactory, EditorFactory];
7✔
306

307
type MapboxStyle = string | object | undefined;
308
type PropSelector<R> = Selector<MapContainerProps, R>;
309

310
export interface MapContainerProps {
311
  visState: VisState;
312
  mapState: MapState;
313
  mapControls: MapControls;
314
  mapStyle: {bottomMapStyle?: MapboxStyle; topMapStyle?: MapboxStyle} & MapStyle;
315
  mapboxApiAccessToken: string;
316
  mapboxApiUrl: string;
317
  visStateActions: typeof VisStateActions;
318
  mapStateActions: typeof MapStateActions;
319
  uiStateActions: typeof UIStateActions;
320

321
  // optional
322
  primary?: boolean; // primary one will be reporting its size to appState
323
  readOnly?: boolean;
324
  isExport?: boolean;
325
  // onMapStyleLoaded?: (map: maplibregl.Map | ReturnType<MapRef['getMap']> | null) => void;
326
  onMapStyleLoaded?: (map: GetMapRef | null) => void;
327
  onMapRender?: (map: GetMapRef | null) => void;
328
  getMapboxRef?: (mapbox?: MapRef | null, index?: number) => void;
329
  index?: number;
330
  deleteMapLabels?: (containerId: string, layerId: string) => void;
331
  containerId?: number;
332

333
  isLoadingIndicatorVisible?: boolean;
334
  activeSidePanel: string | null;
335
  sidePanelWidth?: number;
336

337
  locale?: any;
338
  theme?: any;
339
  editor?: any;
340
  MapComponent?: typeof Map;
341
  deckGlProps?: any;
342
  onDeckInitialized?: (a: any, b: any) => void;
343
  onViewStateChange?: (viewport: Viewport) => void;
344

345
  topMapContainerProps: any;
346
  bottomMapContainerProps: any;
347
  transformRequest?: (url: string, resourceType?: string) => {url: string};
348

349
  datasetAttributions?: DatasetAttribution[];
350

351
  generateMapboxLayers?: typeof generateMapboxLayers;
352
  generateDeckGLLayers?: typeof computeDeckLayers;
353

354
  onMouseMove?: (event: React.MouseEvent & {lngLat?: [number, number]}) => void;
355

356
  children?: React.ReactNode;
357
  deckRenderCallbacks?: {
358
    onDeckLoad?: () => void;
359
    onDeckRender?: (deckProps: Record<string, unknown>) => Record<string, unknown> | null;
360
    onDeckAfterRender?: (deckProps: Record<string, unknown>) => any;
361
  };
362

363
  // Optional: override legend header logo in map controls (used by image export)
364
  logoComponent?: React.FC | React.ReactNode;
365
}
366

367
export default function MapContainerFactory(
368
  MapPopover: ReturnType<typeof MapPopoverFactory>,
369
  MapControl: ReturnType<typeof MapControlFactory>,
370
  Editor: ReturnType<typeof EditorFactory>
371
): React.ComponentType<MapContainerProps> {
372
  class MapContainer extends Component<MapContainerProps> {
373
    displayName = 'MapContainer';
25✔
374

375
    private anyActiveLayerLoading = false;
25✔
376

377
    static contextType = MapViewStateContext;
14✔
378

379
    declare context: React.ContextType<typeof MapViewStateContext>;
380

381
    static defaultProps = {
14✔
382
      MapComponent: Map,
383
      deckGlProps: {},
384
      index: 0,
385
      primary: true
386
    };
387

388
    constructor(props) {
389
      super(props);
25✔
390
    }
391

392
    state = {
25✔
393
      // Determines whether attribution should be visible based the result of loading the map style
394
      showBaseMapAttribution: true
395
    };
396

397
    componentDidMount() {
398
      if (!this._ref.current) {
25!
399
        return;
×
400
      }
401
      observeDimensions(this._ref.current, this._handleResize);
25✔
402
    }
403

404
    componentWillUnmount() {
405
      // unbind mapboxgl event listener
406
      if (this._map) {
2!
407
        this._map?.off(MAPBOXGL_STYLE_UPDATE, nop);
×
408
        this._map?.off(MAPBOXGL_RENDER, nop);
×
409
      }
410
      if (!this._ref.current) {
2!
411
        return;
×
412
      }
413
      unobserveDimensions(this._ref.current);
2✔
414
    }
415

416
    _deck: any = null;
25✔
417
    _map: GetMapRef | null = null;
25✔
418
    _ref = createRef<HTMLDivElement>();
25✔
419
    _deckGLErrorsElapsed: {[id: string]: number} = {};
25✔
420

421
    previousLayers = {
25✔
422
      // [layers.id]: mapboxLayerConfig
423
    };
424

425
    _handleResize = dimensions => {
25✔
426
      const {primary, index} = this.props;
×
427
      if (primary) {
×
428
        const {mapStateActions} = this.props;
×
429
        if (dimensions && dimensions.width > 0 && dimensions.height > 0) {
×
430
          mapStateActions.updateMap(dimensions, index);
×
431
        }
432
      }
433
    };
434

435
    layersSelector: PropSelector<VisState['layers']> = props => props.visState.layers;
56✔
436
    layerDataSelector: PropSelector<VisState['layers']> = props => props.visState.layerData;
56✔
437
    splitMapSelector: PropSelector<SplitMap[]> = props => props.visState.splitMaps;
28✔
438
    splitMapIndexSelector: PropSelector<number | undefined> = props => props.index;
28✔
439
    mapLayersSelector: PropSelector<SplitMapLayers | null | undefined> = createSelector(
25✔
440
      this.splitMapSelector,
441
      this.splitMapIndexSelector,
442
      getMapLayersFromSplitMaps
443
    );
444
    layerOrderSelector: PropSelector<VisState['layerOrder']> = props => props.visState.layerOrder;
25✔
445
    layersToRenderSelector: PropSelector<LayersToRender> = createSelector(
25✔
446
      this.layersSelector,
447
      this.layerDataSelector,
448
      this.mapLayersSelector,
449
      prepareLayersToRender
450
    );
451
    layersForDeckSelector = createSelector(
25✔
452
      this.layersSelector,
453
      this.layerDataSelector,
454
      prepareLayersForDeck
455
    );
456
    filtersSelector = props => props.visState.filters;
28✔
457
    polygonFiltersSelector = createSelector(this.filtersSelector, filters =>
25✔
458
      filters.filter(f => f.type === FILTER_TYPES.polygon && f.enabled !== false)
26!
459
    );
460
    featuresSelector = props => props.visState.editor.features;
28✔
461
    selectedFeatureSelector = props => props.visState.editor.selectedFeature;
28✔
462
    featureCollectionSelector = createSelector(
25✔
463
      this.polygonFiltersSelector,
464
      this.featuresSelector,
465
      (polygonFilters, features) => ({
26✔
466
        type: 'FeatureCollection',
467
        features: features.concat(polygonFilters.map(f => f.value))
×
468
      })
469
    );
470
    // @ts-ignore - No overload matches this call
471
    selectedPolygonIndexSelector = createSelector(
25✔
472
      this.featureCollectionSelector,
473
      this.selectedFeatureSelector,
474
      (collection, selectedFeature) =>
475
        collection.features.findIndex(f => f.id === selectedFeature?.id)
26✔
476
    );
477
    selectedFeatureIndexArraySelector = createSelector(
25✔
478
      (value: number) => value,
25✔
479
      value => {
480
        return value < 0 ? [] : [value];
25!
481
      }
482
    );
483

484
    generateMapboxLayerMethodSelector = props => props.generateMapboxLayers ?? generateMapboxLayers;
25!
485

486
    mapboxLayersSelector = createSelector(
25✔
487
      this.layersSelector,
488
      this.layerDataSelector,
489
      this.layerOrderSelector,
490
      this.layersToRenderSelector,
491
      this.generateMapboxLayerMethodSelector,
492
      (layer, layerData, layerOrder, layersToRender, generateMapboxLayerMethod) =>
493
        generateMapboxLayerMethod(layer, layerData, layerOrder, layersToRender)
×
494
    );
495

496
    // merge in a background-color style if the basemap choice is NO_MAP_ID
497
    // used by <StyledMap> inline style prop
498
    mapStyleTypeSelector = props => props.mapStyle.styleType;
28✔
499
    mapStyleBackgroundColorSelector = props => props.mapStyle.backgroundColor;
28✔
500
    styleSelector = createSelector(
25✔
501
      this.mapStyleTypeSelector,
502
      this.mapStyleBackgroundColorSelector,
503
      (styleType, backgroundColor) => ({
26✔
504
        ...MAP_STYLE.container,
505
        ...(styleType === NO_MAP_ID ? {backgroundColor: rgbToHex(backgroundColor)} : {})
26!
506
      })
507
    );
508

509
    /* component private functions */
510
    _onCloseMapPopover = () => {
25✔
511
      this.props.visStateActions.onLayerClick(null);
×
512
    };
513

514
    _onLayerHover = (_idx: number, info: PickInfo<any> | null) => {
25✔
515
      this.props.visStateActions.onLayerHover(info, this.props.index);
×
516
    };
517

518
    _onLayerSetDomain = (
25✔
519
      idx: number,
520
      value: {domain: VisualChannelDomain; aggregatedBins: AggregatedBin[]}
521
    ) => {
522
      this.props.visStateActions.layerConfigChange(this.props.visState.layers[idx], {
×
523
        colorDomain: value.domain,
524
        aggregatedBins: value.aggregatedBins
525
      } as Partial<LayerBaseConfig>);
526
    };
527

528
    _onRedrawNeeded = (_idx: number) => {
25✔
529
      // updateMapUpdater always returns a new state object reference, which triggers re-render
530
      const {mapStateActions, index} = this.props;
×
531
      mapStateActions.updateMap({}, index);
×
532
    };
533

534
    _onLayerFilteredItemsChange = (idx, event) => {
25✔
535
      this.props.visStateActions.layerFilteredItemsChange(this.props.visState.layers[idx], event);
×
536
    };
537

538
    _onWMSFeatureInfo = (
25✔
539
      idx: number,
540
      data: {
541
        featureInfo: Array<{name: string; value: string}> | string | null;
542
        coordinate?: [number, number] | null;
543
      }
544
    ) => {
545
      this.props.visStateActions.wmsFeatureInfo(
×
546
        this.props.visState.layers[idx],
547
        data.featureInfo,
548
        data.coordinate
549
      );
550
    };
551

552
    _handleMapToggleLayer = layerId => {
25✔
553
      const {index: mapIndex = 0, visStateActions} = this.props;
1!
554
      visStateActions.toggleLayerForMap(mapIndex, layerId);
1✔
555
    };
556

557
    _onMapboxStyleUpdate = update => {
25✔
558
      // force refresh mapboxgl layers
559
      this.previousLayers = {};
×
560
      this._updateMapboxLayers();
×
561

562
      if (update && update.style) {
×
563
        // No attributions are needed if the style doesn't reference Mapbox sources
564
        this.setState({
×
565
          showBaseMapAttribution:
566
            isStyleUsingMapboxTiles(update.style) || !isStyleUsingOpenStreetMapTiles(update.style)
×
567
        });
568
      }
569

570
      if (typeof this.props.onMapStyleLoaded === 'function') {
×
571
        this.props.onMapStyleLoaded(this._map);
×
572
      }
573
    };
574

575
    _setMapRef = mapRef => {
25✔
576
      // Handle change of the map library
577
      if (this._map && mapRef) {
×
578
        const map = mapRef.getMap();
×
579
        if (map && this._map !== map) {
×
580
          this._map?.off(MAPBOXGL_STYLE_UPDATE, nop);
×
581
          this._map?.off(MAPBOXGL_RENDER, nop);
×
582
          this._map = null;
×
583
        }
584
      }
585

586
      if (!this._map && mapRef) {
×
587
        this._map = mapRef.getMap();
×
588
        // i noticed in certain context we don't access the actual map element
589
        if (!this._map) {
×
590
          return;
×
591
        }
592
        // bind mapboxgl event listener
593
        this._map.on(MAPBOXGL_STYLE_UPDATE, this._onMapboxStyleUpdate);
×
594

595
        this._map.on(MAPBOXGL_RENDER, () => {
×
596
          if (typeof this.props.onMapRender === 'function') {
×
597
            this.props.onMapRender(this._map);
×
598
          }
599
        });
600
      }
601

602
      if (this.props.getMapboxRef) {
×
603
        // The parent component can gain access to our MapboxGlMap by
604
        // providing this callback. Note that 'mapbox' will be null when the
605
        // ref is unset (e.g. when a split map is closed).
606
        this.props.getMapboxRef(mapRef, this.props.index);
×
607
      }
608
    };
609

610
    _onDeckInitialized(gl) {
611
      if (this.props.onDeckInitialized) {
×
612
        this.props.onDeckInitialized(this._deck, gl);
×
613
      }
614
    }
615

616
    /**
617
     * 1) Allow effects only for the first view.
618
     * 2) Prevent effect:preRender call without valid generated viewports.
619
     * @param viewIndex View index.
620
     * @returns Returns true if effects can be used.
621
     */
622
    _isOKToRenderEffects(viewIndex?: number): boolean {
623
      return !viewIndex && Boolean(this._deck?.viewManager?._viewports?.length);
29✔
624
    }
625

626
    _onBeforeRender = ({gl}) => {
25✔
627
      setLayerBlending(gl, this.props.visState.layerBlending);
×
628
    };
629

630
    _onDeckError = (error, layer) => {
25✔
631
      const errorMessage = error?.message || 'unknown-error';
23!
632
      const layerMessage = layer?.id ? ` in ${layer.id} layer` : '';
23!
633
      const errorMessageFull =
634
        errorMessage === 'WebGL context is lost'
23!
635
          ? 'Your GPU was disconnected. This can happen if your computer goes to sleep. It can also occur for other reasons, such as if you are running too many GPU applications.'
636
          : `An error in deck.gl: ${errorMessage}${layerMessage}.`;
637

638
      // Throttle error notifications, as React doesn't like too many state changes from here.
639
      const lastShown = this._deckGLErrorsElapsed[errorMessageFull];
23✔
640
      if (!lastShown || lastShown < Date.now() - THROTTLE_NOTIFICATION_TIME) {
23!
641
        this._deckGLErrorsElapsed[errorMessageFull] = Date.now();
23✔
642

643
        // Mark layer as invalid
644
        let extraLayerMessage = '';
23✔
645
        const {visStateActions} = this.props;
23✔
646
        if (layer) {
23!
647
          let topMostLayer = layer;
×
648
          while (topMostLayer.parent) {
×
649
            topMostLayer = topMostLayer.parent;
×
650
          }
651
          if (topMostLayer.props?.id) {
×
652
            visStateActions.layerSetIsValid(topMostLayer, false);
×
653
            extraLayerMessage = 'The layer has been disabled and highlighted.';
×
654
          }
655
        }
656

657
        // Create new error notification or update existing one with same id.
658
        // Update is required to preserve the order of notifications as they probably are going to "jump" based on order of errors.
659
        const {uiStateActions} = this.props;
23✔
660
        uiStateActions.addNotification(
23✔
661
          errorNotification({
662
            message: `${errorMessageFull} ${extraLayerMessage}`,
663
            id: errorMessageFull // treat the error message as id
664
          })
665
        );
666
      }
667
    };
668

669
    /* component render functions */
670

671
    /* eslint-disable complexity */
672
    _renderMapPopover() {
673
      // this check is for limiting the display of the `<MapPopover>` to the `<MapContainer>` the user is interacting with
674
      // the DeckGL onHover event handler adds a `mapIndex` property which is available in the `hoverInfo` object of `visState`
675
      if (this.props.index !== this.props.visState.hoverInfo?.mapIndex) {
29!
676
        return null;
29✔
677
      }
678

679
      // TODO: move this into reducer so it can be tested
680
      const {
681
        mapState,
682
        visState: {
683
          hoverInfo,
684
          clicked,
685
          datasets,
686
          interactionConfig,
687
          animationConfig,
688
          layers,
689
          mousePos: {mousePosition, coordinate, pinned}
690
        }
691
      } = this.props;
×
692
      const layersToRender = this.layersToRenderSelector(this.props);
×
693

694
      if (!mousePosition || !interactionConfig.tooltip) {
×
695
        return null;
×
696
      }
697

698
      const layerHoverProp = getLayerHoverProp({
×
699
        animationConfig,
700
        interactionConfig,
701
        hoverInfo,
702
        layers,
703
        layersToRender,
704
        datasets
705
      });
706

707
      const compareMode = interactionConfig.tooltip.config
×
708
        ? interactionConfig.tooltip.config.compareMode
709
        : false;
710

711
      let pinnedPosition = {x: 0, y: 0};
×
712
      let layerPinnedProp: LayerHoverProp | null = null;
×
713
      if (pinned || clicked) {
×
714
        // project lnglat to screen so that tooltip follows the object on zoom
715
        const viewport = getViewportFromMapState(mapState);
×
716
        const lngLat = clicked ? clicked.coordinate : pinned.coordinate;
×
717
        pinnedPosition = this._getHoverXY(viewport, lngLat);
×
718
        layerPinnedProp = getLayerHoverProp({
×
719
          animationConfig,
720
          interactionConfig,
721
          hoverInfo: clicked,
722
          layers,
723
          layersToRender,
724
          datasets
725
        });
726
        if (layerHoverProp && layerPinnedProp) {
×
727
          layerHoverProp.primaryData = layerPinnedProp.data;
×
728
          layerHoverProp.compareType = interactionConfig.tooltip.config.compareType;
×
729
        }
730
      }
731

732
      const commonProp = {
×
733
        onClose: this._onCloseMapPopover,
734
        zoom: mapState.zoom,
735
        container: this._deck ? this._deck.canvas : undefined
×
736
      };
737

738
      return (
×
739
        <ErrorBoundary>
740
          {layerPinnedProp && (
×
741
            <MapPopover
742
              {...pinnedPosition}
743
              {...commonProp}
744
              layerHoverProp={layerPinnedProp}
745
              coordinate={interactionConfig.coordinate.enabled && (pinned || {}).coordinate}
×
746
              frozen={true}
747
              isBase={compareMode}
748
              onSetFeatures={this.props.visStateActions.setFeatures}
749
              setSelectedFeature={this.props.visStateActions.setSelectedFeature}
750
              // @ts-ignore Argument of type 'Readonly<MapContainerProps>' is not assignable to parameter of type 'never'
751
              featureCollection={this.featureCollectionSelector(this.props)}
752
            />
753
          )}
754
          {layerHoverProp && (!layerPinnedProp || compareMode) && (
×
755
            <MapPopover
756
              x={mousePosition[0]}
757
              y={mousePosition[1]}
758
              {...commonProp}
759
              layerHoverProp={layerHoverProp}
760
              frozen={false}
761
              coordinate={interactionConfig.coordinate.enabled && coordinate}
×
762
              onSetFeatures={this.props.visStateActions.setFeatures}
763
              setSelectedFeature={this.props.visStateActions.setSelectedFeature}
764
              // @ts-ignore Argument of type 'Readonly<MapContainerProps>' is not assignable to parameter of type 'never'
765
              featureCollection={this.featureCollectionSelector(this.props)}
766
            />
767
          )}
768
        </ErrorBoundary>
769
      );
770
    }
771

772
    /* eslint-enable complexity */
773

774
    _getHoverXY(viewport, lngLat) {
775
      const screenCoord = !viewport || !lngLat ? null : viewport.project(lngLat);
×
776
      return screenCoord && {x: screenCoord[0], y: screenCoord[1]};
×
777
    }
778

779
    _renderDeckOverlay(
780
      layersForDeck,
781
      options: {primaryMap: boolean; isInteractive?: boolean; children?: React.ReactNode} = {
×
782
        primaryMap: false
783
      }
784
    ) {
785
      const {
786
        mapStyle,
787
        visState,
788
        mapState,
789
        visStateActions,
790
        mapboxApiAccessToken,
791
        mapboxApiUrl,
792
        deckGlProps,
793
        index,
794
        mapControls,
795
        deckRenderCallbacks,
796
        theme,
797
        generateDeckGLLayers,
798
        onMouseMove
799
      } = this.props;
29✔
800

801
      const {hoverInfo, editor} = visState;
29✔
802
      const {primaryMap, isInteractive, children} = options;
29✔
803

804
      // disable double click zoom when editor is in any draw mode
805
      const {mapDraw} = mapControls;
29✔
806
      const {active: editorMenuActive = false} = mapDraw || {};
29✔
807
      const isEditorDrawingMode = EditorLayerUtils.isDrawingActive(editorMenuActive, editor.mode);
29✔
808

809
      const internalViewState = this.context?.getInternalViewState(index);
29✔
810
      const internalMapState = {...mapState, ...internalViewState};
29✔
811
      const viewport = getViewportFromMapState(internalMapState);
29✔
812

813
      const editorFeatureSelectedIndex = this.selectedPolygonIndexSelector(this.props);
29✔
814

815
      const {setFeatures, onLayerClick, setSelectedFeature} = visStateActions;
29✔
816

817
      const generateDeckGLLayersMethod = generateDeckGLLayers ?? computeDeckLayers;
29✔
818
      const deckGlLayers = generateDeckGLLayersMethod(
29✔
819
        {
820
          visState,
821
          mapState: internalMapState,
822
          mapStyle
823
        },
824
        {
825
          mapIndex: index,
826
          primaryMap,
827
          mapboxApiAccessToken,
828
          mapboxApiUrl,
829
          layersForDeck,
830
          editorInfo: primaryMap
29!
831
            ? {
832
                editor,
833
                editorMenuActive,
834
                onSetFeatures: setFeatures,
835
                setSelectedFeature,
836
                // @ts-ignore Argument of type 'Readonly<MapContainerProps>' is not assignable to parameter of type 'never'
837
                featureCollection: this.featureCollectionSelector(this.props),
838
                selectedFeatureIndexes: this.selectedFeatureIndexArraySelector(
839
                  // @ts-ignore Argument of type 'unknown' is not assignable to parameter of type 'number'.
840
                  editorFeatureSelectedIndex
841
                ),
842
                viewport
843
              }
844
            : undefined
845
        },
846
        {
847
          onLayerHover: this._onLayerHover,
848
          onSetLayerDomain: this._onLayerSetDomain,
849
          onFilteredItemsChange: this._onLayerFilteredItemsChange,
850
          onWMSFeatureInfo: this._onWMSFeatureInfo,
851
          onRedrawNeeded: this._onRedrawNeeded
852
        },
853
        deckGlProps
854
      );
855

856
      const extraDeckParams: {
857
        getTooltip?: (info: any) => object | null;
858
        getCursor?: ({isDragging}: {isDragging: boolean}) => string;
859
      } = {};
29✔
860
      if (primaryMap) {
29!
861
        // Omit hover updates when the pointer position is invalid, ie. over UI overlays or
862
        // outside the map container. In those cases x/y may be < 0
863
        extraDeckParams.getTooltip = info => {
29✔
864
          const x = Number(info?.x);
×
865
          const y = Number(info?.y);
×
866
          if (Number.isNaN(x) || Number.isNaN(y) || x < 0 || y < 0) return null;
×
867

868
          return EditorLayerUtils.getTooltip(info, {
×
869
            editorMenuActive,
870
            editor,
871
            theme
872
          });
873
        };
874

875
        extraDeckParams.getCursor = ({isDragging}: {isDragging: boolean}) => {
29✔
876
          const editorCursor = EditorLayerUtils.getCursor({
×
877
            editorMenuActive,
878
            editor,
879
            hoverInfo
880
          });
881
          if (editorCursor) return editorCursor;
×
882

883
          if (isDragging) return 'grabbing';
×
884
          if (hoverInfo?.layer) return 'pointer';
×
885
          return 'grab';
×
886
        };
887
      }
888

889
      const effects = this._isOKToRenderEffects(index)
29!
890
        ? computeDeckEffects({visState, mapState})
891
        : [];
892

893
      const views = deckGlProps?.views
29!
894
        ? deckGlProps?.views()
895
        : new MapView({legacyMeterSizes: true});
896

897
      let allDeckGlProps = {
29✔
898
        ...deckGlProps,
899
        pickingRadius: DEFAULT_PICKING_RADIUS,
900
        views,
901
        layers: deckGlLayers,
902
        effects
903
      };
904

905
      if (typeof deckRenderCallbacks?.onDeckRender === 'function') {
29!
906
        allDeckGlProps = deckRenderCallbacks.onDeckRender(allDeckGlProps);
×
907
        if (!allDeckGlProps) {
×
908
          // if onDeckRender returns null, do not render deck.gl
909
          return null;
×
910
        }
911
      }
912

913
      return (
29✔
914
        <div
915
          {...(isInteractive
29!
916
            ? {
917
                onMouseMove: primaryMap
29!
918
                  ? event => {
919
                      onMouseMove?.(event);
×
920
                      this._onMouseMoveDebounced(event, viewport);
×
921
                    }
922
                  : undefined
923
              }
924
            : {style: {pointerEvents: 'none'}})}
925
        >
926
          <DeckGL
927
            id="default-deckgl-overlay"
928
            onLoad={() => {
929
              if (typeof deckRenderCallbacks?.onDeckLoad === 'function') {
×
930
                deckRenderCallbacks.onDeckLoad();
×
931
              }
932
            }}
933
            {...allDeckGlProps}
934
            controller={
935
              isInteractive
29!
936
                ? {
937
                    doubleClickZoom: !isEditorDrawingMode,
938
                    dragRotate: this.props.mapState.dragRotate
939
                  }
940
                : false
941
            }
942
            initialViewState={internalViewState}
943
            onBeforeRender={this._onBeforeRender}
944
            onViewStateChange={isInteractive ? this._onViewportChange : undefined}
29!
945
            {...extraDeckParams}
946
            onHover={
947
              isInteractive
29!
948
                ? data => {
949
                    const res = EditorLayerUtils.onHover(data, {
×
950
                      editorMenuActive,
951
                      editor,
952
                      hoverInfo
953
                    });
954
                    if (res) return;
×
955

956
                    this._onLayerHoverDebounced(data, index);
×
957
                  }
958
                : null
959
            }
960
            onClick={(data, event) => {
961
              // @ts-ignore
962
              normalizeEvent(event.srcEvent, viewport);
×
963
              const res = EditorLayerUtils.onClick(data, event, {
×
964
                editorMenuActive,
965
                editor,
966
                onLayerClick,
967
                setSelectedFeature,
968
                mapIndex: index
969
              });
970
              if (res) return;
×
971

972
              visStateActions.onLayerClick(data);
×
973
            }}
974
            onError={this._onDeckError}
975
            ref={comp => {
976
              // @ts-ignore
977
              if (comp && comp.deck && !this._deck) {
35✔
978
                // @ts-ignore
979
                this._deck = comp.deck;
1✔
980
              }
981
            }}
982
            onWebGLInitialized={gl => this._onDeckInitialized(gl)}
×
983
            onAfterRender={() => {
984
              if (typeof deckRenderCallbacks?.onDeckAfterRender === 'function') {
×
985
                deckRenderCallbacks.onDeckAfterRender(allDeckGlProps);
×
986
              }
987

988
              const anyActiveLayerLoading = areAnyDeckLayersLoading(allDeckGlProps.layers);
×
989
              if (anyActiveLayerLoading !== this.anyActiveLayerLoading) {
×
990
                this._onLayerLoadingStateChange();
×
991
                this.anyActiveLayerLoading = anyActiveLayerLoading;
×
992
              }
993
            }}
994
          >
995
            {children}
996
          </DeckGL>
997
        </div>
998
      );
999
    }
1000

1001
    _updateMapboxLayers() {
1002
      const mapboxLayers = this.mapboxLayersSelector(this.props);
×
1003
      if (!Object.keys(mapboxLayers).length && !Object.keys(this.previousLayers).length) {
×
1004
        return;
×
1005
      }
1006

1007
      updateMapboxLayers(this._map, mapboxLayers, this.previousLayers);
×
1008

1009
      this.previousLayers = mapboxLayers;
×
1010
    }
1011

1012
    _renderMapboxOverlays() {
1013
      if (this._map && this._map.isStyleLoaded()) {
29!
1014
        this._updateMapboxLayers();
×
1015
      }
1016
    }
1017
    _onViewportChangePropagateDebounced = debounce(() => {
25✔
1018
      const viewState = this.context?.getInternalViewState(this.props.index);
×
1019
      onViewPortChange(
×
1020
        viewState,
1021
        this.props.mapStateActions.updateMap,
1022
        this.props.onViewStateChange,
1023
        this.props.primary,
1024
        this.props.index
1025
      );
1026
    }, DEBOUNCE_VIEWPORT_PROPAGATE);
1027

1028
    _onViewportChange = viewport => {
25✔
1029
      const {viewState} = viewport;
×
1030
      if (this.props.isExport) {
×
1031
        // Image export map shouldn't be interactive (otherwise this callback can
1032
        // lead to inadvertent changes to the state of the main map)
1033
        return;
×
1034
      }
1035
      const {setInternalViewState} = this.context;
×
1036
      setInternalViewState(viewState, this.props.index);
×
1037
      this._onViewportChangePropagateDebounced();
×
1038
    };
1039

1040
    _onLayerHoverDebounced = debounce((data, index) => {
25✔
1041
      this.props.visStateActions.onLayerHover(data, index);
×
1042
    }, DEBOUNCE_MOUSE_MOVE_PROPAGATE);
1043

1044
    _onMouseMoveDebounced = debounce((event, viewport) => {
25✔
1045
      this.props.visStateActions.onMouseMove(normalizeEvent(event, viewport));
×
1046
    }, DEBOUNCE_MOUSE_MOVE_PROPAGATE);
1047

1048
    _onLayerLoadingStateChange = debounce(() => {
25✔
1049
      // trigger loading indicator update without any change to update UI
1050
      this.props.visStateActions.setLoadingIndicator({change: 0});
×
1051
    }, DEBOUNCE_LOADING_STATE_PROPAGATE);
1052

1053
    _toggleMapControl = panelId => {
25✔
1054
      const {index, uiStateActions} = this.props;
2✔
1055

1056
      uiStateActions.toggleMapControl(panelId, Number(index));
2✔
1057
    };
1058

1059
    /* eslint-disable complexity */
1060
    _renderMap() {
1061
      const {
1062
        visState,
1063
        mapState,
1064
        mapStyle,
1065
        mapStateActions,
1066
        MapComponent = Map,
×
1067
        mapboxApiAccessToken,
1068
        // mapboxApiUrl,
1069
        mapControls,
1070
        isExport,
1071
        locale,
1072
        uiStateActions,
1073
        visStateActions,
1074
        index,
1075
        primary,
1076
        bottomMapContainerProps,
1077
        topMapContainerProps,
1078
        theme,
1079
        datasetAttributions = [],
×
1080
        containerId = 0,
13✔
1081
        isLoadingIndicatorVisible,
1082
        activeSidePanel,
1083
        sidePanelWidth
1084
      } = this.props;
29✔
1085

1086
      const {layers, datasets, editor, interactionConfig} = visState;
29✔
1087

1088
      const layersToRender = this.layersToRenderSelector(this.props);
29✔
1089
      const layersForDeck = this.layersForDeckSelector(this.props);
29✔
1090

1091
      // Current style can be a custom style, from which we pull the mapbox API acccess token
1092
      const currentStyle = mapStyle.mapStyles?.[mapStyle.styleType];
29✔
1093
      const baseMapLibraryName = getBaseMapLibrary(currentStyle);
29✔
1094
      const baseMapLibraryConfig =
1095
        getApplicationConfig().baseMapLibraryConfig?.[baseMapLibraryName];
29✔
1096

1097
      const internalViewState = this.context?.getInternalViewState(index);
29✔
1098
      const mapProps = {
29✔
1099
        ...internalViewState,
1100
        preserveDrawingBuffer: true,
1101
        mapboxAccessToken: currentStyle?.accessToken || mapboxApiAccessToken,
58✔
1102
        // baseApiUrl: mapboxApiUrl,
1103
        mapLib: baseMapLibraryConfig.getMapLib(),
1104
        transformRequest:
1105
          this.props.transformRequest ||
58✔
1106
          transformRequest(currentStyle?.accessToken || mapboxApiAccessToken)
58✔
1107
      };
1108

1109
      const hasGeocoderLayer = Boolean(layers.find(l => l.id === GEOCODER_LAYER_ID));
29✔
1110
      const isSplit = Boolean(mapState.isSplit);
29✔
1111

1112
      const deck = this._renderDeckOverlay(layersForDeck, {
29✔
1113
        primaryMap: true,
1114
        isInteractive: true,
1115
        children: (
1116
          <MapComponent
1117
            key={`bottom-${baseMapLibraryName}`}
1118
            {...mapProps}
1119
            mapStyle={mapStyle.bottomMapStyle ?? EMPTY_MAPBOX_STYLE}
58✔
1120
            {...bottomMapContainerProps}
1121
            ref={this._setMapRef}
1122
          />
1123
        )
1124
      });
1125
      if (!deck) {
29!
1126
        // deckOverlay can be null if onDeckRender returns null
1127
        // in this case we don't want to render the map
1128
        return null;
×
1129
      }
1130
      return (
29✔
1131
        <>
1132
          <MapControl
1133
            mapState={mapState}
1134
            datasets={datasets}
1135
            availableLocales={LOCALE_CODES_ARRAY}
1136
            dragRotate={mapState.dragRotate}
1137
            isSplit={isSplit}
1138
            primary={Boolean(primary)}
1139
            isExport={isExport}
1140
            layers={layers}
1141
            layersToRender={layersToRender}
1142
            mapIndex={index || 0}
53✔
1143
            mapControls={mapControls}
1144
            readOnly={this.props.readOnly}
1145
            scale={mapState.scale || 1}
58✔
1146
            logoComponent={this.props.logoComponent}
1147
            top={
1148
              interactionConfig.geocoder && interactionConfig.geocoder.enabled
87!
1149
                ? theme.mapControlTop
1150
                : 0
1151
            }
1152
            editor={editor}
1153
            locale={locale}
1154
            onTogglePerspective={mapStateActions.togglePerspective}
1155
            onToggleSplitMap={mapStateActions.toggleSplitMap}
1156
            onMapToggleLayer={this._handleMapToggleLayer}
1157
            onToggleMapControl={this._toggleMapControl}
1158
            onToggleSplitMapViewport={mapStateActions.toggleSplitMapViewport}
1159
            onSetEditorMode={visStateActions.setEditorMode}
1160
            onSetLocale={uiStateActions.setLocale}
1161
            onToggleEditorVisibility={visStateActions.toggleEditorVisibility}
1162
            onLayerVisConfigChange={visStateActions.layerVisConfigChange}
1163
            mapHeight={mapState.height}
1164
            setMapControlSettings={uiStateActions.setMapControlSettings}
1165
            activeSidePanel={activeSidePanel}
1166
          />
1167
          {isSplitSelector(this.props) && <Droppable containerId={containerId} />}
38✔
1168

1169
          {deck}
1170
          {this._renderMapboxOverlays()}
1171
          <Editor
1172
            index={index || 0}
53✔
1173
            datasets={datasets}
1174
            editor={editor}
1175
            filters={this.polygonFiltersSelector(this.props)}
1176
            layers={layers}
1177
            onDeleteFeature={visStateActions.deleteFeature}
1178
            onSelect={visStateActions.setSelectedFeature}
1179
            onTogglePolygonFilter={visStateActions.setPolygonFilterLayer}
1180
            onSetEditorMode={visStateActions.setEditorMode}
1181
            style={{
1182
              pointerEvents: 'all',
1183
              position: 'absolute',
1184
              display: editor.visible ? 'block' : 'none'
29!
1185
            }}
1186
          />
1187
          {this.props.children}
1188
          {mapStyle.topMapStyle ? (
29!
1189
            <MapComponent
1190
              key={`top-${baseMapLibraryName}`}
1191
              viewState={internalViewState}
1192
              mapStyle={mapStyle.topMapStyle}
1193
              style={MAP_STYLE.top}
1194
              mapboxAccessToken={mapProps.mapboxAccessToken}
1195
              transformRequest={mapProps.transformRequest}
1196
              mapLib={baseMapLibraryConfig.getMapLib()}
1197
              {...topMapContainerProps}
1198
            />
1199
          ) : null}
1200

1201
          {hasGeocoderLayer
29!
1202
            ? this._renderDeckOverlay(
1203
                {[GEOCODER_LAYER_ID]: hasGeocoderLayer},
1204
                {primaryMap: false, isInteractive: false}
1205
              )
1206
            : null}
1207
          {this._renderMapPopover()}
1208
          {primary !== isSplit ? (
29✔
1209
            <LoadingIndicator
1210
              isVisible={Boolean(isLoadingIndicatorVisible || this.anyActiveLayerLoading)}
42✔
1211
              activeSidePanel={Boolean(activeSidePanel)}
1212
              sidePanelWidth={sidePanelWidth}
1213
            />
1214
          ) : null}
1215
          {this.props.primary ? (
29✔
1216
            <Attribution
1217
              showBaseMapLibLogo={this.state.showBaseMapAttribution}
1218
              showOsmBasemapAttribution={true}
1219
              datasetAttributions={datasetAttributions}
1220
              baseMapLibraryConfig={baseMapLibraryConfig}
1221
            />
1222
          ) : null}
1223
        </>
1224
      );
1225
    }
1226

1227
    render() {
1228
      const {visState, mapStyle} = this.props;
29✔
1229
      const mapContent = this._renderMap();
29✔
1230
      if (!mapContent) {
29!
1231
        // mapContent can be null if onDeckRender returns null
1232
        // in this case we don't want to render the map
1233
        return null;
×
1234
      }
1235

1236
      const currentStyle = mapStyle.mapStyles?.[mapStyle.styleType];
29✔
1237
      const baseMapLibraryName = getBaseMapLibrary(currentStyle);
29✔
1238
      const baseMapLibraryConfig =
1239
        getApplicationConfig().baseMapLibraryConfig?.[baseMapLibraryName];
29✔
1240

1241
      return (
29✔
1242
        <StyledMap
1243
          ref={this._ref}
1244
          style={this.styleSelector(this.props)}
1245
          onContextMenu={event => event.preventDefault()}
×
1246
          $mixBlendMode={visState.overlayBlending}
1247
          $mapLibCssClass={baseMapLibraryConfig.mapLibCssClass}
1248
        >
1249
          {mapContent}
1250
        </StyledMap>
1251
      );
1252
    }
1253
  }
1254

1255
  return withTheme(MapContainer);
14✔
1256
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc