webgis —— 為瓦片構(gòu)建緩存

前言

了解 webgis 或接觸過 webgis 相關(guān)應(yīng)用的童鞋應(yīng)該知道捻悯,webgis 應(yīng)用有個特點擦俐,加載影像地圖或者矢量地圖是剛需宵距。

相信對于國內(nèi)的 gis 行業(yè)的從業(yè)者來說仔役,天地圖應(yīng)該不是一個陌生的存在吧胁赢。

對比其他商業(yè)地圖(高德间护、百度亦渗、騰訊地圖等)而言,天地圖有幾個明顯的優(yōu)點:

  1. 它是官方出品的地圖汁尺,權(quán)威性較高法精。
  2. 它更新比較及時,每年都會對部分區(qū)域的影像進(jìn)行更新痴突。
  3. 國內(nèi)熱門地方搂蜓,影像精度較高,達(dá)到亞米級別辽装。
  4. 坐標(biāo)系采用 wgs84 坐標(biāo)系帮碰,無偏差,無加密拾积。

至于缺點嘛殉挽,這里就不做過多的介紹了,用過的童鞋自然會明白殷勘。

當(dāng)然此再,其實這篇文章的出發(fā)點,解決的就是天地圖的一大缺點——服務(wù)不穩(wěn)定玲销。

這不输拇,最近很長一段時間,天地圖的服務(wù)都不給力贤斜,經(jīng)常加載極其緩慢策吠,還動不動會出現(xiàn)響應(yīng)超時的情況逛裤。

如果只是自己用用倒也沒啥,但是被老板和客戶碰到猴抹,就鬧心了带族,接受到過很多次反饋。

雖然蟀给,這問題有外部的原因蝙砌,也并不全是我們自己能解決的,但是有沒有什么方法跋理,可以作出一定程度的優(yōu)化呢择克?

不然,這個問題前普,極其的影響 webgis 應(yīng)用的使用體驗肚邢。

畢竟,公司的業(yè)務(wù)都或多或少和 webgis 相關(guān)拭卿。

思考

對于這種問題骡湖,其實業(yè)界通常的做法,就是加緩存峻厚。

就是响蕴,經(jīng)典的——用空間換時間。

當(dāng)然目木,對于我們遇到的這個問題來說换途,其實有兩種解決的思路。

一種是刽射,后端做緩存军拟;一種是,前端做緩存誓禁。

當(dāng)然這兩種方案懈息,其實都是針對當(dāng)前問題所作出的妥協(xié)下的無奈。

我們知道摹恰,一般而言辫继,無論是使用 cesium 還是 openlayers 等 webgis 框架,直接目的都是為了能夠在網(wǎng)頁上直接使用地理空間數(shù)據(jù)俗慈。

而地理空間數(shù)據(jù)有個特點姑宽,就是空間范圍大,時間跨度長闺阱,這一特點導(dǎo)致地理空間數(shù)據(jù)的數(shù)據(jù)量量普遍都很大炮车。

所以,雖然公司是專門做 gis 方面應(yīng)用的,也會定期制作發(fā)布一些區(qū)域的影像圖瘦穆,但是像天地圖這種全球性底圖纪隙,我們也很難做到自己發(fā)布一份來使用。

至于其中原因扛或,說起來很簡單绵咱,因為做這個事情的成本和收益不對等。

況且天地圖服務(wù)本身質(zhì)量不錯熙兔,又可以免費使用悲伶,為什么要吃力不討好的另起爐灶呢?

后端緩存

如果要采用后端緩存的方案黔姜,直接用 nginx 做轉(zhuǎn)發(fā)即可拢切。

我們知道,對于天地圖而言秆吵,這是一個瓦片的請求地址(替換成自己的 token 即可):

https://t2.tianditu.gov.cn/img_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=w&TileMatrix=12&TileRow=1665&TileCol=3413&style=default&format=tiles&tk=你的token

對于不同的瓦片而言,請求變化的只是請求地址里面的幾個參數(shù):

  1. TileMatrix——層級數(shù)
  2. TileRow——行號
  3. TileCol——列號

所以五慈,了解了這些以后纳寂,再稍微研究下 nginx 的緩存策略,想要實現(xiàn)后端對天地圖的緩存就很簡單了泻拦。

在這里毙芜,我只做一下簡單的介紹,不作詳細(xì)的贅述争拐,有興趣的童鞋可以自行研究一下腋粥。

前端緩存

有的同學(xué)可能會說,有了后端緩存的方案架曹,為什么要研究前端緩存呢隘冲?

不要著急,讓我們先縷一縷绑雄,做個分析對比展辞。

