streetscape.gl學習筆記(二)

在第一篇中streetscape.gl學習筆記(一)芥颈,大致對LogViewer (React Component)有所了解猴贰,現(xiàn)就看看它的核心組成組件Core3DViewer。

Core3DViewer大體認識

Core3DViewer組件主要由DeckGL、StaticMap搀玖、ObjectLabelsOverlay三個子組件組成蛮穿。這里DeckGL庶骄、StaticMap又分別對應uber的另外兩個開源產(chǎn)品deck.glreact-map-gl
在這里再多聊兩句践磅,這兩個組件是通過兩個Canvas進行組合单刁,而且默認是監(jiān)聽deck.gl的交互事件。因此府适,我就遇到了這個問題羔飞,在streetscape.gl中我同時想和地圖上的元素進行交互(例如:在地下停車場的場景中,我想查看地圖上停車位的基本信息情況)時檐春,就受到限制逻淌。Can streetscape.gl listen to mapbox's own event

好開始上源碼榜田。

export default class Core3DViewer extends PureComponent {
  static propTypes = {
    // Props from loader
    frame: PropTypes.object,
    metadata: PropTypes.object,

    // Rendering options
    showMap: PropTypes.bool,
    showTooltip: PropTypes.bool,
    mapboxApiAccessToken: PropTypes.string,
    mapStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
    xvizStyles: PropTypes.object,
    car: PropTypes.object,
    viewMode: PropTypes.object,
    streamFilter: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.array,
      PropTypes.object,
      PropTypes.func
    ]),
    customLayers: PropTypes.array,
    renderObjectLabel: PropTypes.func,
    getTransformMatrix: PropTypes.func,

    // Callbacks
    onMapLoad: PropTypes.func,
    onDeckLoad: PropTypes.func,
    onHover: PropTypes.func,
    onClick: PropTypes.func,
    onContextMenu: PropTypes.func,
    onViewStateChange: PropTypes.func,

    // Debug info listener
    debug: PropTypes.func,

    // States
    viewState: PropTypes.object,
    viewOffset: PropTypes.object,
    objectStates: PropTypes.object
  };
...
}

首先是定義屬性鳞溉,

  • frame、metadata屬性是為了接收來自父組件透傳過來的XVIZ log數(shù)據(jù)
  • showMap磨总、showTooltip誓篱、mapboxApiAccessToken朋贬、mapStyle 、xvizStyles窜骄、car锦募、viewMode、streamFilter邻遏、customLayers糠亩、renderObjectLabel虐骑、getTransformMatrix這些屬性是為了地圖上渲染元素及顯示樣式所定義的屬性,
  • onMapLoad: PropTypes.func,
    onDeckLoad: PropTypes.func,
    onHover: PropTypes.func,
    onClick: PropTypes.func,
    onContextMenu: PropTypes.func,
    onViewStateChange: PropTypes.func,
    地圖交互事件所定義的屬性
  • debug調(diào)試屬性
  • viewState赎线、viewOffset廷没、objectStates屬性分別記錄當前組件所對應的視圖狀態(tài)和目標物狀態(tài),以便和其他組件進行交互
  static defaultProps = {
    car: DEFAULT_CAR,
    viewMode: VIEW_MODE.PERSPECTIVE,
    xvizStyles: {},
    customLayers: [],
    onMapLoad: noop,
    onDeckLoad: noop,
    onViewStateChange: noop,
    onHover: noop,
    onClick: noop,
    onContextMenu: noop,
    showMap: true,
    showTooltip: false
  };

這部分是定義屬性的初始值垂寥,如果你有車輛的模型文件(json格式)可以在這里替換颠黎。


替換后的效果.png
  constructor(props) {
    super(props);
    this.state = {
      styleParser: this._getStyleParser(props)
    };
  }

