整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          Zoom的Web客戶端如何避免使用WebRTC?

          oom的Web客戶端可以在用戶不下載它們App的情況下加入會議。Chris Koehncke很高興能看到它是如何工作的。這確實有效,不必花時間下載App.并且視頻質量可以接受,對此我們愉快地討論了半小時。

          打開 chrome://webrtc-internals只看到了getUserMedia被用來獲取攝像頭和麥克風,但是沒有看到RTCPeerConnection的使用。這激起了我的興趣,它們是如何不用WebRTC進行通話的?

          為什么Zoom不使用WebRTC?

          Zoom與WebRTC的關系很難說清楚,就像網站上的陳述一樣:

          Jitsi的伙伴對此進行了比較。Tsahi Levent-Levi對此也進行了有用的評論。

          讓我們快速看看在某些有趣的情況下的‘特性’—–在Chrome瀏覽器中運行。

          Zoom Web客戶端

          Chromes網絡開發工具很快展現出兩點:

          WebSockets被用來傳輸數據

          存在wasm文件

          Wasm文件名快速產生了GitHub目錄,在目錄里,與其它JavaScript元素并存。這些文件和產品中所用的文件幾乎相同。

          WebSocket上的媒體

          總體設計很有趣。它使用WebSockets來傳輸媒體,這并不是最好的選擇。就像在WebRTC中使用TURN/TCP一樣,這樣做會對視頻質量產生影響,并且在很多情況下不能很好地工作。通過TCP進行實時交流的問題是丟包會導致重新發送,因此增加了延遲。Tsahi之前對此進行了描述,展示了這樣做對比特率和其它方面的影響。

          這樣做最大的優點是即使TURN/TCPhe TURN/TLS被防火墻擋住,它也有可能正常通過。這當然就避免了WebRTC中由于不能獲得授權代理而無法建立TURN連接的問題。這在Chrome的WebRTC實現中是個長期存在的問題,直到去年才得以解決。

          WebSockets上接收的數據進入了一個基于WASM的解碼器。音頻被傳輸給瀏覽器中的AudioWorklet來對它提供支持。

          視頻被加入canvas。過程非常流暢,并且視頻質量也很高。

          在另一個方向,WebAudio通過getUserMedia的調用捕捉媒體,并且被發送給WASM編碼器,接著通過WebSocket被送達。視頻捕捉以640*360的分辨率進行,不幸的是,在被發送給WASM編碼器之前就被從canvas上抓取下來。

          WASM文件看起來與Zoom的原始客戶端包含同樣的編解碼器,意味著網關不需要進行轉碼。不止是websocket到RTP的傳輸,更像是TURN服務器。被編碼的視頻某種程度上被像素化了,Kranky甚至抱怨視頻中的階梯失真。盡管編碼器對CPU的使用率很高,但是這沒有關系,用戶只會責怪Chrome,下次使用原始客戶端。

          H.264

          使用WASM形式傳輸媒體很有趣,它支持了Chrome/WebRTC不支持的編解碼器。這不是一個創新,FFmpeg之前已經編譯過emscripten很多次,看起來效果不錯。將編碼之后的信息通過WebSocket傳輸就可以使用Chrome的debug工具查看其內容,并且展示具有RTP標題的H264負載。

          令我吃驚的是,NALU沒有顯示出H264-SVC的使用。

          與WebRTC對比

          讓我們總結一下這種情況下Chrome中使用的和WebRTC標準的不同

          特征 | Zoom Web 客戶端 | WebRTC/RTCWeb

          加密 | 普通RTP | DTLS-SRTP

          數據頻道 | n/a ? | 基于SCTP

          ICE | n/a | RFC 5245(RFC8445)

          音頻編解碼| 未知 | Opus

          多媒體流 | 未觀察到 | Chrome最終實現了它

          同時聯播 | 在Web客戶端中未觀察到| 擴展規范

          WebRTC,下一代

          即使離WebRTC1.0完成還有很遠的路,很多人已經在討論下一代了。Zoom Web客戶端的總體設計深深地提醒了我:在今年Stockholm的面對面會議中Google的Peter Thatcher對于WebRTC NV的想法。請查看PPT(從26頁開始)。

          如果我們在2018年重建WebRTC,我們可能會采取同樣的方法來將各種元素分離。大概需要這些步驟:

          1.編譯webrtc.org編解碼器wasm

          2.將解碼器與canvas連接

          3.將編碼器與getUserMedia連接,作為輸入

          4.通過數據通道發送編碼之后的媒體流量,實現傳輸

          5.將RTCDataChannel反饋指標與視頻音頻編碼器連接

          工作組的會議材料中將此方法可視化。

          這個提議比Zoom的方法具有明顯的技術優勢。例如,使用RTCDataChannels來傳輸數據,相較于Websockets可以更好的控制堵塞,尤其是當出現丟包現象時。

          這個設計的巨大優勢是將編解碼器從瀏覽器中分離變為可能,允許自定義版本。主要問題是將數據處理從主線程中高效分離,還有硬件加速。這一直是Chrome的巨大挑戰,我記得很多人抱怨沙盒將問題變得復雜。Zoom看起來可以正常工作,但是我們只嘗試了一對一交談,但WebRTC APP的需求顯然不止于此。更希望重用MediaStreamTrack來進行數據傳輸,這樣就可以更好的處理Canvas和WebAudio元素。

          于canvas組件是原生組件,原生組件的層級是最高的,所以頁面中的其他組件無論設置 z-index 為多少,都無法蓋在原生組件上。

          1.當滾動遮擋時解決辦法。

          <scroll-view> 可以完美解決滾動時canvas層級過高出現的遮擋。

          這里有個問題要注意,使用<scroll-view>必須把 @touchstart寫在標簽內,不然,在真機上不生效(大坑)。

          2.當彈框遮擋時解決辦法。

          當點擊彈框時,使用v-show的方式,切換顯示。從而不會出現層級的問題。
          3.不能使用v-if的原因。

          v-if是讓盒子消失 并不是改變盒子的display 屬性 所以 當時你顯示 v-if的時候 他會重新插入盒子 但是echarts圖表并不會刷新 因為在vue中mounted hook 已經調用一遍echarts表格的方法了。

          解決的辦法就是使用v-show,就不會存在這個問題。

          工作中有不少涉及到地圖的項目,我參加了幾次技術評審,前端伙伴們在 WebGIS 方面的知識儲備稍有不足,這次分享的主要目的是科普一些在前端領域比較常用的 WebGIS 知識。另外,我之前的工作中積攢了一些從零開始搭建 WebGL 地圖引擎的微薄經驗,雖然最終遺憾沒有上線,但在其中學到的一些WebGL知識還是值得分享一下。WebGL 可以說是前端可視化技術領域難度最大的一項圖形編程技術,所以今天就結合 WebGIS 這個話題順帶分享一些 WebGL 的相關知識,不會太深入,很細節的技術點在后續文章里再講解。

          一 WebGIS 常用概念

          在前端領域需要關注的 WebGIS 知識最主要的是搞清楚電子地圖中的各種坐標系,其次需要對路網有一些基本的認知,包含路網的特征以及尋路算法的復雜度量級,其中對算法復雜度的了解不用精確到數字,只需要有一個大致的概念即可。

          路網尋址是一套非常復雜的算法,除了路網本身的有向圖特征以外,還需要將路況、天氣甚至民生、政治等因素考慮在內。這是一項單獨的研究課題,前端研發不需要關注太細節的東西。

          1.1 坐標系

          我們日常接觸的地理坐標最多的是經緯度坐標,地球是一個橢球體,經緯度是球面坐標系。但是我們平時使用的電子地圖都是平面的,如何把球面坐標系下的經緯度坐標映射為電子地圖的平面坐標系(數學上稱謂是笛卡爾直角坐標系)呢?這個映射過程就是投影變換,目前在 WebGIS 領域國際上統一使用墨卡托投影實現。

          下面就分別介紹一下以上兩種坐標系以及映射原理。

          經緯度坐標

          表面上看是兩種,經緯度和墨卡托,但準確的說應該是三種(甚至N種)。因為我們日常接觸到的經緯度坐標都是經過加密算法處理之后的偏移坐標,與地理上真實的經緯度坐標有一定的偏移量。

          真實的地理經緯度坐標系是國際標準,稱為WGS84標準,此標準下的坐標系稱為地球坐標系或地理坐標系。絕大多數電子地圖服務商都不會(或者說不準)直接使用 WGS84 坐標,因為地理信息是涉及國家安全的重要信息,所以一般都需要進行加密。

          我們國家目前使用的加密標準是國家測繪局2002年制定GCJ02 標準,經過加密后的坐標系被稱為火星坐標系。在我國的所有電子地圖都必須至少經過 GCJ02 加密一次才可以上線使用。請注意,至少的意思是經過 GCJ02 加密之后,地圖廠商還可以進行二次甚至三次加密,比如百度地圖使用的 BD09 標準就是在 GCJ02 加密之后進行二次加密的結果。

          下圖顯示的是同一個經緯度坐標在不同地圖上的位置:

          墨卡托坐標

          墨卡托坐標是球面坐標經過墨卡托投影之后得到的笛卡爾直角二維坐標,墨卡托投影全名叫做正軸等角圓柱墨卡托投影。其原理是假設地球被圍在一個中空的圓柱里,其基準緯線(赤道)與圓柱相切接觸,然后再假想地球中心有一盞燈,把球面上的圖形投影到圓柱體上,再把圓柱體展開,這就是一幅選定基準緯線上的“墨卡托投影”繪制出的地圖,見下圖:

          為了便于建模和計算,墨卡托投影在真實的地球模型上做了以下幾個假設:

          • 假設一:地球自轉是“垂直的”。之所以加引號,是因為在宇宙角度上討論垂直和水平沒有任何意義。大家都知道地球的自轉軸(也就是南極點和北極點的連線)是有一個傾斜角的,所以我們見到的地球儀都是傾斜的;
          • 假設二:地球是一個正球體。嚴格來說,這條假設并不是墨卡托投影賦予的,而是來自Web墨卡托投影。原生墨卡托投影得到的平面地圖是一個長方形,Web 墨卡托投影在原生墨卡托投影基礎上的再次簡化,將地球假設為一個正球體,投影后得到的平面地圖是一個正方形。正方形方便瓦片切圖(關于瓦片切圖的知識下文會講),這樣能夠提前將地圖數據切片儲存,提高用戶的使用體驗。缺點是Y軸存在0.33%的誤差;

          墨卡托投影有兩個致命的缺點:

          • 第一,形變非常嚴重。越接近兩極的位置越嚴重,而且投影后視覺上的平面“面積”遠遠大于真實的地理球面面積。所以在某個特殊時期,墨卡托投影被個別北美洲國家鐘愛,因為他們的國家在投影之后“看上去”非常大。
          • 第二,南北極緯度丟失。墨卡托投影能覆蓋的緯度區間大概是 [-85.05, 85.05](單位度deg),區間之外的兩極地區的經緯度坐標經過投影計算得到的值趨近無限大和無限小,無法在平面圖上表達,所以目前市面上的互聯網地圖兩極地區都是“黑洞”。請看下面這張圖:

          現實問題:計算兩點之間的距離

          計算兩個POI點之間的“直線”距離是我們日常項目中出現概率很高的一種需求,之所以“直線”兩字加引號是因為在現實中地球上的兩個點不存在絕對的直線距離,在地理上都是球面距離,也就是數學上的弧長。球面上兩點之間的弧長計算是比較復雜的,而且地球是橢球體,進一步加大了復雜度。

          這個問題有了墨卡托投影的輔助就很好解決了,墨卡托投影的計量單位是米(m),首先將兩個POI點的經緯度坐標換算為墨卡托坐標,剩下的就是簡單的勾股定理計算了。

          經緯度與墨卡托坐標之間的轉換沒有絕對統一的換算公式,每個地圖廠商根據自己的加密算法都多少存在一些差異,一般不能跨地圖廠商使用

          1.2 電子地圖制圖

          電子地圖的制圖是一項非常復雜的流程,技術的縱深涉及前端、后端、(空間)數據庫等等,除了技術層面以外,還涉及民生、政治等因素。篇幅有限,這些細節就不一一列舉了,只挑選在前端范圍內以及現有項目中涉及的知識點講一下,主要有兩個方面:

          1. 瓦片切圖;
          2. 路網結構。

          其中第一點是出于技術層面考慮,對從事 WebGIS 的前端開發者來說是必須具備的,因為我們對地圖只是使用,不會涉及這么深入的知識,所以大家可以當這點為科普內容;第二點的目的是讓大家對路網尋址算法的復雜度有大概的認知,從而在進行與路網相關需求的技術評審時能夠全面考慮,從而制定更合理的研發周期。

          下面就分別展開講一講。

          瓦片金字塔

          參照下面這張圖理解后續的內容:

          球面的經緯度坐標經過墨卡托投影之后是一張二維的平面圖,圖中的大部分內容的變動頻率是非常低的,比如上圖中展示的大陸和海洋板塊,除非遇到地殼運動,否則基本不會變動。為了持久化存儲,在webgis領域引入了「瓦片」的概念,意思是將墨卡托坐標系的二維地圖按照既定的規則切成一個個小方塊保存到服務器,然后前端的應用程序在繪制地圖時將這些方塊按順序拼接為完整的地圖,這些小方塊被稱為瓦片-tiles

          Tile 直接翻譯是“瓷磚”,倒是很貼切,電子地圖就是用一個個 tile 拼起來的,至于為啥被翻譯成“瓦片”我也不清楚,行業術語,跟著叫就是了。

          還記得前面提到的墨卡托投影的第二個假設嗎?將地球假設為正球體,投影之后得到的平面地圖是一個正方形,被切割成一個個瓦片也是正方形,這樣能夠大大降低計算復雜度。因為長方形需要考慮長和寬兩個計算因子,而正方形只需要考慮邊長一個因子即可。

          瓦片的尺寸是固定的,普清瓦片邊長是256像素,高清瓦片邊長在普清基礎上乘以2也就是512像素。但即便是高清瓦片在瀏覽器中渲染的時候也是被壓縮成256像素,這里我先不解釋為什么,大家也先不要看下文,先思考一下為什么這么做。

          留空思考時間..

          5...
          4...
          3...
          2...
          1...
          下面揭曉答案。

          所謂高清和普清的區別在于:在相同物理尺寸上的像素密集程度。高清瓦片是為了讓地圖在高清屏幕上看起來更清晰,高清電子屏幕的準入標準是DPR=2(retina屏),當然目前市面上有很多高清屏已經突破了這個值。DPR是屏幕物理像素與獨立像素的比值,前端開發者應該清楚 DPR 對于圖形的影響,也就能夠理解為何高清瓦片被壓縮一倍了,我就不在贅述了。

          看到這里,前端伙伴們是不是覺得 WebGIS 其實也并沒有那么神秘?其實跟我們日常開發所用的技術有很多共同點和相似性。

          上面介紹了瓦片的基本概念,在地圖中還有另外一個重要概念:比例尺-Scale。可以類比成望遠鏡的放大倍數,倍數越大,看到的東西就越多越清晰,地圖比例尺就類似望遠鏡的放大倍數。在墨卡托投影的平面地圖中比例尺代表每個像素等價的以米(meter)為單位的地理距離

          地圖從宏觀到微觀被切分為不同的級別(level),相鄰level的比例尺一般成兩倍關系(并不絕對,下文解釋)。請再次參考上面的圖片,每放大一個級別(即level+1),每個瓦片都會被切割為4張新瓦片,比如level 1 的1號瓦片在level 2中被切割為1-0、1-1、1-2、1-3四張瓦片,但這四張瓦片代表的地理范圍與 level 1和1號瓦片是完全相同的,只是細節更多了(類比望遠鏡就是看到的東西更清楚了)。

          在這樣的切割規則下,從宏觀到微觀,瓦片的數量隨著地圖 level 的增長成四倍增長關系(4^n),以數量為維度,所有的瓦片構成了一個金字塔結構,這就是 WebGIS 領域的術語:瓦片金字塔 - Tiles Pyramid。如下圖:

          上面介紹的其實是理論上的行業標準,但在現實工作中一般不會嚴格按照這份標準落地。在瓦片切割方面一般由3 個不同于標準的地方:

          1. 相鄰 level 不一定是嚴格的兩倍關系;
          2. 基于第一點,各level的瓦片不一定是無耦合的,部分瓦片可能被相鄰的2個甚至N個 level 共享使用;
          3. 不同的地圖廠商(準確的說應該是地圖數據服務商)使用的 level 上下限邊界可能不同,以搜狗地圖為例,level 最小值=3,最大值=19。

          基于以上3點區別,不同的地圖在一些涉及瓦片和level的計算規則上也有差異,另外再加上坐標加密算法的區別,所以大部分地圖的數據是無法共通的。

          路網結構

          對于路網這部分知識的科普,主要目的是讓大家對路網尋址算法的復雜程度和計算量級有一個大概的認知,從而針對目前以及后續項目中涉及到尋址功能的需求大家能夠對技術上的可行性、成本以及排期有更加理性的評估。

          下面這張圖是在電子地圖上的某個區域的路網示意圖:

          路網在數學上的模型是圖(Graph)。圖論是離散數學的一個分支,在計算機應用科學領域,《數據結構與算法》這門課中有專門的圖論算法,而且占比非常大。但由于相比較其他內容,圖論算法的復雜度高出很多,所以即便教材里有這一部分的內容,但很多高校在實際教學中不會教也不會考(反正我當時沒學~囧)。

          最簡單的圖是一個二元組,由頂點(vertex)和邊(edge)組成,表達式為:

          G = (V,E)

          在 WebGIS 領域,路網在是一種有向帶權圖。所謂帶權圖可以簡單的理解為每條邊有一些額外的屬性,比如路況、方向等等。

          路網尋址的需求主要是用在路徑規劃和導航場景下,這兩種場景有一個共同點:起點和終點是確定。在這個前提下,路網尋址其實就是圖論中經典的最小路徑尋址算法,這種算法已經非常成熟了,而且復雜度也已經被很多前人反復驗證和改良過,目前各家地圖使用的此類算法都是在時間復雜度和空間復雜度之間權衡的最優解,而且還要綜合考慮出行方式、交通、天氣等現實因素(這些在數學模型中都是帶權圖結構中edge的「權」)。

          但是(沒錯,什么都有但是),高效的尋址算法背后,請一定要注意「起點和終點是確定的」這個重要的前提。如果沒有了這個前提,復雜度會呈指數型增長,甚至可以說以現在的計算機硬件技術,這個復雜度是沒有上限的。為什么這么講,且看下文。

          在地圖的業務場景中還有一個非常典型的功能:POI檢索。比如以某個點為中心在指定半徑的圓形區域內檢索特定類型的POI。或者在地圖上自定義指定幾個點,然后在以這些點為頂點的不規則圖形內進行POI檢索。這兩種都是典型的POI檢索場景,跟路網尋址一毛錢關系都沒有。

          然而有時候我們還期望另外一種檢索方式:

          1. 指定某個點為起點坐標;
          2. 指定出行的方式以及最長出行時長或者最長出行距離;
          3. 在前面兩條要求下,找到在出行范圍之內的特定類型(比如酒店、加油站等)的POI。

          針對這種需求,我在搜狗工作期間寫了一個專利,但是在商業軟件領域基本不具備可行性,因為計算量太大了。這個專利純粹是為了完成老板交付的任務~哈哈。

          我們可以設想一下應該按照什么樣的流程去解決這個需求。

          第一種是正向解法:從起點開始沿著路網圖的邊遞進檢索,直到到達出行范圍的最遠邊界。這是符合現實規律的一種方法,就好比我想找一家便利店,最遠不能超過步行30分鐘,然后我就從當前位置開始沿著路走啊走,遇到路口就隨機選一個方向接著走,運氣好的話選的路邊有家店,運氣不好的話只能回到路口再隨機選一個方向試著找找,以此類推。當然現實跟算法的區別就是人的體力有限,一是不可能多線程,二是體力堅持不了走所有的路。

          第二種是逆向解法。就是在進行尋址算法之前盡量做減法,以給定的條件盡量縮小檢索范圍。比如指定步行最長距離是5公里,起點在中關村科貿大廈,按照以下步驟進行:

          1. 首先以科貿大廈為圓心,5公里為半徑,檢索圓形區域內的所有指定類型的POI,得到一個list;
          2. 然后依次以list中的每個POI為終點,科貿大廈為起點進行路徑規劃,得到所有POI與起點的真實地理距離,篩選出小于等于5公里的POI。

          事實上,前文提到的兩種POI檢索場景(圓形和自定義多邊形)都是逆向解法。POI在數據庫中的模型除了坐標以外,還有其他附加屬性,比如國家、城市、行政區域、甚至在哪條路等信息,就是為了縮小檢索范圍從而減輕計算量。

          逆向解法比正向解法的計算量小很多,但是兩種解法的計算量都會隨著出行時長和距離的增加呈指數型增長,幾乎沒有上限(當然這么說不準確,肯定是在地球范圍之內~)。

          如果地圖廠商自己想要不計成本地實現這個需求還是有一定可行性的,因為他們自己擁有路網和POI數據。但是如果我們想實現就很困難了,首先我們沒有數據,所以正向解法絕無可能;其次,我們是采買的地圖廠商的服務,而商業化的服務都是有限制的,比如每天的POI檢索量上限,如果限制在比較小的范圍內同時檢索量沒有超過上限,逆向解法是有一定可行性的。但是(是的,還有但是),對于我們來說,這個可行性必須建立兩個前提下:

          • 第一,如果是以出行距離為邊界,可行性相對高一些;
          • 第二,如果是以出行時間為邊界,則必須約束出行方式為步行或騎行。這兩種方式下的路網尋址算法一般不需要考慮交通等影響出行時長的因素,這樣在任何一方向上的最遠邊界距離都是一致的,即半徑=速度 x 時長。而如果是機動車出行,則必須考慮交通因素,不僅復雜度高,而且每個方向上的最遠邊界距離很大可能不一致,也就是說先圈定一個圓形區域的逆向解法中的“減法”不成立。

          路網相關的知識分享到這里,大家應該對尋址算法的計算量級有大概的認知了吧。作為科普,對 WebGIS 的了解到這個程度就可以了,其中還有很多WebGIS領域內的技術細節,篇幅有限就不一一列舉了。下半部分是跟前端技術相關性比較高內容,以電子地圖的渲染流程為引,介紹一下 WebGL 的一些基礎知識。

          二 WebGIS 與前端

          這塊內容分為兩部分,第一部分介紹一下電子地圖的渲染流程,期間按照瓦片的兩種類型(靜態/動態)分別講一下涉及的前端技術;第二部分以當前主流的矢量地圖為引,簡單介紹一下 WebGL 的一些基礎知識。關于 WebGL 的知識不會很深入,目的是讓大家的對 WebGL 以及圖形編程有大概的認知,后續前端組會制定一套數據可視化技術的系列課程,到時再深入到各項技術的細節知識。

          2.1 地圖渲染流程

          先講一點預備知識,電子地圖涉及幾種坐標系,每種坐標的計量單位如下:

          • 經緯度是球面坐標,我們日常使用經緯度單位的是角度(deg),在進行投影計算時需要換算為弧度(rad);
          • 墨卡托投影得到的二維坐標單位是米(m);
          • 電子屏幕坐標的單位是像素(px)。

          前端拿到的地圖數據中絕大多數是墨卡托坐標,很小一部分是經緯度坐標。墨卡托或經緯度坐標需要先被換算成屏幕坐標,最后被CSS拼接或WebGL渲染。

          這里的屏幕坐標準確的說應該是畫布(canvas)坐標,前端常規認知的屏幕坐標是CSS坐標,在柵格地圖中CSS坐標與canvas坐標是相等的,在矢量地圖中根據屏幕的DPR值,CSS坐標與canvas坐標成倍數關系。

          web地圖的渲染流程大致如下:

          地圖在進入渲染流程之前有一些必要的前置條件:

          • 地圖level,可以從緩存中讀取或者使用默認值;
          • 地圖的中心點坐標,可以通過瀏覽器的地理定位API獲取,也可以從緩存中讀取,如果都取不到,就必須有一個默認值;
          • 瀏覽器畫布的尺寸,如果是高清屏還需要DPR值。

          以上幾個條件的目的是為了計算地圖當前的視野范圍(bounds),進而計算出當前視野包含的瓦片編號列表。

          柵格地圖

          前半部分介紹了瓦片切圖,準確地說應該是「瓦片切割」,早期web地圖使用的瓦片是一張張靜態的png圖片,前端開發者使用CSS position按照瓦片編號拼接成一張完整的二維地圖。對前端來說,瓦片就等同于是圖片,所以“瓦片切圖”這個叫法一直被延續下來。

          但地圖數據本身是一個個坐標值并不是圖片,之所以將瓦片保存為圖片格式是因為早期的瀏覽器沒有能夠繪制海量數據的圖形技術,也就是大家熟知的 WebGL。在這個前提下,地圖廠商會在服務端搭建一套瓦片切圖預處理的流程,簡單理解就是先用 OpenGL 將地圖數據可視化,然后按照既定的規則把每個 level的地圖切割成一張張 256 * 256 的圖片托管到靜態文件服務器,最后前端開發者取圖片拼接。以圖片拼接而成的web地圖叫做「柵格地圖」。

          注意上圖里的切圖服務中包含「瓦片-data」和「瓦片-png」,兩者的內容一般是不同的。瓦片data的功能一方面是為了瓦片圖片切割,另一方面是提供給其他支持矢量圖形技術的平臺使用,比如 app。

          柵格地圖的優點是:

          • 前端的計算量非常小,性能相對高一點,對用戶體驗很友好;
          • 瀏覽器兼容性很好,由于技術原始,所以很多老舊瀏覽器都能夠兼容,比如搜狗的PC地圖即便是現在也能在 IE5 里無bug運行(這可能是唯一值得吹一下的優點了~囧)。

          基于以上兩個優點,目前仍然有很多地圖的JavaScript SDK使用柵格瓦片或者柵格混合矢量數據(一般是底圖用柵格瓦片,建筑物和poi用矢量數據)的形式。不過柵格地圖也有很明顯的缺點:

          • 相對于數據,圖片的體積更大,儲存成本相對更高一些;
          • 位圖是非矢量的,縮放會失真,視覺體驗不佳;
          • 基于上一條,每個瓦片圖片都不能被相鄰level共享,否則會嚴重失真,這進一步加大了圖片數量和儲存成本;
          • 無法3D化。

          矢量地圖

          隨著大部分主流瀏覽器對 WebGL實現了支持,很多地圖廠商都陸續開始研發并上線了矢量地圖。矢量地圖同樣需要預處理的切圖服務,但是預處理的產出并不是圖片格式的瓦片,而是與app一樣的瓦片data,換句話說,矢量web地圖可以與app地圖使用同一份數據,這意味著所有平臺的地圖數據可以統一維護和迭代

          “可以”的意思是可行但不一定,分業務場景。比如導航是app地圖獨有的功能,導航場景使用的地圖數據稱為“市街圖-street map”,這些數據是web地圖用不到的。

          矢量地圖說白了就是把原本OpenGL干的活交給了WebGL干,說起來簡單做起來難,WebGL 是非常底層的圖形編程技術,幾乎沒有任何上層封裝,接近純粹的計算機圖形學。相關的研發人才非常稀缺,圖形編程本身就是一個相對小眾的垂直領域,WebGL 圖形編程則更加小眾,雖然同屬于前端技術領域,但 WebGL 研發人員的招聘和培養難度比常規web前端研發人員要難很多,所以有能力開發 WebGL 矢量地圖的廠商要么是有足夠的人才儲備想為產品錦上添花,比如高德和百度的WebGL地圖第一個產品是自家的PC地圖;要么是有充分的客戶需求兌現商業價值,比如騰訊的WebGL地圖第一個產品是B端的 JavaScript SDK(2020年初上線),截止到今天PC地圖也沒有接入WebGL。否則單純靠愛發電很難落地,比如搜狗地圖的WebGL引擎開發到80%的時候被叫停,之后再也沒有撿起來過。

          2.2 矢量地圖與WebGL

          WebGL 圖形編程與常規web網站是完全不同的一套知識體系,雖然都使用JavaScript語言,但細節技術點完全不同,比如 WebGL 中被大量使用的 buffer、TypedArray、Protobuf等知識點在常規web網站中幾乎不會被涉及,另外還有一套類似C++的shader語言-GLSL。這些細節知識點會在后續的文章中講解,今天就簡單科普一下WebGL的渲染管線以及WebGL矢量地圖中常用的幾種算法。

          WebGL渲染管線

          WebGL 是 canvas的一種渲染上下文(context),canvas有兩種context:2D和WebGL。二者沒有任何關系,相同點是都需要借助canvas輸出圖像。目前大部分瀏覽器都支持 WebGL1.0,對 WebGL 2.0 的兼容很不理想,下文的討論都是針對 1.0 版本。

          下面這段代碼是創建WebGL 上下文的API以及幾個常用配置項:

          const canvas = <htmlcanvaselement>createElement('canvas');
          const gl: WebGLRenderingContext = canvas.getContext("webgl",{
            // 是否開啟自動抗鋸齒,建議關閉,瀏覽器兼容性差開了也沒用,就算有用性能也很差(因為瀏覽器用的抗鋸齒算法是效果很好同時性能很差的一種),大多是自己寫代碼實現
          antialias: false,
          // 是否開啟透明通道,一般建議關閉,性能損耗嚴重,自己寫代碼根據透明值計算出混合色值更高效。如果開啟的話,對研發人員的技術能力有更高要求
          alpha: false,
          // 是否開啟 stencil(模板) 緩沖區支持,數據量大的應用建議開啟,配合stencil test能夠減少無效渲染
          stencil: true,
          // 是否開啟 depth(深度) 緩存區支持,簡易的webgl地圖基本用不到depth test,一般是關閉的。像mapbox這類復雜的webgl地圖引擎是開啟的
          depth: false
          });
          

          WebGL 中有幾個核心概念:

          • shader - 著色器,分為兩種:
            • vertex shader - 頂點著色器,用于確定圖元頂點的坐標;
            • fragment shader - 片段著色器,用于處理光柵化之后的點陣像素信息,包括色值、透明度等等。

          除了以上兩種shader以外,OpenGL 還支持 geometry shader-幾何著色器,不過也不常用。WebGL不支持幾何著色器,

          • program(沒有準確翻譯),用于綁定(attach)兩種著色器。

          基于上面的幾個核心概念,WebGL 執行渲染的API調用流程是:分別創建兩種shader -> 創建一個program -> 將program與兩個shader綁定 -> 鏈接(link)program ->激活(use)program -> 傳參給shader -> 傳值&渲染。如下:

          // 1.1-創建vertex shader instance
          const vShader:WebGLShader = gl.createShader(gl.VERTEX_SHADER);
          // 1.2-指定vertex shader源-vShadersStr,字符串格式
          gl.shaderSource(vShader, vShadersStr);
          // 1.3-編譯vertex shader
          gl.compileShader(vShader);
          // 2.1-創建fragmentshader instance
          constfShader:WebGLShader = gl.createShader(gl.FRAGMENT_SHADER);
          // 2.2-指定fragmentshader源-fShadersStr,字符串格式
          gl.shaderSource(fShader,fShadersStr);
          // 2.3-編譯fragmentshader
          gl.compileShader(fShader);
          // 3-創建program
          const program: WebGLProgram = gl.createProgram();
          // 4-綁定program與兩個shader
          gl.attachShader(program, vShader);
          gl.attachShader(program, fShader);
          // 5-鏈接program
          gl.linkProgram(program);
          // 6-激活program
          gl.useProgram(program);
          // 7-傳值&渲染相關API下文再講
          

          接下來就是傳值和執行渲染,這部分需要了解WebGL shader中的三種變量類型:

          • attribute變量是由JavaScript API 傳給頂點著色器的數據,術語為vertexBufferObject-VBO,顧名思義是一種二進制的buffer,在JavaScript中的表達是類型數組-TypedArray。根據精度的不同需求最常用的有Float32Array和Uint8Array。attitude主要是包含頂點坐標,但是并沒有嚴格的限制,可以傳遞任何其他用途的數據,比如色值-color,前提是數據精度相同;
          • uniform變量也是由JavaScript API傳遞給著色器,不過可以同時被頂點和片段著色器訪問,通常用于傳遞所有頂點共用的數據,比如MVP矩陣(下文介紹)、畫布分辨率、色值等等。uniform不是常量,著色器中有常量的定義規范-defined,語法類似C++如下:
          #define PI 3.1415926538
          
          • varying變量不是由JavaScript API傳入著色器,而是在頂點著色器中根據其他數據(attribute/uniform/defined)計算出來,然后傳遞給片段著色器中同名varying變量。目的有兩種:減少GPU的計算壓力。因為頂點著色器只會計算指定圖元的頂點數量,而片段著色器需要在圖元覆蓋的所有像素點都計算一次;片段著色器無法訪問attribute數據,varying變量可以傳遞一些與attribute相關的數據。

          結合上文的幾種變量類型,WebGL的渲染流程大致如下圖所示(條紋框表示GPU內部流程,開發者無法干預):

          1. 在CPU側(也就是JavaScript側)計算出必要的數據,包括VBO和uniform,然后傳遞給著色器;
          2. 頂點著色器計算出制定圖元的頂點坐標和必要的varying變量;
          3. 接下來是開發者不可控的GPU內部邏輯,包括圖元裝配和光柵化:
            1. 圖元裝配:根據JavaScript調用的繪圖API所指定的圖元類型(點/線段/三角形)和頂點坐標組裝成對應的幾何圖形;
            2. 光柵化:將裝配好的幾何圖形轉化為二維圖像,圖像中的每個點都對應一個物理像素點,叫做片元或片段(fragment);
          4. 片段著色器在圖元覆蓋的像素點依次計算出色值結果;
          5. 接下來是測試混合(Test&Blending)階段,之后會生成幀緩存FBO,這部分也是開發者不可控的;
          6. 最后電子屏幕取幀緩存數據進行展示。

          MVP矩陣

          簡單聊一下上文提到的 MVP 矩陣,細節的技術實現方案后續的分享中再說。

          MVP 矩陣是仿射變換過程中三種變換矩陣的統稱:

          • M代表Model,Model矩陣即模型矩陣,可以簡單理解為圖形本身的變換矩陣,經過Model矩陣變換后得到頂點在世界空間中的坐標值;
          • V代表View,View矩陣即觀察矩陣,作用是將世界空間的頂點坐標映射到可以簡單理解為攝像機(即觀察者,camera是一個抽象對象)為中心的觀察空間中;
          • P代表Projection,Projection矩陣即投影矩陣,圖形編程中兩種投影方式:正向投影和透視投影。Projection矩陣的作用是將觀察空間的三維坐標映射到二維的裁剪空間中,可以理解成將三維的圖形投影到二維的畫布上。

          頂點的原始坐標需要依次經過Model矩陣、View矩陣和Projection矩陣變換(左乘)之后才能夠得到它在裁剪空間中的最終坐標值。如下代碼所示:

          precision mediump float;
          attribute vec2 a_position;
          uniform vec2 u_resolution;
          uniform mat4 u_mMatrix;
          uniform mat4 u_vMatrix;
          uniform mat4 u_projMatrix;
          void main() {
              position = (u_vMatrix*u_mMatrix*vec4(a_position,0,1)).xy;
              
              gl_Position = u_projMatrix*vec4((position / u_resolution * 2.0 - 1.0)*vec2(1,-1), 0, 1);
          }
          

          上面代碼中的u_resolution是畫布的尺寸,Model和View矩陣的數值一般是與畫布的坐標使用相同的計量單位(px),Projection矩陣一般是歸一化的矩陣。

          三種矩陣在數學上沒有區別只是計算邏輯上的三種抽象,都是4*4矩陣,都可以包含位移、縮放、斜切等形變信息。一般Projection矩陣是單獨的,Model和View矩陣可以分開也可以在CPU側計算之后得到一個Model&View矩陣再傳入頂點著色器。

          WebGIS常用算法

          最后這部分介紹兩種 WebGIS 領域常用的算法,準確地說應該是 WebGIS 繪圖領域,一種是多邊形三角剖分算法,一種是R-Tree算法。這兩種算法與 WebGIS本身并沒有太大關系,屬于計算機圖形學通用的算法。

          三角剖分算法

          計算機圖形學中只有三種基本圖元:點、線段、三角形。點和線段的適用面很窄,極少被使用,

          繪圖過程中絕大部分的圖形底層都是一個個三角形組成的,如下圖所示:

          喜歡玩3D游戲的人可能知道,建模對游戲的視覺效果影響很大,除了模型本身的設計風格以外,建模的精細度也很重要,而衡量精細度的核心指標之一便是三角形的數量。雖然數量不是唯一指標,但細致的3D模型的三角形數量一定非常龐大,一般數量越多,模型的邊緣越平滑,視覺效果越好。反面例子比如下圖展示學動畫三年系列,人物(姑且算是個人吧)模型邊緣有非常明顯的棱角,過渡非常不順滑。

          回到 WebGIS 領域,我們看到的電子地圖是由一個個不規則的多邊形(Polygon)和線(Line)組成,三角剖分算法的作用就是把這些多邊形分割成一個個三角形,然后才能夠被 WebGL 繪制出來。

          其實線也是多邊形,因為 WebGL 1.0 不支持寬于1像素的線,所以寬線必須以多邊形的形式繪制。

          三角剖分算法有兩種類型,一種是多邊形三角剖分,一種是點集三角剖分,后者在圖形編程領域不常用,我們只需要關注多邊形三角剖分。

          三角剖分是典型的動態規劃算法,對于多邊形三角剖分最簡單的場景就是三個點,也就是三角形,這種根本不需要分割。再復雜一點就是矩形,前端小伙伴們可以想像一下我們常用的 CSS盒子,html布局就是一個個矩形拼起來的,對于一個矩形來說需要2個三角形組成。然后依次再遞增多邊形的頂點個數,比如6個:

          這時候需要4個三角形。

          很細節的算法實現就不講了,其實我也沒搞太懂哈哈。對于前端工程師來說,從零實現這套算法的代價太大,更別提還要很細化地調優,我們直接使用經過大量實踐驗證的開源算法和工具就可以了。WebGL圖形編程常用的三角剖分工具是Libtess,這套算法也是OpenGL編程常用的,非常高效。

          R-Tree算法

          R-Tree是一種樹狀數據結構,在 GIS領域主要用于空間數據的儲存。在繪圖方面,R-Tree較多地被用于圖形沖突檢測。

          柵格地圖的POI點坐標是在瓦片預處理過程中被計算好的,哪個顯示哪個不顯示都被預定義好了,前端拿到數據之后按照既定的坐標渲染出來即可。而矢量地圖則不然,前文提到,矢量地圖實際上就是讓WebGL干了OpenGL的活,不單是繪圖,繪圖過程中的任何事情都變成了前端的事情,POI沖突檢測就是其中一項。

          先看下面這張圖:

          圖中有兩個POI點:微電子與納電子學系(下文簡稱POI點A)和超導量子信息處理實驗室(下文簡稱POI點B),每個點都有圖標和文本兩部分,點A和點B的文本都位于圖標的下方。

          POI有一個「權重-rank」的屬性,繪圖時要保障權重高的優先渲染,如果畫布空間有限則要合理地調整低權重POI的布局甚至不渲染。仍然以上圖為例,假設點A的權重高于點B:

          1. 先渲染點A,圖標必須渲染出來;
          2. (偽)隨機選一個方位放置文本,圖中選的是圖標下方;
          3. 渲染點B,點B的圖標與點A的圖標和文本都不沖突,正常渲染;
          4. 渲染點B的文本,可選四個方位-上下左右(復雜情況下可選八個方位),使用R-Tree描述文本的矩形盒子,檢測發現上左右都會與點A的文本發生位置沖突,只有下方可行。

          以上便是使用R-Tree進行位置沖突檢測的簡易流程。除了POI位置檢測以外,繪圖中R-Tree另一個使用場景是道路名稱的位置標注算法,如下圖中的「雙清路」「荷清路」文本:

          R-Tree沖突檢測的開源工具推薦rbush。

          POI的位置布局(POI Placement)算法也是單獨的一項研究課題,有大量論文,大家有興趣可以自行查閱相關資料。

          其實R-Tree不僅僅適用于圖形編程,在常規前端領域也有可借鑒的場景。比如下圖展示的一個報表看板:

          圖中的布局亂了,報表之間存在遮擋情況,如果這種情形需要前端實現一個自動布局,也就是圖中的「一鍵美化」功能,你可能考慮怎么辦?

          這時候就可以嘗試用R-Tree解決,每個報表的容器都是一個個矩形盒子,使用rbush可以檢測出所有矩形的沖突情況,然后再嘗試自動調整布局直到rbush檢測不沖突為止。R-Tree提供了一種解決思路和搭配的工具,在此基礎之上可以進一步完善細化的布局調整邏輯。

          三 總結

          以上是今天分享的全部內容,簡單總結一下。

          第一部分介紹了 WebGIS 領域的一些基礎知識,包括坐標體系、制圖繪圖流程和路網結構。對于日常工作中涉及地圖的項目,對這些基礎知識有個大概了解可以對工作有輔助作用比如技術評審。

          第二部分介紹了兩種地圖類型以及矢量地圖所使用的圖形技術WebGL,簡單分享了WebGL的渲染管線和常用的兩種算法。電子地圖不像游戲、動畫等高復雜度圖形應用對WebGL技術有很苛刻的要求,地圖引擎頂多發揮了WebGL 三分之一的能力,我們日后在數據可視化方面的技術需求,可能涉及WebGL的部分甚至不如地圖那么復雜,所以今天我們對WebGL先有一個大概的認知,后續再一步步學習內部的細節知識。


          主站蜘蛛池模板: 国产精品无码不卡一区二区三区| 天天综合色一区二区三区| 亚洲综合无码一区二区三区| 国产一区二区三区播放心情潘金莲| 国产成人高清亚洲一区91| 蜜臀AV一区二区| 中文字幕VA一区二区三区 | 清纯唯美经典一区二区| 亚洲av成人一区二区三区观看在线| 国产一区二区视频在线播放| 国产精品盗摄一区二区在线| 在线电影一区二区三区| 亚洲AV日韩综合一区| 亚洲爆乳精品无码一区二区| 人妖在线精品一区二区三区| 久久人做人爽一区二区三区| 99久久精品国产一区二区成人 | 日韩高清一区二区三区不卡 | 中文字幕一区二区视频| 丰满少妇内射一区| 国产在线精品一区二区中文| 真实国产乱子伦精品一区二区三区 | 国产一区二区三区乱码| 三上悠亚精品一区二区久久| 国产凹凸在线一区二区| 亚洲国产国产综合一区首页| 91video国产一区| 伊人色综合视频一区二区三区| 亚洲综合色一区二区三区| 毛片无码一区二区三区a片视频| 理论亚洲区美一区二区三区 | 国产大秀视频在线一区二区| 无码中文字幕乱码一区 | 无码国产精品一区二区免费I6| 久久精品国内一区二区三区| 国产成人免费一区二区三区| 中文字幕一区二区区免| 久久久久人妻一区二区三区| 国产精品无码亚洲一区二区三区| 国产成人一区二区动漫精品| 制服中文字幕一区二区|