后端緩存,實質(zhì)上是把天地圖服務(wù)器上的瓦片資源万牺,緩存到我們自己的服務(wù)器上罗珍,會增加我們自己服務(wù)器的存儲和帶寬;但是它有個優(yōu)點是脚粟,一旦有了緩存覆旱,以后高頻訪問的地方,就可以不依賴天地圖的服務(wù)了核无,并且自己所有的項目都能用扣唱。

但是前端緩存也有自己的優(yōu)勢,不占用自己服務(wù)器的存儲和帶寬;一旦有了緩存以后画舌,能支持離線加載堕担,速度更快;每個用戶都有自己的小型緩存服務(wù)曲聂,不會相互影像霹购。

當(dāng)然前端緩存,也不是沒有缺點的朋腋,其中一個重要的影響就是齐疙,會增加代碼的復(fù)雜度,對開發(fā)人員的要求較高旭咽;效果比不上瀏覽器的默認(rèn)緩存贞奋,稍微會影響一些加載效率;對用戶電腦的要求較高穷绵。

我們姑且不評價采用哪種方式更好轿塔,單純從解決問題的角度出發(fā),來探討下如何增加一層前端緩存仲墨。

我們知道勾缭,用 Window.localStorage - Web APIs | MDN 或者 Window.sessionStorage - Web APIs | MDN 肯定是不行,因為它們的容量都有限制目养,達(dá)不到我們的使用要求俩由。

Web SQL 基本已經(jīng)處于廢棄狀態(tài),因此癌蚁,基本上可以確定幻梯,我們能選用的方案就是 Using IndexedDB - Web APIs | MDN

當(dāng)然 IndexedDB 原生的 api 寫起來比較復(fù)雜努释,可以用已經(jīng)封裝好的框架碘梢,幫助我們開發(fā),可以參考 mdn 頁面的推薦:Using IndexedDB - Web APIs | MDN洽洁。

當(dāng)然痘系,這篇文章,不太會專注于怎么把數(shù)據(jù)存儲在 IndexedDB 里面饿自,如果你不了解而剛好又感興趣的話汰翠,可以翻翻博主以前的文章,相信會有一些啟發(fā)昭雌。

既然是與 webgis 相關(guān)的文章复唤,那么我們當(dāng)然要從 cesium、openlayers 這些 webgis 常用的框架入手烛卧,介紹佛纫,如何在這些框架里面實現(xiàn)瓦片的前端緩存妓局。

openlayers

對于 openlayers 來說,想要實現(xiàn)該功能很簡單呈宇。

加載 wmts 瓦片地圖的時候 好爬,有一個選項 tileLoadFunction ,支持傳入一個自定義的加載函數(shù)甥啄。

OpenLayers v7.1.0 API - Class: WMTS

我們先來看看存炮,該函數(shù)的默認(rèn)值:

function(imageTile, src) {
  imageTile.getImage().src = src;
};

分析可知,其主要干的事情就是給目標(biāo)瓦片對象賦予 src 值蜈漓。

而對于某個瓦片的鏈接來說穆桂,我們可以直接從 url 參數(shù)中分析出來所有我們需要的信息,因此我們直接在鏈接上下文章即可融虽。

比如我們在使用 openlayers 加載天地圖時候享完,該函數(shù)的第二個參數(shù) src 可能是下面的值:

https://t2.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL=49&TILEROW=28&TILEMATRIX=6&tk=你的token

那么我們就可以直接用正則或者 URLSearchParams - Web API 接口參考 | MDN 對 url 進(jìn)行處理,得到我們想要的層級有额、行列號般又、圖層名等信息,作為存儲的唯一標(biāo)識巍佑。

接下來倒源,我們需要通過 ajax 獲取到圖像的二進(jìn)制數(shù)據(jù),然后通過 URL.createObjectURL() - Web APIs | MDN 將二進(jìn)制文件轉(zhuǎn)成一個鏈接句狼,直接賦值給 imageTile.getImage().src 即可。

關(guān)鍵代碼如下:

const tileLoadFunction = async(imageTile, src) => {
    // 獲取 key
    const cname = this._getKeyName(src);
    // 查詢下緩存內(nèi)是否有該數(shù)據(jù)
    const data = await getOneByName(cname);
    // 拿到目標(biāo) image 對象
    const image = imageTile.getImage();
    image.onload = function onload() {
        // 圖片加載完热某,釋放一個URL資源.
        window.URL.revokeObjectURL(this.src);
    };
    // 如果存在緩存腻菇,用緩存數(shù)據(jù)
    if (data) {
        image.src = URL.createObjectURL(data.blob);
    } else {
        // 如果不存在緩存,通過 fetch 加載數(shù)據(jù)
        const res = await fetch(src);
        const newBlob = await res.blob();
        image.src = URL.createObjectURL(newBlob);
        // 加入緩存
        addOneImage({
            name: cname,
            blob: newBlob
        });
    }
}