構(gòu)造函數(shù)中,在state中添加一個styleParser滞项,其類型是XVIZStyleParser狭归,用于各stream流的樣式獲取。
接下來從render函數(shù)入手文判,看看deck.gl是怎么繪制和交互的

  render() {
    const {
      mapboxApiAccessToken,
      frame,
      metadata,
      objectStates,
      renderObjectLabel,
      getTransformMatrix,
      style,
      mapStyle,
      viewMode,
      showMap
    } = this.props;
    const {styleParser} = this.state;
    return (
      <DeckGL
        width="100%"
        height="100%"
        ref={this.deckRef}
        effects={[LIGHTS]}
        views={getViews(viewMode)}
        viewState={this._getViewState()}
        layers={this._getLayers()}
        layerFilter={this._layerFilter}
        getCursor={this._getCursor}
        onLoad={this._onDeckLoad}
        onHover={this._onLayerHover}
        onClick={this._onLayerClick}
        onViewStateChange={this._onViewStateChange}
        _onMetrics={this._onMetrics}
      >
        {showMap && (
          <StaticMap
            reuseMap={true}
            attributionControl={false}
            mapboxApiAccessToken={mapboxApiAccessToken}
            mapStyle={mapStyle}
            visible={frame && frame.origin && !viewMode.firstPerson}
            onLoad={this._onMapLoad}
          />
        )}

        <ObjectLabelsOverlay
          objectSelection={objectStates.selected}
          frame={frame}
          metadata={metadata}
          renderObjectLabel={renderObjectLabel}
          xvizStyleParser={styleParser}
          style={style}
          getTransformMatrix={getTransformMatrix}
        />

        {this.props.children}
      </DeckGL>
    );
  }

我們關(guān)注<DeckGL/>中的屬性賦值过椎,這里最重要的幾個是views、viewState戏仓、layers疚宇,讓我們來看看這幾個屬性是干嘛的,以及程序是怎么給它賦值的柜去。

views

在搜索到views屬性時灰嫉,Deck.gl推薦先了解一下Views and Projections

view是什么,能否實現(xiàn)多個視圖嗓奢,并同步視圖狀態(tài),寫到這實在寫不下去浑厚,自己研究還不夠透徹股耽,繼續(xù)看streetscape.gl和deck.gl的API和示例。
以下是multi-view的效果

image.png

自己改造后的地下停車場效果
image.png

有研究的小伙伴可以留言交流钳幅,另外作者最近又接觸了carla物蝙,有感興趣的童鞋也可以交流。


接著往下寫敢艰,這兩天研究了view诬乞、view state、viewport钠导,大致理解了一些震嫉。寫下來防止自己忘了。

View

view可以理解為一個視圖窗口牡属,它的組成成員包括票堵,id、在canvas中的x逮栅、y坐標位置悴势,視圖窗口的長寬(width窗宇、height)、特定攝像頭參數(shù)(例如:攝像頭的視野特纤、近平面還是遠平面军俊、透視還是正交投影等參數(shù))、視圖能夠進行的哪些操作controller捧存。deck.gl允許定義多個view粪躬,可以將屏幕切分成多個視圖。這些視圖在操作時能夠同步互動也可以獨立矗蕊。
我們來看看streetscape.gl中定義了哪些view短蜕。
在/modules/core/utils/constants.js中定義了VIEW_MODE

export const VIEW_MODE = {
  TOP_DOWN: {
    name: 'top-down',
    initialViewState: {},
    orthographic: true,
    tracked: {
      position: true
    }
  },
  PERSPECTIVE: {
    name: 'perspective',
    initialViewState: {
      maxPitch: 85,
      pitch: 60
    },
    tracked: {
      position: true,
      heading: true
    }
  },
  DRIVER: {
    name: 'driver',
    initialProps: {
      maxPitch: 0
    },
    firstPerson: {
      position: [0, 0, 1.5]
    },
    mapInteraction: {
      dragPan: false,
      scrollZoom: false
    }
  }
};

從中可以看出,定義了三種viewmode傻咖,并定義相應的controller交互有哪些朋魔,初始的viewstate,以及跟蹤哪些tracked卿操。
在應用中通過傳遞viewMode屬性給core-3d-viewer警检,進而初始化view。在core-3d-viewer.js中害淤,獲取View如下:

export function getViews(viewMode) {
  const {name, orthographic, firstPerson, mapInteraction} = viewMode;

  const controllerProps = {...mapInteraction, keyboard: false};

  if (firstPerson) {
    return new FirstPersonView({
      id: name,
      fovy: 75,
      near: 1,
      far: 10000,
      focalDistance: 6,
      controller: controllerProps
    });
  }

  return new MapView({
    id: name,
    orthographic,
    controller: controllerProps
  });
}

從中可以看出扇雕,getViews生成兩類View,分別是FirstPersonView和MapView窥摄。針對不同的View镶奉,deck.gl Views and Projections
有相關(guān)介紹,可以了解崭放。

image.png

View State

