geotrellis使用(三十五)Cesium加載geotrellis TMS瓦片

前言

作任何事情都不是想象中的那麼簡單。很久沒有更新技術博客了,跟最近瞎忙有很大關係,雖然說是瞎忙也抽空研究了些技術。css

主要是前端渲染,像原生的WebGL和Cesium。WebGL寫了幾篇博客,自我感受還能夠。Cesium是一個封裝好的WEB端3D Earth框架,有了WebGL的基礎以後切換到Cesium按理說一切應該是瓜熟蒂落,簡單的測試了幾個功能以後發現確實很是好,簡單的幾行代碼就能夠實現Google Earth的功能,固然Google Earth重要的絕對不是他的渲染框架。html

前期作了不少Geotrellis的工做,那麼我就想着能不能把Geotrellis發佈的TMS加載到Cesium中來,原本這是很簡單的嘛,之前是在leaft-let中顯示,如今就是換一個地方顯示而已,而且Cesium已經調通。說幹就幹,結果怎麼着,前天晚上整到四點,昨天折騰了幾個小時竟然一直不出圖,因此我說任何看似簡單的事情其實都不簡單,下面就讓我娓娓道來。前端

1、Cesium

1.1 簡介

介紹以前仍是來簡單介紹一下Cesium,固然若是後面繼續對此框架進行研究的話可能也會多寫幾篇關於此框架的博客。git

官網地址:https://cesiumjs.org/,Github地址:https://github.com/AnalyticalGraphicsInc/cesium程序員

其功能簡單明瞭,固然也很強大,基礎教程能夠參考http://blog.csdn.net/UmGsoil/article/category/7005304,固然官方文檔更好。github

1.2 簡單使用

無需考慮這麼複雜,從簡單裏說Cesium就是一個前端地圖渲染引擎,與leaft-let、OpenLayer相同,只是Cesium作成了3D的。因此從基礎功能都是類似的。跨域

首先在html頁面加載Cesium,以下:瀏覽器

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello 3D Earth</title>
    <script src="CesiumUnminified/Cesium.js"></script>
    <style>
        @import url(CesiumUnminified/Widgets/widgets.css);
        html, body, #cesiumContainer {
            width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden;
        }
    </style>
</head>
<body>
    <div id="cesiumContainer"></div>
    <script src="my_js.js"></script>
</body>
</html>

其中CesiumUnminified存儲了相關文件,從Github中下載便可。my_js.js是咱們本身要寫的js文件。my_js.js最簡單的狀況只須要一句話便可:cookie

var viewer = new Cesium.Viewer("cesiumContainer");

這樣瀏覽器就會渲染出一個3維地球並自動加載微軟的影像地圖。那麼如何更改或者添加圖層呢?網絡

var viewer = new Cesium.Viewer("cesiumContainer", {
    animation: true, //是否顯示動畫控件(左下方那個)
    baseLayerPicker: false, //是否顯示圖層選擇控件
    geocoder: true, //是否顯示地名查找控件
    timeline: true, //是否顯示時間線控件
    sceneModePicker: true, //是否顯示投影方式控件
    navigationHelpButton: false, //是否顯示幫助信息控件
    infoBox: true, //是否顯示點擊要素以後顯示的信息
    imageryProvider : new Cesium.WebMapTileServiceImageryProvider({
        url: "http://t0.tianditu.com/vec_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=vec&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles",
        layer: "tdtVecBasicLayer",
        style: "default",
        format: "image/jpeg",
        tileMatrixSetID: "GoogleMapsCompatible",
        show: false
    }),
    geocoder: false // no default bing maps
});
//全球影像中文註記服務
viewer.imageryLayers.addImageryProvider(new Cesium.WebMapTileServiceImageryProvider({
    url: "http://t0.tianditu.com/cia_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=cia&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default.jpg",
    layer: "tdtAnnoLayer",
    style: "default",
    format: "image/jpeg",
    tileMatrixSetID: "GoogleMapsCompatible",
    show: false
}));