cesium

要想在 cesium 中使用前端緩存昔馋,實質(zhì)上和在 openlayers 實際上大同小異筹吐。

但是比較棘手的是,cesium 并未直接提供改寫瓦片請求的接口秘遏,因此我們需要通過重載源碼的方式丘薛,來實現(xiàn)我們的需求。

我們知道邦危,在 cesium 的架構(gòu)中洋侨,ImageryProvider 是所有圖層的容器對象,每個 layer 必定會對應(yīng)著一個該對象的實例倦蚪。

分析 cesium 的源碼可知希坚, ImageryProvider 對象的 loadImage 方法,是圖層中加載每個瓦片的方法陵且。

因此裁僧,我們在 loadImage 上下手即可:

// 將 ImageryProvider 對象上的 loadImage 方法重命名
ImageryProvider.loadImage2 = ImageryProvider.loadImage;
// 重載 loadImage 方法
ImageryProvider.loadImage = function loadImage(imageryProvider, url) {
    // 符合我們需求的,調(diào)用重載的方法
    if (imageryProvider instanceof WebMapTileServiceImageryProvider) {
        // 返回 promise
        return new Promise((resolve) => {
            // 獲取唯一的 key
            const {
                layer,
                tilecol,
                tilematrix,
                tilerow,
            } = url.queryParameters;
            const cname = `${layer} ${tilematrix} ${tilerow} ${tilecol}`;

            // 從緩存里獲取數(shù)據(jù)
            getOneByName(cname).then(async (data) => {
                // 存在數(shù)據(jù)
                if (data) {
                    const imgUrl = URL.createObjectURL(data.blob);
                    const img = new Image();
                    img.src = imgUrl;
                    img.crossOrigin = 'Anonymous';
                    img.onload = () => {
                        // 直接將圖片返回
                        resolve(img);
                    };
                } else {
                    // 不存在數(shù)據(jù)
                    const resource = Resource.createIfNeeded(url);
                    const newBlob = await resource.fetchImage({
                        preferBlob: true,
                        preferImageBitmap: false,
                        flipY: true,
                    });
                    // 加入緩存
                    addOneImage({
                        name: cname,
                        blob: newBlob.blob
                    });
                    // 將 fetchImage 獲取到的對象返回
                    resolve(newBlob);
                }
            });
        });
    }
    // 調(diào)用默認(rèn)的方法
    return ImageryProvider.loadImage2.call(this, imageryProvider, url);
};

后記

就像文章前面所說的那樣,無論是前端緩存還是后端緩存聊疲,都只是一種輔助措施茬底,一種多方考量下的無奈之舉,兩者都有自己的缺點和優(yōu)勢获洲。

不過話又說回來阱表,web 應(yīng)用作手動緩存,給人一種多此一舉的感覺昌妹。

畢竟很多情況下捶枢,瀏覽器為了加快網(wǎng)頁的訪問速度,已經(jīng)盡可能的做了很多事情飞崖。

而通常情況下烂叔,效率最高的方式,肯定是去遵循瀏覽器的默認(rèn)緩存策略固歪,從而使得我們的應(yīng)用達(dá)到最好的使用效果蒜鸡。

奇淫巧計有一定的幫助,但是終究作用還是有限牢裳,解決問題的同時逢防,勢必會引發(fā)新的問題。

?著作權(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
  • 文/潘曉璐 我一進(jìn)店門晌畅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來但指,“玉大人,你說我怎么就攤上這事抗楔∑宓剩” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵谓谦,是天一觀的道長贫橙。 經(jīng)常有香客問我,道長反粥,這世上最難降的妖魔是什么卢肃? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任疲迂,我火速辦了婚禮,結(jié)果婚禮上莫湘,老公的妹妹穿的比我還像新娘尤蒿。我一直安慰自己,他們只是感情好幅垮,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布腰池。 她就那樣靜靜地躺著,像睡著了一般忙芒。 火紅的嫁衣襯著肌膚如雪示弓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天呵萨,我揣著相機與錄音奏属,去河邊找鬼。 笑死潮峦,一個胖子當(dāng)著我的面吹牛囱皿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播忱嘹,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼嘱腥,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了拘悦?” 一聲冷哼從身側(cè)響起齿兔,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎础米,沒想到半個月后愧驱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡椭盏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吻商。 大學(xué)時的朋友給我發(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
  • 正文 我出身青樓,卻偏偏與公主長得像孩等,于是被迫代替她去往敵國和親艾君。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

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