streetscape.gl学习笔记(一)

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


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,产生这样的效果


    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 (
      <div onContextMenu={preventDefault}>
        <Core3DViewer
          {...this.props}
          onViewStateChange={this._onViewStateChange}
          onClick={this._onClickObject}
          onHover={this._onHoverObject}
          onContextMenu={this._onContextMenu}
          viewState={viewState}
          viewOffset={viewOffset}
          objectStates={objectStates}
        >
          {this._renderTooltip()}
        </Core3DViewer>
      </div>
    );
  }

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

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

    return (
      showTooltip &&
      hoverInfo && (
        <HoverTooltip info={hoverInfo} style={style.tooltip} renderContent={renderTooltip} />
      )
    );
  }

_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 <Component {...otherProps} {...logState} log={log} />;
    }
  }

  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 (
      <TooltipContainer theme={theme} style={{left: info.x, top: info.y}} userStyle={style}>
        {renderContent(info)}
      </TooltipContainer>
    );
  }

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

export default withTheme(HoverTooltip);

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

export function withTheme(Component) {
  class ThemedComponent extends React.Component {
    render() {
      return (
        <ThemeContext.Consumer>
          {_theme => <Component {...this.props} theme={_theme} />}
        </ThemeContext.Consumer>
      );
    }
  }

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

  return ThemedComponent;
}

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


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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345