這段代碼就會自動在3維地球中加載天地圖的線劃圖並添加註記。因此剩下的事情就很簡單了,只須要再添加我本身的TMS便可。

1.3 問題來了

在上述代碼下方添加以下代碼:

var layers =  viewer.scene.imageryLayers; //全部圖層(非基本圖層)

var layer = layers.addImageryProvider(
    new Cesium.UrlTemplateImageryProvider({
        url : 'http://xxxx/modis/ndvi/{z}/{x}/{y}',
        format: "image/png"
    })
);
//50%透明度
layer.alpha = 0.5;
//兩倍亮度
layer.brightness = 2.0;

很簡單的代碼,獲取圖層對象,而後添加一層,url爲咱們本身的瓦片請求格式,這是我用Geotrellis發佈的modis數據ndvi服務。並設置該圖層透明度和增長亮度防止蓋住上面的註記層。原本應該是點擊一下刷新就出來效果的事情,結果足足折騰到我崩潰。

不管怎麼刷新就是出不來那層瓦片,其餘兩層數據正常顯示,打開瀏覽器的調試模式,可以看到對ndvi瓦片的請求返回的都是200 OK,也能在調試中看到單個瓦片應有的效果。而後變換各類添加圖層的格式(UrlTemplateImageryProvider、WebMapTileServiceImageryProvider、Cesium.createTileMapServiceImageryProvider這些不是本文重點,在後續文章詳細介紹)均顯示不出瓦片,然後又去掉其餘兩層瓦片只保留NDVI,最後又添加Geotrellis發佈的其餘TMS服務,可是不管怎麼折騰,只要是我本身Geotrellis發佈的TMS均沒法顯示,折騰到四點多,始終沒有出來,在stackoverflow和github上提了問,等了半天也無人回覆,只好悶悶不樂的去睡了。

2、解決方案

2.1 轉角遇到答案

