UNPKG

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
// 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 }) ] : []) ]; } }