学习GeoTrellis是我最难的一段时间,资料很少,群也很少。靠着自己一点一点摸索,稍稍能做出点结果。分享给大家。
在这里也是很感谢网上的各位大佬,没有他们的帮助,也就没有今天的这篇文章。我也只不过踩在巨人的肩膀上,做了一些简单的整理。
我并没有使用官方的sbt,而是使用了maven进行搭建。需要注意的是,需要IDEA安装Scala并在maven中添加框架支持,将Scala添加其中。
首先利用IDEA构建一个Maven项目。pom.xml 内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>GeoTrellis</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<scala.version>2.11.8</scala.version>
</properties>
<pluginRepositories>
<pluginRepository>
<id>scala-tools.org</id>
<name>Scala-Tools Maven2 Repository</name>
<url>http://scala-tools.org/repo-releases</url>
</pluginRepository>
</pluginRepositories>
<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.specs</groupId>
<artifactId>specs</artifactId>
<version>1.2.5</version>
<scope>test</scope>
</dependency>
<!-- 添加依赖 -->
<dependency>
<groupId>org.locationtech.geotrellis</groupId>
<artifactId>geotrellis-spark_2.11</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.locationtech.geotrellis</groupId>
<artifactId>geotrellis-raster_2.11</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.locationtech.geotrellis</groupId>
<artifactId>geotrellis-spark-etl_2.11</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.locationtech.geotrellis</groupId>
<artifactId>geotrellis-cassandra_2.11</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.locationtech.geotrellis</groupId>
<artifactId>geotrellis-proj4_2.11</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.locationtech.geotrellis</groupId>
<artifactId>geotrellis-util_2.11</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.locationtech.geotrellis</groupId>
<artifactId>geotrellis-macros_2.11</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_2.11</artifactId>
<version>2.4.17</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-http-core_2.11</artifactId>
<version>10.0.3</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-http_2.11</artifactId>
<version>10.0.3</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-http-spray-json_2.11</artifactId>
<version>10.0.3</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.11</artifactId>
<version>2.3.0</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/scala</directory>
<excludes>
<exclude>**/*.scala</exclude>
</excludes>
</resource>
</resources>
<sourceDirectory>src/main/scala</sourceDirectory>
<plugins>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
<configuration>
<scalaVersion>${scala.version}</scalaVersion>
<args>
<arg>-target:jvm-1.8</arg>
</args>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<configuration>
<downloadSources>true</downloadSources>
<buildcommands>
<buildcommand>ch.epfl.lamp.sdt.core.scalabuilder</buildcommand>
</buildcommands>
<additionalProjectnatures>
<projectnature>ch.epfl.lamp.sdt.core.scalanature</projectnature>
</additionalProjectnatures>
<classpathContainers>
<classpathContainer>org.eclipse.jdt.launching.JRE_CONTAINER</classpathContainer>
<classpathContainer>ch.epfl.lamp.sdt.launching.SCALA_CONTAINER</classpathContainer>
</classpathContainers>
</configuration>
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
<configuration>
<scalaVersion>${scala.version}</scalaVersion>
</configuration>
</plugin>
</plugins>
</reporting>
</project>
因为在本地测试,因此不用搭建Spark集群也可以,但是需要搭建一个HDFS集群,用来存放数据
在scala文件夹下创建application.conf
创建该配置文件,是为了方便配置管理,各位亲也可以不创建,直接卸载类的变量中。
内容如下:
server {
port = 8080
file.path = "I:/0practiceArry/GeoTrellis/out/beijing"
interface = "0.0.0.0"
}
spark {
master = "local[*]",
appName = "GeoTrellis WMTS"
}
geoTrellis {
hadoop.path = "hdfs://HBase105:9000/GeoTrellis/Beijing.tif"
outFile.path = "I:/0practiceArry/GeoTrellis/out/beijing"
instance.fileName = "landsat"
instance.pngFileName = "pngContainer"
isPNG = true
zoom = 10
tileSize = 256
}
- server中配置的是akka服务端口和ip,以及切片文件放置的位置
- spark 中配置的spark的master和应用名称
- geoTrellis 配置的是:
- 输入文件路径 —— hadoop.path
- 输出文件路径 —— outFile.path
- 生成切片文件夹名称 —— instance.fileName
- 生成切片png文件夹名称 —— instance.pngFileName
- 是否生成png瓦片 —— isPNG
- 设置最大切片等级 —— zoom
- 设置瓦片大小 —— tileSize
- 在这里需要注意的是,
- Beijing.tif 与 Beijing.tiff 没有区别,都可以识别
- 影像500M的时候,如果内存没有64以上,请不要把tileSize 设置为128 (我试过,64G内存都撑不住)
编写切片代码
本人Scala并不算熟悉,因此有很多地方都有所借鉴。一些个人理解,还请各位大佬指导。
导入包
这个部分我拿出来说,是因为很多地方都需要(隐式?)导入,所以直接先贴出来
import java.io.File
import com.typesafe.config.{Config, ConfigFactory}
import geotrellis.proj4._
import geotrellis.raster._
import geotrellis.raster.render._
import geotrellis.raster.resample._
import geotrellis.spark._
import geotrellis.spark.io.file._
import geotrellis.spark.io.hadoop._
import geotrellis.spark.io._
import geotrellis.spark.io.index._
import geotrellis.spark.pyramid._
import geotrellis.spark.tiling._
import org.apache.spark.rdd.RDD
import org.apache.spark._
import geotrellis.vector._
import org.apache.spark.rdd._
获取配置文件中的值
val config: Config = ConfigFactory.load()
// 输入文件路径
private val hadoopInPath: String = config.getString("geoTrellis.hadoop.path")
// 输出文件路径
private val outFilePath: String = config.getString("geoTrellis.outFile.path")
// spark Master
private val master: String = config.getString("spark.master")
// spark 应用名称
private val appName: String = config.getString("spark.appName")
// 切片等级
private val setZoom: Int = config.getInt("geoTrellis.zoom")
private val tileSize: Int = config.getInt("geoTrellis.tileSize")
// 是否生成PNG
private val isPNG: Boolean = config.getBoolean("geoTrellis.isPNG")
// 实例名称
private val instanceFileName: String = config.getString("geoTrellis.instance.fileName")
// 图片实例名称
private val InstancePNGFileName: String = config.getString("geoTrellis.instance.pngFileName")
创建Spark配置
private val sparkConf: SparkConf = new SparkConf()
.setAppName(appName)
.setMaster(master)
.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
.set("spark.kryo.registrator", "geotrellis.spark.io.kryo.KryoRegistrator")
val sparkContext = new SparkContext(sparkConf)
main函数
def main(args: Array[String]): Unit = {
try {
run(sparkContext)
} finally {
sparkContext.stop()
}
}
主函数 run
def run(implicit sparkContext: SparkContext): Unit = {
// 构建GeoRDD
// ProjectedExtent类型值必须赋予,否则创建元数据信息的时候会报找不到
val geoRDD: RDD[(ProjectedExtent, Tile)] = sparkContext.hadoopGeoTiffRDD(hadoopInPath)
// 创建元数据信息
val (_, rasterMetaData) = TileLayerMetadata.fromRDD(geoRDD, FloatingLayoutScheme(512))
// 创建切片RDD
val tiled: RDD[(SpatialKey, Tile)] = geoRDD.
tileToLayout(rasterMetaData.cellType, rasterMetaData.layout, Bilinear)
// 请特别注意,此处咨询过 GeoTrellis 的作者,请不要随意的使用repartition!!
// repartition 会将所有的数据进行重新分区,增加计算量!
// .repartition(100)
// 设置投影和瓦片大小
val layoutScheme: ZoomedLayoutScheme = ZoomedLayoutScheme(WebMercator, tileSize = tileSize)
val (maxZoom, reprojected) = TileLayerRDD(tiled, rasterMetaData)
.reproject(WebMercator, layoutScheme, Bilinear)
// 创建输出存储区
val attributeStore = FileAttributeStore(outFilePath)
val writer = FileLayerWriter(attributeStore)
val colorRender = ColorRamps.LightToDarkSunset
Pyramid.upLevels(reprojected, layoutScheme, setZoom, Bilinear) { (rdd, z) =>
val landsatId = LayerId(instanceFileName, z)
// 写出图层
writer.write(landsatId, rdd, ZCurveKeyIndexMethod)
// 如果需要生成PNG则生成PNG
if (isPNG) {
renderPNG(sparkContext, z, colorRender)
}
}
}
Pyramid.upLevels 函数是从最大切片等级开始进行运算的(个人理解哈,因为Spark运行时,是从最大等级任务开始执行的)
渲染PNG
def renderPNG(implicit sparkContext: SparkContext, zoom: Int, colorRender: Any): Unit = {
val path = outFilePath + "/" + instanceFileName
val file = new File(path)
// 如果不存在,则直接停止运行
if (!file.exists()) {
sparkContext.stop()
}
// 创建png结果文件夹
val pngOutFileName = outFilePath + "/" + InstancePNGFileName
createFolder(pngOutFileName)
// 获取文件名
val files = file.listFiles()
files.foreach(item => {
val zoomLevel = item.getName
// 创建级别文件夹
val zoomLevelFileName = pngOutFileName + "/" + zoomLevel
createFolder(zoomLevelFileName)
// 读取图层
val reader = FileLayerReader(outFilePath)
val layerId = LayerId(instanceFileName, zoomLevel.toInt)
/*
* 应该导入 import geotrellis.spark.io._
* */
val layers: RDD[(SpatialKey, Tile)] with Metadata[TileLayerMetadata[SpatialKey]] =
reader.read[SpatialKey, Tile, TileLayerMetadata[SpatialKey]](layerId)
// GeoTrellis 与Google 的 ZXY相同
layers.foreach(layer => {
val key = layer._1
val tile = layer._2
// 定义x
val outPathPNGYFileName = zoomLevelFileName + '/' + key.col
createFolder(outPathPNGYFileName)
// 定义y
val pngFileName = outPathPNGYFileName + "/" + key.row + ".png"
colorRender match {
case ramp: ColorRamp =>
tile.renderPng(ramp).write(pngFileName)
case ramp: ColorMap =>
tile.renderPng(ramp).write(pngFileName)
}
})
})
}
/*
* 创建文件夹
* */
def createFolder(folderName: String): File = {
val file = new File(folderName)
// 如果没有图片输出文件夹,则创建
if (!file.exists()) {
file.mkdirs()
}
file
}
创建PNG的时候,需要手动创建文件夹,GeoTrellis 的切片规则与Google相同, ZXY
通过
reader.read[SpatialKey, Tile, TileLayerMetadata[SpatialKey]](layerId)
获取指定级别下的切片数据,他是个RDD算子,因此此过程依然需要Spark环境。
在定义颜色的时候,需要注意的是,ColorMap 是值对值的一一对应关系映射,也就是说,一个值是一个颜色,而不是一个范围(唯一值渲染),而ColorRamp是一个色彩范围(色带)
结果
如果生成PNG的话,则目录结构如下:
在pngContainer中是切片图片,可以直接利用web 容器进行发布,采用 XYZ的方式进行加载。
创建PNG的时候,需要手动创建文件夹,GeoTrellis 的切片规则与Google相同, ZXY
通过
reader.read[SpatialKey, Tile, TileLayerMetadata[SpatialKey]](layerId)
获取指定级别下的切片数据,他是个RDD算子,因此此过程依然需要Spark环境。
在定义颜色的时候,需要注意的是,ColorMap 是值对值的一一对应关系映射,也就是说,一个值是一个颜色,而不是一个范围(唯一值渲染),而ColorRamp是一个色彩范围(色带)
结果
如果生成PNG的话,则目录结构如下:
在pngContainer中是切片图片,可以直接利用web 容器进行发布,采用 XYZ的方式进行加载。
在landsat中的文件,需要利用akka或者是Spring boot 进行后台处理后,才能渲染给前端。