View State定義了特定View所對應的當前視圖狀態(tài)(例如:攝像頭當前位置哨苛、方向、當前放縮級別等等)币砂,當視圖是可以和用戶進行交互時建峭,用戶在視圖內(nèi)每平移、旋轉(zhuǎn)决摧、放縮亿蒸,都會更新view state。
在core-3d-viewer.js中掌桩,獲取View State如下:

 _getViewState() {
    const {viewMode, frame, viewState, viewOffset} = this.props;
    
    const trackedPosition = frame
      ? {
          longitude: frame.trackPosition[0],
          latitude: frame.trackPosition[1],
          // This is due to a bug in deck.gl where coordinateOrigin.z is not applied
          // Remove when deck is fixed
          altitude: frame.trackPosition[2] - frame.origin[2],
          bearing: 90 - frame.heading
        }
      : null;
    return getViewStates({viewState, viewMode, trackedPosition, offset: viewOffset});
  }

從屬性中獲取viewMode边锁、當前數(shù)據(jù)幀frame、當前的viewState以及viewOffset拘鞋,
從數(shù)據(jù)幀frame中獲取當前車輛的位置砚蓬、高程、航向角盆色,
利用getViewStates函數(shù)進行viewState的計算灰蛙。
再看看getViewStates:

// Creates viewports that contains information about car position and heading
export function getViewStates({viewState, trackedPosition, viewMode, offset}) {
  const {name, firstPerson, tracked = {}} = viewMode;

  const viewStates = {};

  if (firstPerson) {
    if (trackedPosition) {
      const bearing = trackedPosition.bearing;
      viewState = {
        ...viewState,
        ...firstPerson,
        longitude: trackedPosition.longitude,
        latitude: trackedPosition.latitude,
        bearing: bearing + offset.bearing
      };
    }
    viewStates[name] = viewState;
  } else {
    viewState = {...viewState, transitionDuration: 0};
    offset = {...offset};

    // Track car position & heading
    if (tracked.position && trackedPosition) {
      viewState.longitude = trackedPosition.longitude;
      viewState.latitude = trackedPosition.latitude;
    } else {
      offset.x = 0;
      offset.y = 0;
    }
    if (tracked.heading && trackedPosition) {
      viewState.bearing = trackedPosition.bearing;
    } else {
      offset.bearing = 0;
    }
    // Put the tracked object on the ground
    // TODO - support flying vehicle
    if (trackedPosition) {
      viewState.position = [0, 0, trackedPosition.altitude];
    }
    viewStates[name] = offsetViewState(viewState, offset);
  }
  return viewStates;
}

這段代碼第一行注釋告訴我們祟剔,該函數(shù)是計算除包含車輛位置和航向角的viewports。
這段也是我最難理解的摩梧,我試著解釋看看物延。
程序進來是一個if判斷,我們重點看else這段仅父。
當viewmode是top-down或者perspective時叛薯,進入到這段。

viewState = {...viewState, transitionDuration: 0};

首先繼承自傳參過來的viewState笙纤,并添加了transitionDuration屬性耗溜,設置其為0。也就意味著viewState之間的過渡是立即的省容。在View State Transitions
中有相應的介紹抖拴。
接著通過判斷viewmode中跟蹤的tracked以及當前傳入的trackedPosition設置viewState

    // Track car position & heading
    if (tracked.position && trackedPosition) {
      viewState.longitude = trackedPosition.longitude;
      viewState.latitude = trackedPosition.latitude;
    } else {
      offset.x = 0;
      offset.y = 0;
    }
    if (tracked.heading && trackedPosition) {
      viewState.bearing = trackedPosition.bearing;
    } else {
      offset.bearing = 0;
    }
    // Put the tracked object on the ground
    // TODO - support flying vehicle
    if (trackedPosition) {
      viewState.position = [0, 0, trackedPosition.altitude];
    }

從判斷語句可以看出,當viewmode為perspective模式時腥椒,viewState會根據(jù)車輛的位置和航向角實時調(diào)整阿宅,當viewmode為top-down模式時,viewState只會根據(jù)車輛的位置實時調(diào)整笼蛛。
最后洒放,也是最復雜的來了。通過設置好的viewState和offset滨砍,獲得對應模式下的viewStates往湿。

 viewStates[name] = offsetViewState(viewState, offset);

來看看offsetViewState這個函數(shù)

// Adjust lng/lat to position the car 1/4 from screen bottom
function offsetViewState(viewState, offset) {
  const shiftedViewState = {
    ...viewState,
    bearing: viewState.bearing + offset.bearing
  };

  const helperViewport = new WebMercatorViewport(shiftedViewState);

  const pos = [viewState.width / 2 + offset.x, viewState.height / 2 + offset.y];
  
  const lngLat = [viewState.longitude, viewState.latitude];
  
  const [longitude, latitude] = helperViewport.getLocationAtPoint({
    lngLat,
    pos
  });
  return {
    ...shiftedViewState,
    longitude,
    latitude
  };
}

