在第一篇中streetscape.gl學習筆記(一)芥颈,大致對LogViewer (React Component)有所了解猴贰,現(xiàn)就看看它的核心組成組件Core3DViewer。
Core3DViewer大體認識
Core3DViewer組件主要由DeckGL、StaticMap搀玖、ObjectLabelsOverlay三個子組件組成蛮穿。這里DeckGL庶骄、StaticMap又分別對應uber的另外兩個開源產(chǎn)品deck.gl和react-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格式)可以在這里替換颠黎。
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的效果
自己改造后的地下停車場效果
有研究的小伙伴可以留言交流钳幅,另外作者最近又接觸了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)介紹,可以了解崭放。
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