GeoTrellis是一个基于Apache spark 的用于处理栅格数据的scala库和框架
GeoTrellis解决了三个核心的问题
GeoTrellis可以将数据(Tiff) 从本地,HDFS,S3中导入到本地,HDFS,Accumulo,HBASE,CASSANDRA,S3等,可选方式很多,而且是通过Spark集群并行处理,相当于GeoTrellis已经实现了分布式的瓦片切割。
GeoTrellis是针对大数据量栅格数据进行分布式空间计算的框架,所以无论采取何种操作,都是先将大块的数据切割成一定大小的小数据(瓦片),这是分治的思想,也是分布式计算的精髓。GeoTrellis的第一步就是要将数据切片(无论是存储在内存还是持久化),然而即使能力再大,在实际工作中也难以处理以下几种需求:
全球(大范围) 高分辨率遥感影像数据,数据量在TB级
局部地区数据更新
不同时间数据融合
可行的方案是执行更新操作或者分批处理,GeoTrellis框架中提供了数据的ETL接口,但是只能进行write操作,不能进行update操作,write操作会覆盖此图层中已有数据,边处数据无法凭借,导致数据缺失,所以只能分批处理写到不同的图层。
Catalog
DataSource
TileSetRasterLayer
AccumuloAttributeStore / AccumuloValueReader // 读取Accumulo 瓦片数据
RasterExtent
GeoTiffReader
SinglebandGeoTiff // 读取单波段
MultibandGeoTiff // 读取多波段
LayerReader // 读取集群中整层的瓦片信息
GeoTiff
SpatialKey //每幅瓦片在Accumulo中对应的瓦片Key值,可以通过Key值获取到对应的瓦片
// tileReader.readerSpatialKey, Tile.read(k)
SparkUtils
TileLayerMetadata
HadoopGeoTiffRDD //读取Tiff文件类
Reproject : 重投影
直接导入raster数据,通过ETL类生成金字塔,保存到Accumulo
单波段Tiff数据导入:
implicit val sc = SparkUtils.createSparkContext("ETL SinglebandIngest", new SparkConf(true)) Etl.ingest[ProjectedExtent, SpatialKey, Tile](args, ZCurveKeyIndexMethod) sc.stop()
多波段Tiff数据导入:
implicit val sc = SparkUtils.createSparkContext("ETL MultibandIngest", new SparkConf(true)) Etl.ingest[ProjectedExtent, SpatialKey, MultibandTile](args, ZCurveKeyIndexMethod) sc.stop()
GeoTrellis读取Tiff文件
HadoopGeoTiffRDD
读取矢量文件 -> 矢量栅格化 -> 走栅格流程
geotrellis.shapefile.ShapeFileReader.readSimpleFeatures(path)
得到 RasterExtent
Rasterizer.rasterizeWithValue(features, re, 100)
Rasterizer.foreachCellByGeometry(feature.geometry, re)
参考: https://www.cnblogs.com/shoufengwei/p/5422844.html
在application.conf 中配置一个catolog.json文件,其中记录DataSource信息,通过此信息获取数据
--layoutScheme
: tms/floating
floating切瓦片的时候只有0层 , 相当于用floating处理的就是原始数据只将数据切割成256*256的块,层为0(具体x、y编号不需要操心,geotrellis会自动计算)
tms会建立金字塔 ,用tms会将数据从最大层(此最大层根据数据的分辨率计算得出)切到第一层,调用的时候直接根据层进行调用
--pyramid
: 加上此参数在 layoutScheme = tms的时候会建立金字塔
-I path=file:/..
: 果此处的路径为文件,则单独导入此文件,如果为文件夹,则一次将整个路径导入,并且会自动拼接,瓦片不会有缝隙。geotrellis不但能够分布式瓦片切割,还能自动拼接 。
--layer
: 此参数用于区分不同的数据,取数据的时候根据此项区分不同的数据
参考: https://www.cnblogs.com/shoufengwei/p/5468068.html
LayerReader : 读取整层瓦片信息,然后根据偏移得到点值
val r = reader.read[SpatialKey, Tile, TileLayerMetadata[SpatialKey]](layerId)
val mapTransform = r.metadata.mapTransform
val key = r.metadata.mapTransform(point)
val dataValues: Seq[Double] = r.asRasters().lookup(key).map(_.getDoubleValueAtPoint(point))
val value = if(dataValues == null || dataValues.length <= 0) 0 else dataValues.head
ValueReader : 首先找到对应瓦片,然后偏移得到此点
val key = attributeStore.readMetadata[TileLayerMetadata[SpatialKey]](layerId).mapTransform(point)
val (col, row) = attributeStore.readMetadata[TileLayerMetadata[SpatialKey]](layerId).toRasterExtent().mapToGrid(point)
//读取瓦片
val tile: Tile = tileReader.reader[SpatialKey, Tile](layerId).read(key)
val tileCol = col - key.col * tile.cols
val tileRow = row - key.row * tile.rows
println(s"tileCol=${tileCol} tileRow = ${tileRow}")
tile.get(tileCol, tileRow)
多波段瓦片读取
val multiTile = tileReader.reader[SpatialKey, MultibandTile](LayerId(name, zoom)).read(key)
从多波段中获取单个波段
MultibandTile
multiTile.bands(bandNum)
直接读取整层数据
reader.read[SpatialKey, Tile, TileLayerMetadata[SpatialKey]](layerId)
为read方法添加一个LayerQuery对象
reader.read[SpatialKey, Tile, TileLayerMetadata[SpatialKey]](layerId, new LayerQuery[SpatialKey, TileLayerMetadata[SpatialKey]].where(Intersects(polygon)))
第二种方式的语法汤
reader.query[SpatialKey, Tile, TileLayerMetadata[SpatialKey]](layerId).where(Intersects(polygon)).result
HBase
Accumulo
参考: https://www.cnblogs.com/shoufengwei/p/5422844.html
IO(Http) ! Http.Bind(service, host, port)
需要使用以下语句系统遍自动的在host和相应的port上发起服务。
具体路由信息需要在service类中定义。service类需要继承Actor方法,并覆盖父类的receive方法。
参考:https://www.cnblogs.com/shoufengwei/p/5856323.html
geotrellis.spart.etl //处理ETL数据处理
ETL工作就是将数据切割成瓦片并镜像持久化,GeoTrellis支持数据放在内存中,或者放在Accumulo,HABSE等分布式数据库或者HDFS和普通文件系统中
geotrellis.Ingest 是调用Geotrellis内部数据导入的类,就是调用了ETL类进行数据自动上传
EtlConf是GeoTrellis中导入数据的配置类,需要创建EtlConf的实例,然后交给ETL即可完成数据导入,依赖Inputjson,output.json和 backend-profiles.json文件
输入:input.json //ETL输入
输出:output.json //ETL输出
保存:backend.json //数据保存
基于瓦片:
渲染为JPG:renderJpg
渲染为PNG: renderPng
颜色表: ColorMap
Options[classBoundaryType noDataColor fallbackColor strict ]
ColorMap.fromQuantileBreaks
参考:https://www.cnblogs.com/shoufengwei/p/5753753.html
获取polygon对应瓦片:val masked = raster.mask(polygon)
合并瓦片:val stitch = masked.stitch val tile = stitch.tile
参考:https://www.cnblogs.com/shoufengwei/p/5753753.html
val rep = tile.reproject(extent, srcCRS, dstCRS, method).tile val res = rep.convert(cellType)
瓦片裁剪
tile.crop(startcol, startrow, endcol, endrow)
FileCOGLayerWriter COGLayer
COGLayerMetadata
FileCOGValueReader
通过gdal添加时间头
gdal_edit -mo TIFFTAG_DATETIME="time" yourtiff.tif
通过GeoTrellis添加时间头信息
val tiff = SinglebandGeoTiff(path) tiff.tags.headTags + (Tags.TIFFTAG_DATETIME -> time) val newtiff = new SinglebandGeoTiff(tiff.tile, tiff.extent, tiff.crs, Tags(map, tiff.tags.bandTags), tiff.options) newtiff.write(newTiffPath)
改变导入参数
普通tiff数据导入的时候条用ETL类的方式:
Etl.ingest[ProjectedExtent, SpatialKey, Tile](args)
时间序列数据导入时:
Etl.ingest[TemporalProjectedExtent, SpaceTimeKey, Tile](args)
主要添加时间支持,ProjectedExtent 变为 TemporalProjectExtent,SpatialKey变为SpaceTimeKey,如果是多波段的需要将Tile替换为 MultibandTile。
改变导入参数
修改input.json中只需要将format由geotiff改为temporal-geotiff;output.json中需要将keyIndexMethod中的内容改成如下方式:
"keyIndexMethod":{
"type":"zorder",
"temporalResolution": 86400000,
"timeTag":"TIFFTAG_DATETIME",
"timeFormat":"yyyy:MM:dd HH:mm:ss"
}
其中temporalResolution表示时间精度,理论上来说,设置此值表示当你根据时间查询的时候在这个精度范围内的数据都应该能够查询出来
到此,时间序列数据已经导入到accumulo中。
获取对应时间序列瓦片
前台将请求时间,瓦片的x,y,z传入后台,根据这四个参数查询,相较普通查询,多添加了饿时间条件
val dt = DateTimeFormat.forPattern("yyyy:MM:dd HH:mm:ss").parseDateTime(time)
val key = SpaceTimeKey(x, y, dt)
val layerId = LayerId(name, zoom)
respondWithMediaType(MediaTypes.`image/png`) {
val result = {
val tile = tileReader.reader[SpaceTimeKey, Tile](layerId).read(key)
tile.renderPng.bytes
}
complete(result)
}
name标示数据导入是存放的名字,tileReader为AccumuloValueReader实例
这样就能将用户请求的时间以及x、y、z瓦片数据渲染之后发送到前台,这里还需要强调的是Geotrellis中时间处理采用joda开源框架 。
坡度:Slope 读取瓦片,调用tile.slope 即可实现坡度
缓冲区: Buffer
栅格化几何:
Rasterizer.foreachCellByGeometry(geom, rasterExtent)(f)
ArrayTile(array, rasterExtent.cols, rasterExtent.rows)
高程信息:tile.histogram
tile.histogram.foreach { (key, value) => { map(key.toDouble) = map(key.toDouble) + value }
weishoufeng: https://blog.csdn.net/luoye4321/article/category/9294349
https://blog.csdn.net/qq_32432081?t=1
https://www.cnblogs.com/shoufengwei/p/5619419.html