diff --git a/examples/custom-xviz-layers/README.md b/examples/custom-xviz-layers/README.md new file mode 100644 index 00000000..e7fa5355 --- /dev/null +++ b/examples/custom-xviz-layers/README.md @@ -0,0 +1,5 @@ +# streetscape.gl Starter Kit + +This is an example showing the use of custom xviz layers for custom xviz streams. + +[Instructions for running this application](../../docs/get-started/starter-kit.md) diff --git a/examples/custom-xviz-layers/index.html b/examples/custom-xviz-layers/index.html new file mode 100644 index 00000000..eb7a7e83 --- /dev/null +++ b/examples/custom-xviz-layers/index.html @@ -0,0 +1,23 @@ + + + + + streetscape.gl quick start + + + + +
+ + + diff --git a/examples/custom-xviz-layers/package.json b/examples/custom-xviz-layers/package.json new file mode 100644 index 00000000..b45e1fee --- /dev/null +++ b/examples/custom-xviz-layers/package.json @@ -0,0 +1,34 @@ +{ + "name": "streetscape.gl-quick-start", + "description": "A template app of streetscape.gl", + "version": "0.1.0", + "scripts": { + "start-local": "webpack-dev-server --env.local --progress --hot --open", + "start-streaming-local": "webpack-dev-server --env.local --env.stream --progress --hot --open", + "start-live-local": "webpack-dev-server --env.local --env.live --progress --hot --open", + "start": "webpack-dev-server --progress --hot --open", + "start-streaming": "webpack-dev-server --env.stream --progress --hot --open", + "start-live": "webpack-dev-server --env.live --progress --hot --open" + }, + "dependencies": { + "@deck.gl/extensions": "^8.4.13", + "react": "^16.3.0", + "react-dom": "^16.3.0", + "streetscape.gl": "^1.0.0" + }, + "devDependencies": { + "@babel/cli": "^7.0.0", + "@babel/core": "^7.0.0", + "@babel/plugin-proposal-class-properties": "^7.0.0", + "@babel/preset-env": "^7.0.0", + "@babel/preset-react": "^7.0.0", + "babel-loader": "^8.0.0", + "source-map-loader": "^0.2.3", + "webpack": "^4.20.0", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.1" + }, + "resolutions": { + "@deck.gl/core": "8.4.12" + } +} diff --git a/examples/custom-xviz-layers/src/app.js b/examples/custom-xviz-layers/src/app.js new file mode 100644 index 00000000..6ef8edf6 --- /dev/null +++ b/examples/custom-xviz-layers/src/app.js @@ -0,0 +1,172 @@ +// Copyright (c) 2019 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. + +/* global document, console */ +/* eslint-disable no-console, no-unused-vars, no-undef */ +import React, {PureComponent} from 'react'; +import {render} from 'react-dom'; +import {PathStyleExtension} from '@deck.gl/extensions'; +import {setXVIZConfig, getXVIZConfig} from '@xviz/parser'; +import { + LaneLayer, + LogViewer, + PlaybackControl, + StreamSettingsPanel, + MeterWidget, + TrafficLightWidget, + TurnSignalWidget, + XVIZPanel, + VIEW_MODE +} from 'streetscape.gl'; +import {Form} from '@streetscape.gl/monochrome'; + +import {XVIZ_CONFIG, APP_SETTINGS, MAPBOX_TOKEN, MAP_STYLE, XVIZ_STYLE, CAR} from './constants'; + +setXVIZConfig(XVIZ_CONFIG); + +const TIMEFORMAT_SCALE = getXVIZConfig().TIMESTAMP_FORMAT === 'seconds' ? 1000 : 1; + +// __IS_STREAMING__ and __IS_LIVE__ are defined in webpack.config.js +const exampleLog = require(__IS_STREAMING__ + ? './log-from-stream' + : __IS_LIVE__ + ? './log-from-live' + : './log-from-file').default; + +class Example extends PureComponent { + state = { + log: exampleLog, + settings: { + viewMode: 'PERSPECTIVE', + showTooltip: false + } + }; + + componentDidMount() { + this.state.log.on('error', console.error).connect(); + } + + _onSettingsChange = changedSettings => { + this.setState({ + settings: {...this.state.settings, ...changedSettings} + }); + }; + + render() { + const {log, settings} = this.state; + + return ( +
+
+ +
+ +
+
+ +
+
+
+ { + // + return streamMetadata.primitive_type === 'polyline'; + }, + /* + * Defines props to spread into `this.getSublayerProps` in the XVIZLayer + * @param {Object} xvizLayerProps - all props passed into XVIZLayer + * @param {Object} primitiveLayerProps - all layer-type props that are used for the primitive layer + * @param {Object} state - the XVIZLayer state + */ + getSubProps: ({xvizLayerProps, primitiveLayerProps, state}) => { + return { + id: 'path-dual', + getWidth: [0.1, 0.1, 0.1], + getPath: f => f.vertices, + getDashArray: () => [1, 2], + getDashArray2: () => [2, 4], + getColor2: state.layerProps.getColor, + extensions: [new PathStyleExtension({dash: true})] + }; + } + } + ]} + /> +
+ +
+ +
+ +
+ (x > 6 ? 'FAST' : '')} + min={0} + max={20} + /> +
+
+
+ new Date(x * TIMEFORMAT_SCALE).toUTCString()} + /> +
+
+
+ ); + } +} + +render(, document.getElementById('app')); diff --git a/examples/custom-xviz-layers/src/constants.js b/examples/custom-xviz-layers/src/constants.js new file mode 100644 index 00000000..ef7bf779 --- /dev/null +++ b/examples/custom-xviz-layers/src/constants.js @@ -0,0 +1,54 @@ +// Copyright (c) 2019 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 {CarMesh} from 'streetscape.gl'; + +/* eslint-disable camelcase */ +export const MAPBOX_TOKEN = process.env.MapboxAccessToken; // eslint-disable-line + +export const MAP_STYLE = 'mapbox://styles/mapbox/light-v9'; + +export const XVIZ_CONFIG = { + PLAYBACK_FRAME_RATE: 10 +}; + +export const CAR = CarMesh.sedan({ + origin: [1.08, -0.32, 0], + length: 4.3, + width: 2.2, + height: 1.5, + color: [160, 160, 160] +}); + +export const APP_SETTINGS = { + viewMode: { + type: 'select', + title: 'View Mode', + data: {TOP_DOWN: 'Top Down', PERSPECTIVE: 'Perspective', DRIVER: 'Driver'} + }, + showTooltip: { + type: 'toggle', + title: 'Show Tooltip' + } +}; + +export const XVIZ_STYLE = { + '/tracklets/objects': [{name: 'selected', style: {fill_color: '#ff8000aa'}}], + '/lidar/points': [{style: {point_color_mode: 'ELEVATION'}}] +}; diff --git a/examples/custom-xviz-layers/src/log-from-file.js b/examples/custom-xviz-layers/src/log-from-file.js new file mode 100644 index 00000000..0d375df4 --- /dev/null +++ b/examples/custom-xviz-layers/src/log-from-file.js @@ -0,0 +1,31 @@ +// Copyright (c) 2019 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 {XVIZFileLoader} from 'streetscape.gl'; + +export default new XVIZFileLoader({ + timingsFilePath: + 'https://raw.githubusercontent.com/uber/xviz-data/master/kitti/2011_09_26_drive_0005_sync/0-frame.json', + getFilePath: index => + `https://raw.githubusercontent.com/uber/xviz-data/master/kitti/2011_09_26_drive_0005_sync/${index + + 1}-frame.glb`, + worker: true, + maxConcurrency: 4 +}); diff --git a/examples/custom-xviz-layers/src/log-from-live.js b/examples/custom-xviz-layers/src/log-from-live.js new file mode 100644 index 00000000..c1a9beb9 --- /dev/null +++ b/examples/custom-xviz-layers/src/log-from-live.js @@ -0,0 +1,32 @@ +// Copyright (c) 2019 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 {XVIZLiveLoader} from 'streetscape.gl'; + +export default new XVIZLiveLoader({ + logGuid: 'mock', + bufferLength: 10, + serverConfig: { + defaultLogLength: 30, + serverUrl: 'ws://localhost:8081' + }, + worker: true, + maxConcurrency: 4 +}); diff --git a/examples/custom-xviz-layers/src/log-from-stream.js b/examples/custom-xviz-layers/src/log-from-stream.js new file mode 100644 index 00000000..6e4eba38 --- /dev/null +++ b/examples/custom-xviz-layers/src/log-from-stream.js @@ -0,0 +1,32 @@ +// Copyright (c) 2019 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 {XVIZStreamLoader} from 'streetscape.gl'; + +export default new XVIZStreamLoader({ + logGuid: 'mock', + // bufferLength: 15, + serverConfig: { + defaultLogLength: 30, + serverUrl: 'ws://localhost:8081' + }, + worker: true, + maxConcurrency: 4 +}); diff --git a/examples/custom-xviz-layers/webpack.config.js b/examples/custom-xviz-layers/webpack.config.js new file mode 100644 index 00000000..11877aa3 --- /dev/null +++ b/examples/custom-xviz-layers/webpack.config.js @@ -0,0 +1,73 @@ +// Copyright (c) 2019 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. + +/* eslint-disable no-process-env */ +const {resolve} = require('path'); +const webpack = require('webpack'); + +const BABEL_CONFIG = { + presets: ['@babel/preset-env', '@babel/preset-react'], + plugins: ['@babel/proposal-class-properties'] +}; + +const CONFIG = { + mode: 'development', + entry: { + app: resolve('./src/app.js') + }, + devtool: 'source-map', + output: { + path: resolve('./dist'), + filename: 'bundle.js' + }, + module: { + noParse: /(mapbox-gl)\.js$/, + rules: [ + { + // Compile ES2015 using bable + test: /\.js$/, + exclude: /node_modules/, + loader: 'babel-loader', + options: BABEL_CONFIG + } + ] + }, + plugins: [ + new webpack.HotModuleReplacementPlugin(), + new webpack.EnvironmentPlugin(['MapboxAccessToken']) + ] +}; + +module.exports = (env = {}) => { + let config = Object.assign({}, CONFIG); + + // This switch between streaming and static file loading + config.plugins = config.plugins.concat([ + new webpack.DefinePlugin({__IS_STREAMING__: JSON.stringify(Boolean(env.stream))}), + new webpack.DefinePlugin({__IS_LIVE__: JSON.stringify(Boolean(env.live))}) + ]); + + if (env.local) { + // This line enables bundling against src in this repo rather than installed module + config = require('../webpack.config.local')(config)(env); + } + + return config; +}; diff --git a/modules/core/src/components/log-viewer/core-3d-viewer.js b/modules/core/src/components/log-viewer/core-3d-viewer.js index 5658cb0f..a03a15a0 100644 --- a/modules/core/src/components/log-viewer/core-3d-viewer.js +++ b/modules/core/src/components/log-viewer/core-3d-viewer.js @@ -74,6 +74,7 @@ export default class Core3DViewer extends PureComponent { PropTypes.func ]), customLayers: PropTypes.array, + customXVIZLayers: PropTypes.array, renderObjectLabel: PropTypes.func, getTransformMatrix: PropTypes.func, viewOptions: PropTypes.object, @@ -100,6 +101,7 @@ export default class Core3DViewer extends PureComponent { viewMode: VIEW_MODE.PERSPECTIVE, xvizStyles: {}, customLayers: [], + customXVIZLayers: [], onMapLoad: noop, onDeckLoad: noop, onViewStateChange: noop, @@ -245,6 +247,7 @@ export default class Core3DViewer extends PureComponent { streamsMetadata, objectStates, customLayers, + customXVIZLayers, getTransformMatrix, styleParser } = opts; @@ -286,7 +289,6 @@ export default class Core3DViewer extends PureComponent { ...coordinateProps, pickable: true, - data: primitives, style: stylesheet, objectStates, @@ -297,7 +299,9 @@ export default class Core3DViewer extends PureComponent { zIndex: Z_INDEX[primitives[0].type] || 0, // Selection props (app defined, not used by deck.gl) - streamName + streamName, + streamMetadata: streamsMetadata[streamName], + customXVIZLayers }); } return null; @@ -397,7 +401,8 @@ export default class Core3DViewer extends PureComponent { viewMode, viewState, viewOffset, - showMap + showMap, + customXVIZLayers } = this.props; const {styleParser, views} = this.state; const layers = this.getLayers({ @@ -408,7 +413,8 @@ export default class Core3DViewer extends PureComponent { objectStates, customLayers, getTransformMatrix, - styleParser + styleParser, + customXVIZLayers }); const viewStates = this.getViewState({viewMode, frame, viewState, viewOffset}); diff --git a/modules/core/src/layers/xviz-layer.js b/modules/core/src/layers/xviz-layer.js index c307e8cb..234f795d 100644 --- a/modules/core/src/layers/xviz-layer.js +++ b/modules/core/src/layers/xviz-layer.js @@ -107,6 +107,129 @@ const STYLE_TO_LAYER_PROP = { text_baseline: 'getAlignmentBaseline' } }; +/* + ####### EXTENDING XVIZ LAYER FOR PRIMITIVES ######### +*/ +// not sure if necessary, just to have enums for existing primitives +const LAYER_TYPES = { + SCATTERPLOT: 'scatterplot', + PATH: 'path', + POINTCLOUD: 'pointcloud', + STADIUM: 'stadium', + POLYGON: 'polygon', + TEXT: 'text' +}; + +// Defines the way that each primitive layer type is handled in the application +const LAYER_HANDLERS = { + [LAYER_TYPES.SCATTERPLOT]: { + layerType: LAYER_TYPES.SCATTERPLOT, + layerClass: ScatterplotLayer, + // returns data to hold in state + // updateState calls `getState({data: preprocessData})` + preprocessData: props => { + const {data} = props; + if (data[0].vertices && Array.isArray(data[0].vertices[0])) { + const processedData = data.reduce((arr, multiPoints) => { + multiPoints.vertices.forEach(pt => { + arr.push({...multiPoints, vertices: pt}); + }); + return arr; + }, []); + return processedData; + } + return data; + }, + getLayerTypeProps: ({xvizLayerProps, layerProps}) => { + const {updateTriggers} = layerProps; + return { + // `vertices` is used xviz V1 and `center` is used by xviz V2 + getPosition: f => f.vertices || f.center, + updateTriggers: deepExtend(updateTriggers, { + getFillColor: {useSemanticColor: xvizLayerProps.useSemanticColor} + }) + }; + } + }, + [LAYER_TYPES.STADIUM]: { + layerType: LAYER_TYPES.STADIUM, + layerClass: PathLayer, + getLayerTypeProps: ({xvizLayerProps, layerProps}) => { + const {updateTriggers} = layerProps; + return { + getPath: f => [f.start, f.end], + rounded: true, + updateTriggers: deepExtend(updateTriggers, { + getColor: {useSemanticColor: xvizLayerProps.useSemanticColor} + }) + }; + } + }, + [LAYER_TYPES.POINTCLOUD]: { + layerType: LAYER_TYPES.POINTCLOUD, + layerClass: PointCloudLayer, + getLayerTypeProps: ({xvizLayerProps, state}) => { + const {data} = state; + return { + data: { + length: data[0].points.length / 3, + attributes: { + getPosition: data[0].points, + getColor: data[0].colors + } + }, + vehicleRelativeTransform: xvizLayerProps.vehicleRelativeTransform, + getPosition: p => p + }; + } + }, + [LAYER_TYPES.PATH]: { + layerType: LAYER_TYPES.PATH, + layerClass: PathLayer, + getLayerTypeProps: ({xvizLayerProps, layerProps}) => { + const {updateTriggers} = layerProps; + return { + getPath: f => f.vertices, + updateTriggers: deepExtend(updateTriggers, { + getColor: {useSemanticColor: xvizLayerProps.useSemanticColor} + }) + }; + } + }, + [LAYER_TYPES.POLYGON]: { + layerType: LAYER_TYPES.POLYGON, + layerClass: PolygonLayer, + getLayerTypeProps: ({xvizLayerProps, layerProps}) => { + const {updateTriggers} = layerProps; + const {lightSettings, useSemanticColor, opacity} = xvizLayerProps; + const {stroked} = layerProps; + return { + opacity: opacity || 1, + lightSettings, + wireframe: stroked, + getPolygon: f => f.vertices, + updateTriggers: deepExtend(updateTriggers, { + getLineColor: {useSemanticColor}, + getFillColor: {useSemanticColor} + }) + }; + } + }, + [LAYER_TYPES.TEXT]: { + layerType: LAYER_TYPES.TEXT, + layerClass: TextLayer, + getLayerTypeProps: ({xvizLayerProps, layerProps}) => { + const {updateTriggers} = layerProps; + const {useSemanticColor} = xvizLayerProps; + return { + getText: f => f.text, + updateTriggers: deepExtend(updateTriggers, { + getColor: {useSemanticColor} + }) + }; + } + } +}; const EMPTY_OBJECT = {}; @@ -267,15 +390,9 @@ export default class XVIZLayer extends CompositeLayer { let data = props.data; const dataType = this._getLayerType(data); type = XVIZ_TO_LAYER_TYPE[dataType]; - - if (type === 'scatterplot' && data[0].vertices && Array.isArray(data[0].vertices[0])) { - // is multi point - data = data.reduce((arr, multiPoints) => { - multiPoints.vertices.forEach(pt => { - arr.push({...multiPoints, vertices: pt}); - }); - return arr; - }, []); + const layerHandler = LAYER_HANDLERS[type]; + if (layerHandler && layerHandler.preprocessData) { + data = layerHandler.preprocessData(props); } this.setState({data}); @@ -289,120 +406,69 @@ export default class XVIZLayer extends CompositeLayer { } renderLayers() { - const {lightSettings} = this.props; const {type, data} = this.state; if (!type) { return null; } - const {linkTitle, streamName, objectType} = this.props; + const {linkTitle, streamName, streamMetadata, objectType} = this.props; const layerProps = this._getLayerProps(); - const updateTriggers = layerProps.updateTriggers; const forwardProps = { linkTitle, streamName, objectType }; - - switch (type) { - case 'scatterplot': - return new ScatterplotLayer( - forwardProps, - layerProps, - this.getSubLayerProps({ - id: 'scatterplot', - data, - // `vertices` is used xviz V1 and `center` is used by xviz V2 - getPosition: f => f.vertices || f.center, - updateTriggers: deepExtend(updateTriggers, { - getFillColor: {useSemanticColor: this.props.useSemanticColor} - }) - }) - ); - - case 'pointcloud': - return new PointCloudLayer( - forwardProps, - layerProps, - this.getSubLayerProps({ - id: 'pointcloud', - data: { - length: data[0].points.length / 3, - attributes: { - getPosition: data[0].points, - getColor: data[0].colors - } - }, - vehicleRelativeTransform: this.props.vehicleRelativeTransform, - getPosition: p => p - }) - ); - - case 'path': - return new PathLayer( - forwardProps, + // multiple layers? + const customXvizLayerMatch = this.props.customXVIZLayers.find(({streamMatch}) => + streamMatch(streamName, streamMetadata) + ); + + if (customXvizLayerMatch) { + let primitiveLayerProps = {}; + const parentLayerHandler = LAYER_HANDLERS[customXvizLayerMatch.extendPrimitive]; + if (parentLayerHandler) { + primitiveLayerProps = parentLayerHandler.getLayerTypeProps({ + xvizLayerProps: this.props, layerProps, - this.getSubLayerProps({ - id: 'path', - data, - getPath: f => f.vertices, - updateTriggers: deepExtend(updateTriggers, { - getColor: {useSemanticColor: this.props.useSemanticColor} - }) - }) - ); - - case 'stadium': - return new PathLayer( - forwardProps, - layerProps, - this.getSubLayerProps({ - id: 'stadium', - data, - getPath: f => [f.start, f.end], - rounded: true, - updateTriggers: deepExtend(updateTriggers, { - getColor: {useSemanticColor: this.props.useSemanticColor} - }) - }) - ); + state: this.state + }); + } - case 'polygon': - return new PolygonLayer( - forwardProps, - layerProps, - this.getSubLayerProps({ - id: 'polygon', - opacity: this.props.opacity || 1, - data, - lightSettings, - wireframe: layerProps.stroked, - getPolygon: f => f.vertices, - updateTriggers: deepExtend(updateTriggers, { - getLineColor: {useSemanticColor: this.props.useSemanticColor}, - getFillColor: {useSemanticColor: this.props.useSemanticColor} - }) + const LayerClass = customXvizLayerMatch.layerClass || parentLayerHandler?.layerClass; + return new LayerClass( + forwardProps, + layerProps, + this.getSubLayerProps({ + id: customXvizLayerMatch.id, + data, + ...primitiveLayerProps, + ...customXvizLayerMatch.getSubProps({ + xvizLayerProps: this.props, + primitiveLayerProps, + state: this.state }) - ); + }) + ); + } + if (!LAYER_HANDLERS[type]) { + return null; + } - case 'text': - return new TextLayer( - forwardProps, + const {layerClass: LayerClass, getLayerTypeProps} = LAYER_HANDLERS[type]; + return new LayerClass( + forwardProps, + layerProps, + this.getSubLayerProps({ + id: type, + data, + ...getLayerTypeProps({ + xvizLayerProps: this.props, layerProps, - this.getSubLayerProps({ - id: 'text', - data, - getText: f => f.text, - updateTriggers: deepExtend(updateTriggers, { - getColor: {useSemanticColor: this.props.useSemanticColor} - }) - }) - ); - - default: - return null; - } + state: this.state + }) + }) + ); } }