GeoSpark

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的数据的分区方案匹变为自己的分区方案。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值