GeoMesa Spark
1、GeoMesa Spark最底层为geomesa-spark-jts模块
2、geomesa-spark-core模块是spark core的扩展,支持支持geotools的Query,生成系列化好的simplefeature类型的rdd
3、geomesa-spark-sql模块允许使用sql方式进行查询,会将sql语句转换为Query对象进行查询
Spark JTS
Spark JTS模块提供用户自定义函数(udf)和用户自定义类型(udt),
导入jts包
<dependency>
<groupId>org.locationtech.geomesa</groupId>
<artifactId>geomesa-spark-jts_2.11</artifactId>
// version, etc.
</dependency>
例:
import org.locationtech.jts.geom._
import org.apache.spark.sql.types._
import org.locationtech.geomesa.spark.jts._
import spark.implicits._
val schema = StructType(Array(
StructField("name",StringType, nullable=false),
StructField("pointText", StringType, nullable=false),
StructField("polygonText", StringType, nullable=false),
StructField("latitude", DoubleType, nullable=false),
StructField("longitude", DoubleType, nullable=false)))
val dataFile = this.getClass.getClassLoader.getResource("jts-example.csv").getPath
val df = spark.read
.schema(schema)
.option("sep", "-")
.option("timestampFormat", "yyyy/MM/dd HH:mm:ss ZZ")
.csv(dataFile)
val alteredDF = df
.withColumn("polygon", st_polygonFromText($"polygonText"))
.withColumn("point", st_makePoint($"latitude", $"longitude"))
从地理空间中直接创建DF
import spark.implicits._
val point = new GeometryFactory().createPoint(new Coordinate(3.4, 5.6))
val df = Seq(point).toDF("point")
配置
要想启用配置,需要:
org.locationtech.geomesa.spark.jts._
然后创建SparkSession
并且使用SparkSession.withJTS
或者
直接使用initJTS(SQLContext)
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.SQLContext
import org.locationtech.geomesa.spark.jts._
val spark: SparkSession = SparkSession.builder() // ... initialize spark session
spark.withJTS
UDT和UDF
1、UDT
Spark JTS模块可以接受表示几何对象的类,并将它们注册为SparkSQL中的用户定义类型(udt)。例如,Geometry 类会被注册为GeometryUDT。
可接受的类如下:
GeometryUDT
PointUDT
LineStringUDT
PolygonUDT
MultiPointUDT
MultiLineStringUDT
MultiPolygonUDT
GeometryCollectionUDT
2、UDF
select * from chicago where st_contains(st_makeBBOX(0.0, 0.0, 90.0, 90.0), geom)
其中st_contains 和st_makeBBOX就是自定义函数
UDF也可以给DF的API使用,比如:
import org.locationtech.geomesa.spark.jts._
import spark.implicits. _
chicagoDF.where(st_contains(st_makeBBOX(0.0, 0.0, 90.0, 90.0), $"geom"))
输出GeoJson
import org.locationtech.geomesa.spark.jts.util.GeoJSONExtensions._
val df : DataFrame = // Some data frame
val geojsonDf = df.toGeoJSON //将DF转为GeoJson
只给出schema,转换器就可以推断哪个字段包含几何图形,但是对于多个几何字段,它默认使用第一个字段。可以通过在schema中提供所需几何图形的索引(从0开始)来覆盖此行为。例如,如果所需的几何图形是模式的第三个字段,则使用df. retailojson(2)。
val geoJsonString = geojsonDF.collect.mkString("[",",","]")
Spark Core
Spark Core底层使用的是RDD(simplefeature类型)
例:
通过对GeoMesa数据存储的地理空间查询并创建RDD:
// DataStore params to a hypothetical GeoMesa Accumulo table
val dsParams = Map(
"accumulo.instance.id" -> "instance",
"accumulo.zookeepers" -> "zoo1,zoo2,zoo3",
"accumulo.user" -> "user",
"accumulo.password" -> "*****",
"accumulo.catalog" -> "geomesa_catalog",
"geomesa.security.auths" -> "USER,ADMIN")
// set SparkContext
val conf = new SparkConf().setMaster("local[*]").setAppName("testSpark")
val sc = SparkContext.getOrCreate(conf)
// create RDD with a geospatial query using GeoMesa functions
val spatialRDDProvider = GeoMesaSpark(dsParams)
val filter = ECQL.toFilter("CONTAINS(POLYGON((0 0, 0 90, 90 90, 90 0, 0 0)), geom)")
val query = new Query("chicago", filter)
val resultRDD = spatialRDDProvider.rdd(new Configuration, sc, dsParams, query)
resultRDD.collect
// Array[org.opengis.feature.simple.SimpleFeature] = Array(
// ScalaSimpleFeature:4, ScalaSimpleFeature:5, ScalaSimpleFeature:6,
// ScalaSimpleFeature:7, ScalaSimpleFeature:9)
配置
在geomesa-spark-core中**,通过SpatialRDDProvider去访问数据**,
在运行时添加额外的jar包:
--jars file://path/to/geomesa-accumulo-spark-runtime_2.11-$VERSION.jar
Simple Feature 的序列化
要想实现simplefeature的序列化,必须指定一个Kryo序列化注册器
本地模式下不需要指定
在spark Conf中指定:
conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
conf.set("spark.kryo.registrator", classOf[GeoMesaSparkKryoRegistrator].getName)
用法
val spatialRDDProvider = GeoMesaSpark(params) //通过GeoMesaSpark创建spatialRDDProvider
// 用于创建SpatialRDDProvider 的参数
val params = Map(
"param1" -> "foo",
"param2" -> "bar")
// 创建查询,you或没有过滤器的两种情况
val query = new Query("foo")
// val query = new Query("foo", ECQL.toFilter("name like 'A%'"))
// 获取rdd
val rdd = GeoMesaSpark(params).rdd(new Configuration(), sc, params, query)
保存features
GeoMesaSpark(params).save(rdd, params, "gdelt")
Spatial RDD Providers
HBase RDD Provider(其他的自己去看官网)
存在于geomesa-hbase-spark模块中
提供GeoMesa HBaseDataStore的读写功能
连接到hbase必须需要hbase-site.xml,设置如下:
$ spark-shell --jars file:///opt/geomesa/dist/spark/geomesa-hbase-spark-runtime_2.11-${VERSION}.jar,file:///usr/lib/hbase/conf/hbase-site.xml
使用方式:
val params = Map("hbase.zookeepers" -> "zoo1,zoo2,zoo3", "hbase.catalog" -> "geomesa")
val query = new Query("gdelt")
val rdd = GeoMesaSpark(params).rdd(new Configuration(), sc, params, query)
SparkSQL
引入:
<dependency>
<groupId>org.locationtech.geomesa</groupId>
<artifactId>geomesa-spark-sql_2.11</artifactId>
// version, etc.
</dependency>
例如:
// DataStore params to a hypothetical GeoMesa Accumulo table
val dsParams = Map(
"accumulo.instance.id" -> "instance",
"accumulo.zookeepers" -> "zoo1,zoo2,zoo3",
"accumulo.user" -> "user",
"accumulo.password" -> "*****",
"accumulo.catalog" -> "geomesa_catalog",
"geomesa.security.auths" -> "USER,ADMIN")
// Create SparkSession
val sparkSession = SparkSession.builder()
.appName("testSpark")
.config("spark.sql.crossJoin.enabled", "true")
.master("local[*]")
.getOrCreate()
// Create DataFrame using the "geomesa" format
val dataFrame = sparkSession.read
.format("geomesa")
.options(dsParams)
.option("geomesa.feature", "chicago")
.load()
dataFrame.createOrReplaceTempView("chicago")
// Query against the "chicago" schema
val sqlQuery = "select * from chicago where st_contains(st_makeBBOX(0.0, 0.0, 90.0, 90.0), geom)"
val resultDataFrame = sparkSession.sql(sqlQuery)
resultDataFrame.show
/*
+-------+------+-----------+--------------------+-----------------+
|__fid__|arrest|case_number| dtg| geom|
+-------+------+-----------+--------------------+-----------------+
| 4| true| 4|2016-01-04 00:00:...|POINT (76.5 38.5)|
| 5| true| 5|2016-01-05 00:00:...| POINT (77 38)|
| 6| true| 6|2016-01-06 00:00:...| POINT (78 39)|
| 7| true| 7|2016-01-07 00:00:...| POINT (20 20)|
| 9| true| 9|2016-01-09 00:00:...| POINT (50 50)|
+-------+------+-----------+--------------------+-----------------+
*/
配置:
使用SpatialRDDProvider (和spark core一样)
将多个数据进行join时,需要将spark.sql.crossJoin.enabled设置为true
val spark = SparkSession.builder().
// ...
config("spark.sql.crossJoin.enabled", "true").
// ...
getOrCreate()
cross-jion效率低,最好把一个表加入内存
使用:
// 创建DF
val dataFrame = sparkSession.read
.format("geomesa")
.options(dsParams)
.option("geomesa.feature", typeName)
.load()
创建临时表:
dataFrame.createOrReplaceTempView("chicago")
sql语句查询:
val sqlQuery = "select * from chicago where st_contains(st_makeBBOX(0.0, 0.0, 90.0, 90.0), geom)"
val resultDataFrame = sparkSession.sql(sqlQuery)
手动注册UDT和UDF:
SQLTypes.init(sparkSession.sqlContext)
写数据:
dataFrame.write.format("geomesa").options(dsParams).option("geomesa.feature", "featureName").save()
这会自动将RDD[Row]转换为RDD[SimpleFeature]并并行地写入数据存储。要实现这一点,featureName必须已经存在于数据存储中。
当写回特性时,可以通过_fid_列指定feature ID:
dataFrame
.withColumn("__fid__", $"custom_fid")
.write
.format("geomesa")
.options(dsParams)
.option("geomesa.feature", "featureName")
.save
内存索引
如果数据量很小,可以告诉GeoMesa SparkSQL将RDDs持久化到内存中,并利用CQEngine作为内存索引数据存储。在创建数据时添加option(“cache”、“true”),此时将在每个属性上放置一个索引,fid和geometry除外。对于基于geometry的索引,添加option(“indexGeom”、“true”)。对该关系的查询将自动命中缓存的RDD并查询位于每个分区上的内存数据存储,这将产生显著的速度提升。
如果对数据有了解的话,可以自己确定哪些数据适合存入索引,通过option(“query”, “dtg AFTER 2016-12-31T23:59:59Z”)设置。
空间分区,加快join
可以对空间进行分区来加快查询
通过option(“spatial”, “true”)选项可以是相邻的空间分到相同的分区,默认情况下,数据会被分配到N × N的网格
共有4中分配方式,每个都可以通过命名来确定:option(“strategy”, strategyName)
EQUAL --------- 计算数据的边界并将其划分为大小相同的NxN网格,其中N = sqrt(分区数)
WEIGHTED ---------- 和EQUAL类似,但确保每个网格单元中沿每个轴的数据比例相等。
EARTH --------- 和EQUAL类似,但使用整个地球作为边界,而不是根据数据计算它们。
RTREE ----------- 基于数据样本构造一个r树,并使用边界矩形的子集作为分区的区分线
空间划分的优点:
1.查询时可直接定位到确定的分区,从而跳过扫描分区的开销,而扫描分区肯定不包含所需的数据。
2.如果使用相同的方案对两个数据集进行分区,导致两个数据集具有相同的分区轨迹线,那么空间连接可以使用分区轨迹线作为连接中的键。这大大减少了完成join所需的比较数量。
其他选项:
option(“partitions”, “n”) ------- 指定底层RDDs的分区数(覆盖默认分区数)
option(“bounds”, “POLYGON in WellKnownText”) ------- 限定WEIGHTED 和 EQUAL的边界,所有不在这些范围内的数据都将放在一个单独的分区中
option(“cover”, “true”) -------- 由于只有EQUAL和EARTH分区策略才能保证分区轨迹线在不同数据集中是相同的,因此具有此选项的数据将会强制与它join的数据的分区方案匹变为自己的分区方案。