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

keplergl / kepler.gl / 24343963226

13 Apr 2026 12:42PM UTC coverage: 60.04% (-0.2%) from 60.224%
24343963226

push

github

web-flow
feat: upgrade heatmap layer from mapbox to deckgl (#3372)

* feat: upgrade heatmap layer from mapbox to deckgl

Signed-off-by: Ihor Dykhta <dikhta.igor@gmail.com>

* fixes to shader logic

Signed-off-by: Ihor Dykhta <dikhta.igor@gmail.com>

* fix for noneLayerDataAffectingProps

Signed-off-by: Ihor Dykhta <dikhta.igor@gmail.com>

* remove _unfiltered from layer props

Signed-off-by: Ihor Dykhta <dikhta.igor@gmail.com>

---------

Signed-off-by: Ihor Dykhta <dikhta.igor@gmail.com>

6699 of 13294 branches covered (50.39%)

Branch coverage included in aggregate %.

33 of 68 new or added lines in 4 files covered. (48.53%)

18 existing lines in 3 files now uncovered.

13747 of 20760 relevant lines covered (66.22%)

77.63 hits per line

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

1.02
/src/layers/src/mapbox-utils.ts
1
// SPDX-License-Identifier: MIT
2
// Copyright contributors to the kepler.gl project
3

4
import Layer, {OVERLAY_TYPE_CONST} from './base-layer';
5
import {Feature} from 'geojson';
6

7
import {findById} from '@kepler.gl/utils';
8

9
/**
10
 * This function will convert layers to mapbox layers
11
 * @param layers the layers to be converted
12
 * @param layerData extra layer information
13
 * @param layerOrder the order by which we should convert layers
14
 * @param layersToRender {[id]: true | false} object whether each layer should be rendered
15
 * @returns
16
 */
17
export function generateMapboxLayers(
18
  layers: Layer[] = [],
×
19
  layerData: any[] = [],
×
20
  layerOrder: string[] = [],
×
21
  layersToRender: {[key: string]: boolean} = {}
×
22
): {[key: string]: Layer} {
23
  if (layerData.length > 0) {
×
24
    return layerOrder
×
25
      .slice()
26
      .reverse()
27
      .filter(layerId => {
28
        const layer = findById(layerId)(layers);
×
29
        return layer?.overlayType === OVERLAY_TYPE_CONST.mapboxgl && layersToRender[layerId];
×
30
      })
31
      .reduce((acc, layerId) => {
32
        const layerIndex = layers.findIndex(l => l.id === layerId);
×
33
        if (layerIndex === -1) {
×
34
          return acc;
×
35
        }
36

37
        const layer = layers[layerIndex];
×
38

39
        if (!(layer.overlayType === OVERLAY_TYPE_CONST.mapboxgl && layersToRender[layerId])) {
×
40
          return acc;
×
41
        }
42

43
        return {
×
44
          ...acc,
45
          [layer.id]: {
46
            id: layer.id,
47
            data: layerData[layerIndex].data,
48
            isVisible: layer.config.isVisible,
49
            config: layerData[layerIndex].config,
50
            hidden: layer.config.hidden,
51
            sourceId: layerData[layerIndex].config.source
52
          }
53
        };
54
      }, {});
55
  }
56

57
  return {};
×
58
}
59

60
type newLayersType = {
61
  [key: string]: Layer & Partial<{data: any; sourceId: any; isVisible: boolean}>;
62
};
63
type oldLayersType = {[key: string]: Layer & {data?: any}};
64
/**
65
 * Update mapbox layers on the given map
66
 * @param map
67
 * @param newLayers Map of new mapbox layers to be displayed
68
 * @param oldLayers Map of the old layers to be compare with the current ones to detect deleted layers
69
 *                  {layerId: sourceId}
70
 */
71
export function updateMapboxLayers(
72
  map,
73
  newLayers: newLayersType = {},
×
74
  oldLayers: oldLayersType | null = null
×
75
) {
76
  // delete no longer existed old layers
77
  if (oldLayers) {
×
78
    checkAndRemoveOldLayers(map, oldLayers, newLayers);
×
79
  }
80

81
  // insert or update new layer
82
  Object.values(newLayers).forEach(overlay => {
×
83
    const {id: layerId, config, data, sourceId, isVisible} = overlay;
×
84
    if (!data && !config) {
×
85
      return;
×
86
    }
87

88
    const {data: oldData, config: oldConfig} = (oldLayers && oldLayers[layerId]) || {};
×
89

90
    if (data && data !== oldData) {
×
91
      updateSourceData(map, sourceId, data);
×
92
    }
93

94
    // compare with previous configs
95
    if (oldConfig !== config) {
×
96
      updateLayerConfig(map, layerId, config, isVisible);
×
97
    }
98
  });
99
}
100

101
function checkAndRemoveOldLayers(map, oldLayers: oldLayersType, newLayers: newLayersType) {
102
  Object.keys(oldLayers).forEach(layerId => {
×
103
    if (!newLayers[layerId]) {
×
104
      map.removeLayer(layerId);
×
105
    }
106
  });
107
}
108

109
function updateLayerConfig(map, layerId, config, isVisible) {
110
  const mapboxLayer = map.getLayer(layerId);
×
111

112
  if (mapboxLayer) {
×
113
    // check if layer already is set
114
    // remove it if exists
115
    map.removeLayer(layerId);
×
116
  }
117

118
  map.addLayer(config);
×
119
  map.setLayoutProperty(layerId, 'visibility', isVisible ? 'visible' : 'none');
×
120
}
121

122
function updateSourceData(map, sourceId, data) {
123
  const source = map.getSource(sourceId);
×
124

125
  if (!source) {
×
126
    map.addSource(sourceId, {
×
127
      type: 'geojson',
128
      data
129
    });
130
  } else {
131
    source.setData(data);
×
132
  }
133
}
134

135
/**
136
 *
137
 * @param filteredIndex
138
 * @param getGeometry {({index: number}) => any}
139
 * @param getProperties {({index: number}) => any}
140
 * @returns FeatureCollection
141
 */
142
export function geoJsonFromData(
143
  filteredIndex: number[] = [],
×
144
  getGeometry: (arg: {index: number}) => any,
145
  getProperties: (arg: {index: number}) => object
146
) {
UNCOV
147
  const geojson: {type: string; features: Feature[]} = {
×
148
    type: 'FeatureCollection',
149
    features: []
150
  };
151

UNCOV
152
  for (let i = 0; i < filteredIndex.length; i++) {
×
UNCOV
153
    const index = filteredIndex[i];
×
UNCOV
154
    const rowIndex = {index};
×
UNCOV
155
    const geometry = getGeometry(rowIndex);
×
156

UNCOV
157
    if (geometry) {
×
UNCOV
158
      geojson.features.push({
×
159
        type: 'Feature',
160
        properties: {
161
          index,
162
          ...getProperties(rowIndex)
163
        },
164
        geometry
165
      });
166
    }
167
  }
168

UNCOV
169
  return geojson;
×
170
}
171

172
export const prefixGpuField = name => `gpu:${name}`;
13✔
173

174
export function gpuFilterToMapboxFilter(gpuFilter) {
UNCOV
175
  const {filterRange, filterValueUpdateTriggers} = gpuFilter;
×
176

UNCOV
177
  const hasFilter = Object.values(filterValueUpdateTriggers).filter(d => d);
×
178

UNCOV
179
  if (!hasFilter.length) {
×
UNCOV
180
    return null;
×
181
  }
182

UNCOV
183
  const condition = ['all'];
×
184

185
  // [">=", key, value]
186
  // ["<=", key, value]
UNCOV
187
  const expressions = Object.values(filterValueUpdateTriggers as ({name: string} | null)[]).reduce(
×
188
    (accu: any[], gpu, i) =>
UNCOV
189
      gpu?.name
×
190
        ? [
191
            ...accu,
192
            ['>=', prefixGpuField(gpu.name), filterRange[i][0]],
193
            ['<=', prefixGpuField(gpu.name), filterRange[i][1]]
194
          ]
195
        : accu,
196
    condition
197
  );
198

UNCOV
199
  return expressions;
×
200
}
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