streetscape.gl学习笔记(一)

作者最近在做智能驾驶的项目,目前前端展示通过ol加载二维瓦片,地图上渲染的元素也不够酷炫。在项目启动之初正好是uber开源streetscape.gl之时,甚是欣喜,能有机会一睹大厂之作,算得上是享受。一直到最近几周才有功夫看看streetscape.gl的源码,想把获得的信息梳理一下,形成笔记,后面自己的控制台升级可以参照。
二话不说,先上个demo栗子镇楼。


streetscape.gl学习笔记(一)_第1张图片
image.png

可以说页面风格很简洁,作为智能驾驶辅助测试展示平台足矣。
设计内容主要分这几块:

  • 地图展示
  • 时间进度条
  • 摄像头拍摄
  • 传感器图表
  • 数据流树装列表
    先从我最关心的地图展示源码解读开始
    streetscape.gl将地图上展示交互都打包成一个控件,它就是LogViewer (React Component)。
    PS:通过介绍我们可以知道,是将XVIZ log数据进行渲染的控件。而XVIZ log数据是什么呢,就是streetscape.gl要求的后端数据交互格式数据,详见xviz。

LogViewer是啥样

接下来就看看LogViewer的源码,它是怎么样实现将XVIZ log数据在控件中渲染的,同时海提供hover和click的交互,并产生相应的tooltip和popup。
将源码下载到本地,在./modules/core/src/components/log-viewer.js中,我们可以看到这个组件的定义。

class LogViewer extends PureComponent {
  static propTypes = {
    ...Core3DViewer.propTypes,

    // Props from loader
    frame: PropTypes.object,
    metadata: PropTypes.object,

    // Rendering options
    renderTooltip: PropTypes.func,
    style: PropTypes.object,

    // Callbacks
    onSelectObject: PropTypes.func,
    onContextMenu: PropTypes.func,
    onViewStateChange: PropTypes.func,
    onObjectStateChange: PropTypes.func,

    // Optional: to use with external state management (e.g. Redux)
    viewState: PropTypes.object,
    viewOffset: PropTypes.object,
    objectStates: PropTypes.object
  };
...
}

首先定义的是组件类自身的属性,在开发模式下,当提供一个不合法的值作为prop时,控制台会出现警告。这是一个很好的开发习惯,可以采用。
PS:React在15.5版本之后, 代替使用 PropTypes 直接从 React 对象这种导入方式, 安装一个新的包 prop-types 并且使用如下的方式进行导入,所以在package.json文件dependencies中需要添加一条依赖"prop-types": "^15.6.2"
LogViewer的属性一部分来自Core3DViewer,LogViewer组件由Core3DViewer和HoverTooltip这两个重要组件组成。

  • frame、metadata属性是为了接收来自与后端XVIZ log数据
  • renderTooltip、style属性是为了在LogViewer中渲染tooltip,产生这样的效果


    streetscape.gl学习笔记(一)_第2张图片
    image.png
  • onSelectObject、onContextMenu、onViewStateChange、onObjectStateChange属性是为了和地图上交互而进行回调响应
  • viewState、viewOffset、objectStates属性分别记录当前组件所对应的视图状态和目标物状态,以便和其他组件进行交互
  static defaultProps = {
    ...Core3DViewer.defaultProps,

    style: {},
    onViewStateChange: noop,
    onObjectStateChange: noop,
    onSelectObject: noop,
    onContextMenu: noop,
    getTransformMatrix: (streamName, context) => null
  };

  constructor(props) {
    super(props);

    this.state = {
      viewState: {
        width: 1,
        height: 1,
        longitude: 0,
        latitude: 0,
        ...DEFAULT_VIEW_STATE,
        ...props.viewMode.initialViewState
      },
      viewOffset: {
        x: 0,
        y: 0,
        bearing: 0
      },
      objectStates: {},
      hoverInfo: null
    };
  }

