kepler.gl.geoiq
Version:
kepler.gl is a webgl based application to visualize large scale location data in the browser
353 lines (317 loc) • 9.49 kB
JavaScript
// Copyright (c) 2023 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import memoize from 'lodash.memoize';
import uniq from 'lodash.uniq';
import Layer, {colorMaker} from '../base-layer';
import DeckGLMVTLayer from 'deckgl-layers/mvt-geojson-layer/mvt-geojson-layer';
import {GeoJsonLayer as DeckGLGeoJsonLayer} from 'deck.gl';
import MVTLayerIcon from './mvt-geojson-layer-icon';
import {
GEOJSON_FIELDS,
HIGHLIGH_COLOR_3D,
NO_VALUE_COLOR,
CHANNEL_SCALES,
SCALE_TYPES,
ALL_FIELD_TYPES
} from 'constants/default-settings';
import {hexToRgb} from 'utils/color-utils';
import {
getSampleData,
getLatLngBounds,
maybeToDate,
getSortingFunction,
notNullorUndefined
} from 'utils/data-utils';
import {
getQuantileDomain,
getOrdinalDomain,
getLinearDomain
} from 'utils/data-scale-utils';
export const geojsonVisConfigs = {
opacity: 'opacity',
thickness: {
type: 'number',
defaultValue: 0.5,
label: 'Stroke Width',
isRanged: false,
range: [0, 100],
step: 0.1,
group: 'stroke',
property: 'thickness'
},
strokeColor: 'strokeColor',
colorRange: 'colorRange',
strokeColorRange: 'strokeColorRange',
radius: 'radius',
sizeRange: 'strokeWidthRange',
radiusRange: 'radiusRange',
heightRange: 'elevationRange',
elevationScale: 'elevationScale',
stroked: 'stroked',
filled: 'filled',
enable3d: 'enable3d',
wireframe: 'wireframe',
featData: []
};
export const geoJsonRequiredColumns = [''];
const defaultGetFieldValue = (field, d) => d[field.tableFieldIndex - 1];
export default class MVTLayer extends Layer {
constructor(props) {
super(props);
this.registerVisConfig(geojsonVisConfigs);
}
get visualChannels() {
return {
...super.visualChannels,
strokeColor: {
property: 'strokeColor',
field: 'strokeColorField',
scale: 'strokeColorScale',
domain: 'strokeColorDomain',
range: 'strokeColorRange',
key: 'strokeColor',
channelScaleType: CHANNEL_SCALES.color,
condition: config => config.visConfig.stroked
},
size: {
...super.visualChannels.size,
property: 'stroke',
condition: config => config.visConfig.stroked
},
height: {
property: 'height',
field: 'heightField',
scale: 'heightScale',
domain: 'heightDomain',
range: 'heightRange',
key: 'height',
channelScaleType: 'size',
condition: config => config.visConfig.enable3d
},
radius: {
property: 'radius',
field: 'radiusField',
scale: 'radiusScale',
domain: 'radiusDomain',
range: 'radiusRange',
key: 'radius',
channelScaleType: 'radius'
}
};
}
// cScale =
// this.config.colorField &&
// this.getVisChannelScale(
// 'quantile',
// this.updateLayerDomain(this),
// colorRange.colors.map(hexToRgb)
// );
get type() {
return 'MVT';
}
get layerIcon() {
return MVTLayerIcon;
}
getDefaultLayerConfig(props = {}) {
return {
...super.getDefaultLayerConfig(props),
// add height visual channel
heightField: null,
heightDomain: [0, 1],
heightScale: 'linear',
// add radius visual channel
radiusField: null,
radiusDomain: [0, 1],
radiusScale: 'linear',
// add stroke color visual channel
strokeColorField: null,
strokeColorDomain: [0, 1],
strokeColorScale: 'quantile'
};
}
shouldRenderLayer() {
return this.type && this.config.isVisible;
}
calculateLayerDomain(dataset, visualChannel) {
const {allData, filteredIndexForDomain} = dataset;
const defaultDomain = [0, 1];
const {scale} = visualChannel;
const scaleType = this.config[scale];
if (!allData) {
return defaultDomain;
}
const field = this.config[visualChannel.field];
if (!field) {
// if colorField or sizeField were set back to null
return defaultDomain;
}
if (!SCALE_TYPES[scaleType]) {
Console.error(`scale type ${scaleType} not supported`);
return defaultDomain;
}
// TODO: refactor to add valueAccessor to field
const fieldIdx = field.tableFieldIndex - 1;
const isTime = field.type === ALL_FIELD_TYPES.timestamp;
const valueAccessor = maybeToDate.bind(
null,
isTime,
fieldIdx,
field.format
);
const indexValueAccessor = i => valueAccessor(allData[i]);
const sortFunction = getSortingFunction(field.type);
switch (scaleType) {
case SCALE_TYPES.ordinal:
case SCALE_TYPES.point:
// do not recalculate ordinal domain based on filtered data
// don't need to update ordinal domain every time
return getOrdinalDomain(allData, valueAccessor);
case SCALE_TYPES.quantile:
return getQuantileDomain(
filteredIndexForDomain,
indexValueAccessor,
sortFunction
);
case SCALE_TYPES.quantize:
case SCALE_TYPES.linear:
case SCALE_TYPES.sqrt:
default:
return getLinearDomain(filteredIndexForDomain, indexValueAccessor);
}
}
getEncodedChannelValue(
scale,
data,
field,
nullValue = NO_VALUE_COLOR,
getValue = defaultGetFieldValue
) {
const {type} = field;
// const value = getValue(field, data);
const value = data;
if (!notNullorUndefined(value)) {
return nullValue;
}
let attributeValue;
if (type === ALL_FIELD_TYPES.timestamp) {
// shouldn't need to convert here
// scale Function should take care of it
attributeValue = scale(new Date(value));
} else {
attributeValue = scale(value);
}
if (!notNullorUndefined(attributeValue)) {
attributeValue = nullValue;
}
return attributeValue;
}
renderLayer({
idx,
objectHovered,
mapState,
datasets,
filters,
loadEDLinkData
}) {
var config = this.config;
const zoomFactor = this.getZoomFactor(mapState);
this.updateLayerDomain(datasets[this.config.dataId]);
const {
colorField,
strokeColorField,
strokeColorScale,
strokeColorDomain,
sizeField,
sizeScale,
sizeDomain,
color,
visConfig
} = this.config;
const cScale =
colorField &&
this.getVisChannelScale(
this.config.colorScale,
this.config.colorDomain,
this.config.visConfig.colorRange.colors.map(hexToRgb)
);
// stroke color
const scScale =
strokeColorField &&
this.getVisChannelScale(
strokeColorScale,
strokeColorDomain,
visConfig.strokeColorRange.colors.map(hexToRgb)
);
// calculate stroke scale - if stroked = true
const sScale =
sizeField &&
visConfig.stroked &&
this.getVisChannelScale(sizeScale, sizeDomain, visConfig.sizeRange);
// const getFillColor = d => {
// console.log('d', d);
// cScale
// ? this.getEncodedChannelValue(cScale, d.properties[0], colorField)
// : [128, 128, 128];
// };
var tempLayr = new DeckGLMVTLayer({
id: this.id,
idx,
url: this.config.dataId,
dataset: datasets[this.config.dataId],
deckGLFilters: filters.filter(f => f.dataId === config.dataId),
cScale,
scScale,
sScale,
lineWidthScale: visConfig.thickness * zoomFactor * 8,
strokeColorField,
strokeColor: visConfig.strokeColor,
strokeColorRange: visConfig.strokeColorRange,
sizeField,
sizeRange: visConfig.sizeRange,
colorField,
filled: visConfig.filled,
stroked: visConfig.stroked,
opacity: visConfig.opacity,
getEncodedChannelValue: (cScale, props, colorField) =>
this.getEncodedChannelValue(cScale, props, colorField),
color,
loadEDLinkData
});
return [
tempLayr,
...(this.isLayerHovered(objectHovered)
? [
new DeckGLGeoJsonLayer({
id: `${this.id}-hovered`,
data: [objectHovered.object],
getLineWidth: 1,
getRadius: 23,
getElevation: 45,
getLineColor: [255, 255, 0],
getFillColor: [0, 255, 255],
stroked: true,
pickable: false,
filled: false
})
]
: [])
];
}
}