今天中午小睡片刻,起牀後收到一封郵件,趕忙打開看了一下,是Github的回覆郵件,喜出望外,結果一看內容原來是告訴我不要在Issue中發佈提問,告訴了我Google的提問列表(https://groups.google.com/forum/#!msg/cesium-dev/RfAlZZkPBaM/xGOK01trAwAJ;context-place=forum/cesium-dev),整我的當時就很差了,既然這樣只有上去瞅瞅,打開簡單一搜索,竟然有現成的,問題描述跟個人如出一轍,解決方案是添加CORS。

其實我以前折騰到四點多的時候腦子裏就有這個意識,必定是我發佈的TMS缺乏了某個東西(或者是某個東西與Cesium的要求不一致),致使Cesium沒法正常顯示個人瓦片,因此一看到這個我就亢奮了,程序員的直覺告訴我這確定就是我要找的東西。

2.2 解決

因此問題就來了,看樣子我要在Geotrellis中折騰CORS了。Geotrellis採用Scala語言開發,因此我也是拿Scala寫的,發佈網絡服務用的是Akka,Akka是開源的網絡服務框架,因而就搜索了一下Akka CORS,很快就有了答案。

關於CORS的介紹,看這篇文章就夠了:http://www.ruanyifeng.com/blog/2016/04/cors.html。CORS簡單來講就是跨域資源共享,當跨域進行Ajax請求的時候進行權限驗證等操做。其實細細想來卻是這麼回事,Cesium請求瓦片必定用的是XMLHttpRequest,而個人TMS又未使用CORS,因而怎麼折騰都出不來結果,固然對這塊不太熟悉是致使問題發生的直接緣由。

找到問題解決就很容易了,Github中有現成的解決方案。首先添加一個CorsSupport特質,以下:

import akka.http.scaladsl.model.HttpHeader
import akka.http.scaladsl.model.HttpMethods._
import akka.http.scaladsl.model.HttpResponse
import akka.http.scaladsl.model.headers._
import akka.http.scaladsl.model.headers.Origin
import akka.http.scaladsl.server.Directive0
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.MethodRejection
import akka.http.scaladsl.server.RejectionHandler

trait CorsSupport {

  protected def corsAllowOrigins: List[String]

  protected def corsAllowedHeaders: List[String]

  protected def corsAllowCredentials: Boolean

  protected def optionsCorsHeaders: List[HttpHeader]

  protected def corsRejectionHandler(allowOrigin: `Access-Control-Allow-Origin`) =
    RejectionHandler
    .newBuilder().handle {
      case MethodRejection(supported) =>
        complete(HttpResponse().withHeaders(
          `Access-Control-Allow-Methods`(OPTIONS, supported) ::
            allowOrigin ::
            optionsCorsHeaders
        ))
    }
    .result()

  private def originToAllowOrigin(origin: Origin): Option[`Access-Control-Allow-Origin`] =
    if (corsAllowOrigins.contains("*") || corsAllowOrigins.contains(origin.value))
      origin.origins.headOption.map(`Access-Control-Allow-Origin`.apply)
    else
      None

  def cors[T]: Directive0 = mapInnerRoute { route => context =>
    ((context.request.method, context.request.header[Origin].flatMap(originToAllowOrigin)) match {
      case (OPTIONS, Some(allowOrigin)) =>
        handleRejections(corsRejectionHandler(allowOrigin)) {
          respondWithHeaders(allowOrigin, `Access-Control-Allow-Credentials`(corsAllowCredentials)) {
            route
          }
        }
      case (_, Some(allowOrigin)) =>
        respondWithHeaders(allowOrigin, `Access-Control-Allow-Credentials`(corsAllowCredentials)) {
          route
        }
      case (_, _) =>
        route
    })(context)
  }
}

爾後在發佈TMS服務的類中實現該特質:重寫虛方法,並在原先發布TMS服務的地方將原結果傳入cors方法:

override val corsAllowOrigins: List[String] = List("*")

override val corsAllowedHeaders: List[String] = List("Origin", "X-Requested-With", "Content-Type", "Accept", "Accept-Encoding", "Accept-Language", "Host", "Referer", "User-Agent")

override val corsAllowCredentials: Boolean = true

override val optionsCorsHeaders: List[HttpHeader] = List[HttpHeader](
`Access-Control-Allow-Headers`(corsAllowedHeaders.mkString(", ")),
`Access-Control-Max-Age`(60 * 60 * 24 * 20), // cache pre-flight response for 20 days
`Access-Control-Allow-Credentials`(corsAllowCredentials)
)

def service = cors {
    pathPrefix("map") {
        ...
    }
}

注意此處的cors方法,其自己是一個無參數方法,此處傳入的是Directive0的apply方法的參數,因此返回的仍然是Route類型。

上述兩段代碼實現的就是將TMS服務實現CORS服務。請求的域爲*,即任何域均可;請求頭爲"Origin", "X-Requested-With", "Content-Type", "Accept", "Accept-Encoding", "Accept-Language", "Host", "Referer", "User-Agent";並支持發送cookie等認證。完成上述改造後從新編譯運行geotrellis程序,刷新瀏覽器便可看到咱們想要的結果,效果以下:

3、總結

本文簡單記錄了將Cesium和Geotrellis結合中碰到的一個小問題,只是剛開始,後續估計問題會更多,無他法,只能咬着牙往下走。結果很簡單,折騰的時間卻很長,可是不折騰確定是不會有結果的,只能是想辦法加快折騰的速度。固然有些東西必定會記得你的折騰,好比腰椎頸椎固然還有大腦,在折騰中你會對總體框架更加熟悉。

Geotrellis系列文章連接地址http://www.cnblogs.com/shoufengwei/p/5619419.html

相關文章
相關標籤/搜索