这段是定义属性的默认值以及构造函数。
中间一段定义了一段事件,让我们看看最后的render

  render() {
    const viewState = this.props.viewState || this.state.viewState;
    const viewOffset = this.props.viewOffset || this.state.viewOffset;
    const objectStates = this.props.objectStates || this.state.objectStates;

    return (
      
{this._renderTooltip()}
); }

render最后返回的是一个div所包裹的Core3DViewer,这里我们可以看看作者是如何实现在地图上渲染Tooltip的。

  _renderTooltip() {
    const {showTooltip, style, renderTooltip} = this.props;
    const {hoverInfo} = this.state;

    return (
      showTooltip &&
      hoverInfo && (
        
      )
    );
  }

_renderTooltip最后返回的HoverTooltip组件的实例。

const getLogState = log => ({
  frame: log.getCurrentFrame(),
  metadata: log.getMetadata()
});

export default connectToLog({getLogState, Component: LogViewer});

这里作者通过connectToLog将log的state和组件进行绑定,我们可以看一下connectToLog这个function是如何定义的

import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';

import XVIZLoaderInterface from '../loaders/xviz-loader-interface';

export default function connectToLog({getLogState, Component}) {
  class WrappedComponent extends PureComponent {
    static propTypes = {
      log: PropTypes.instanceOf(XVIZLoaderInterface)
    };

    state = {
      logVersion: -1
    };

    componentDidMount() {
      const {log} = this.props;
      if (log) {
        log.subscribe(this._update);
      }
    }

    componentWillReceiveProps(nextProps) {
      const {log} = this.props;
      const nextLog = nextProps.log;
      if (log !== nextLog) {
        if (log) {
          log.unsubscribe(this._update);
        }
        if (nextLog) {
          nextLog.subscribe(this._update);
        }
      }
    }

    componentWillUnmount() {
      const {log} = this.props;
      if (log) {
        log.unsubscribe(this._update);
      }
    }

    _update = logVersion => {
      this.setState({logVersion});
    };

    render() {
      const {log, ...otherProps} = this.props;

      const logState = log && getLogState(log, otherProps);

      return ;
    }
  }

  return WrappedComponent;
}

该函数的作用是将LogState绑定到组件上来,其中采用了React的HOC高阶组件通过属性代理进行属性的绑定。

顺路看看HoverTooltip

在剖析Core3DViewer之前,我们先看看HoverTooltip,在./modules/core/src/components/hove-tooltip.js中,我们可以看到这个组件的定义。

const TooltipContainer = styled.div(props => ({
  ...props.theme.__reset__,
  position: 'absolute',
  pointerEvents: 'none',
  margin: props.theme.spacingNormal,
  padding: props.theme.spacingNormal,
  maxWidth: 320,
  overflow: 'hidden',
  background: props.theme.background,
  color: props.theme.textColorPrimary,
  zIndex: 100001,
  ...evaluateStyle(props.userStyle, props)
}));

首先通过@emotion/styled来创建一个div,作为tooltip的容器。

  render() {
    const {theme, info, style, renderContent = this._renderContent} = this.props;

    return (
      
        {renderContent(info)}
      
    );
  }

然后运用这个容器,并传递属性。

export default withTheme(HoverTooltip);

最后将器包装成ThemedComponent组件输出。
在 uber-web/monochrome我们可以看到withTheme的定义,如下:

export function withTheme(Component) {
  class ThemedComponent extends React.Component {
    render() {
      return (
        
          {_theme => }
        
      );
    }
  }

  ThemedComponent.propTypes = Component.propTypes;
  ThemedComponent.defaultProps = Component.defaultProps;

  return ThemedComponent;
}

该方法功能是让组件带上Theme主题,这样只要修改Theme就能直接修改前端组件的风格。


先写到这,以上只是个人见解,如若有不对地方,还请帮忙指出。
接下来会介绍重量级组件Core3DViewer,希望有研究的小伙伴一起交流。

你可能感兴趣的:(streetscape.gl学习笔记(一))