還是看看注釋,是將經(jīng)緯度坐標調(diào)整到車離屏幕底部1/4的位置惋戏。
這段代碼讓我弄不明白的是煌茴,哪里看得出是讓小車離距離屏幕底部1/4處。

const pos = [viewState.width / 2 + offset.x, viewState.height / 2 + offset.y];

這邊也只是取1/2日川,我打印出viewState的width和height,全是1矩乐,因為在view中沒有設置width和height龄句,所以采用的默認值1。如果有小伙伴理解這段請指點迷津

  const [longitude, latitude] = helperViewport.getLocationAtPoint({
    lngLat,
    pos
  });

該函數(shù)我理解為將當前車輛的經(jīng)緯度位置匹配到view所指定的像素位置散罕,而返回的視圖中心點經(jīng)緯度坐標分歇。
getLocationAtPoint可以從deck.gl源碼中找到

  /**
   * Get the map center that place a given [lng, lat] coordinate at screen
   * point [x, y]
   *
   * @param {Array} lngLat - [lng,lat] coordinates
   *   Specifies a point on the sphere.
   * @param {Array} pos - [x,y] coordinates
   *   Specifies a point on the screen.
   * @return {Array} [lng,lat] new map center.
   */
  getMapCenterByLngLatPosition({lngLat, pos}) {
    const fromLocation = pixelsToWorld(pos, this.pixelUnprojectionMatrix);
    const toLocation = this.projectFlat(lngLat);

    const translate = vec2.add([], toLocation, vec2.negate([], fromLocation));
    const newCenter = vec2.add([], this.center, translate);

    return this.unprojectFlat(newCenter);
  }

  // Legacy method name
  getLocationAtPoint({lngLat, pos}) {
    return this.getMapCenterByLngLatPosition({lngLat, pos});
  }

getLocationAtPoint是一個零時的方法名,實際調(diào)用的是getMapCenterByLngLatPosition這個方法欧漱,從該方法的命名及首行注釋可以看出职抡,該方法是將指定的經(jīng)緯度放到指定的像素坐標上所獲得的地圖中心點坐標。

ViewPort

上段代碼中误甚,我們看到了一個viewport

const helperViewport = new WebMercatorViewport(shiftedViewState);

從deck.gl文檔中了解到viewport
viewport本質(zhì)上是一個地理空間攝像頭缚甩,且集成了很多功能谱净,能夠?qū)?D坐標正反投影到屏幕坐標上。
viewport class專注于數(shù)學運算例如坐標轉(zhuǎn)換擅威,計算視圖矩陣或投影矩陣以及webgl頂點著色器所需要的uniforms

image.png

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末壕探,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子郊丛,更是在濱河造成了極大的恐慌李请,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厉熟,死亡現(xiàn)場離奇詭異导盅,居然都是意外死亡,警方通過查閱死者的電腦和手機揍瑟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門白翻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人月培,你說我怎么就攤上這事嘁字。” “怎么了杉畜?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵纪蜒,是天一觀的道長。 經(jīng)常有香客問我此叠,道長纯续,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任灭袁,我火速辦了婚禮猬错,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘茸歧。我一直安慰自己倦炒,他們只是感情好,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布软瞎。 她就那樣靜靜地躺著逢唤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涤浇。 梳的紋絲不亂的頭發(fā)上鳖藕,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音只锭,去河邊找鬼著恩。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的喉誊。 我是一名探鬼主播邀摆,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼裹驰!你這毒婦竟也來了隧熙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤幻林,失蹤者是張志新(化名)和其女友劉穎贞盯,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沪饺,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡躏敢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了整葡。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片件余。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖遭居,靈堂內(nèi)的尸體忽然破棺而出啼器,到底是詐尸還是另有隱情,我是刑警寧澤俱萍,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布端壳,位于F島的核電站,受9級特大地震影響枪蘑,放射性物質(zhì)發(fā)生泄漏损谦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一岳颇、第九天 我趴在偏房一處隱蔽的房頂上張望照捡。 院中可真熱鬧,春花似錦话侧、人聲如沸栗精。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽术羔。三九已至,卻和暖如春乙漓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背释移。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工叭披, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓涩蜘,卻偏偏與公主長得像嚼贡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子同诫,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容