作任何事情都不是想象中的那么简单。很久没有更新技术博客了,跟最近瞎忙有很大关系,虽然说是瞎忙也抽空研究了些技术。css
主要是前端渲染,像原生的WebGL和Cesium。WebGL写了几篇博客,自我感受还能够。Cesium是一个封装好的WEB端3D Earth框架,有了WebGL的基础以后切换到Cesium按理说一切应该是瓜熟蒂落,简单的测试了几个功能以后发现确实很是好,简单的几行代码就能够实现Google Earth的功能,固然Google Earth重要的绝对不是他的渲染框架。html
前期作了不少Geotrellis的工做,那么我就想着能不能把Geotrellis发布的TMS加载到Cesium中来,原本这是很简单的嘛,之前是在leaft-let中显示,如今就是换一个地方显示而已,而且Cesium已经调通。说干就干,结果怎么着,前天晚上整到四点,昨天折腾了几个小时竟然一直不出图,因此我说任何看似简单的事情其实都不简单,下面就让我娓娓道来。前端
介绍以前仍是来简单介绍一下Cesium,固然若是后面继续对此框架进行研究的话可能也会多写几篇关于此框架的博客。git
官网地址:https://cesiumjs.org/,Github地址:https://github.com/AnalyticalGraphicsInc/cesium。程序员
其功能简单明了,固然也很强大,基础教程能够参考http://blog.csdn.net/UmGsoil/article/category/7005304,固然官方文档更好。github
无需考虑这么复杂,从简单里说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便可。
在上述代码下方添加以下代码:
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上提了问,等了半天也无人回复,只好闷闷不乐的去睡了。
今天中午小睡片刻,起床后收到一封邮件,赶忙打开看了一下,是Github的回复邮件,喜出望外,结果一看内容原来是告诉我不要在Issue中发布提问,告诉了我Google的提问列表(https://groups.google.com/forum/#!msg/cesium-dev/RfAlZZkPBaM/xGOK01trAwAJ;context-place=forum/cesium-dev),整我的当时就很差了,既然这样只有上去瞅瞅,打开简单一搜索,竟然有现成的,问题描述跟个人如出一辙,解决方案是添加CORS。
其实我以前折腾到四点多的时候脑子里就有这个意识,必定是我发布的TMS缺乏了某个东西(或者是某个东西与Cesium的要求不一致),致使Cesium没法正常显示个人瓦片,因此一看到这个我就亢奋了,程序员的直觉告诉我这确定就是我要找的东西。
因此问题就来了,看样子我要在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程序,刷新浏览器便可看到咱们想要的结果,效果以下:
本文简单记录了将Cesium和Geotrellis结合中碰到的一个小问题,只是刚开始,后续估计问题会更多,无他法,只能咬着牙往下走。结果很简单,折腾的时间却很长,可是不折腾确定是不会有结果的,只能是想办法加快折腾的速度。固然有些东西必定会记得你的折腾,好比腰椎颈椎固然还有大脑,在折腾中你会对总体框架更加熟悉。
Geotrellis系列文章连接地址http://www.cnblogs.com/shoufengwei/p/5619419.html