Geomesa学习3 - 数据操作

本章主要介绍GeoMesa的操作流程,包括数据写入、索引创建、数据查询等,GeoMesa前期的安装部署见GeoMesa安装

一、 Geomesa主要常用类

描述
SimpleFeatureBuilder 用来创建feature,feature是geomesa完整数据的实体类,一个feature对应一条数据
SimpleFeature 空间要素的抽象表达,默认含有geometry字段,根据定义的SimpleFeatureType依次传入相应类型的数据,最后设置Feature的ID即可。
SimpleFeatureType 要素元数据描述,包括字段名、类型、空间参考等,类似表结构中不同字段的类型等信息。
DataStore 要素数据集,定义了用户操作数据的接口。DataStore是数据的核心访问模型,存储了数据集的名称、数据结构与类型、数据访问源等信息,类似一种数据元信息的存储集合,用于定义和描述数据的基本信息。
FeatureSource 用于数据查询
FeatureStore FeatureSource子类,增加数据更新功能
SimpleFeatureCollection 数据要素集合,按需加载
Query 数据查询类,封装了各项查询条件

二、 数据写入

2.1 数据表示形式

原始数据样式——人数据:

id name 出生时间 出生地经度 出生地维度
1 Tom 1536041936000 1.6432 -19.123

通过引用GeoJson库,在Web APi中可以用下面的方法将上述数据定义成一个点数据:

point_feature = Feature(id="1",geometry=Point((1.6432, -19.123)), 
properties={"id":"1","dtg":1536041936000,"name": "Tom"}) 

GeoTools API中可以通过SimpleFeatureBuilder构造一个人的SimpleFeature

SimpleFeatureBuilder builder = new SimpleFeatureBuilder(sft);
builder.set("peopleId", "1");
builder.set("geom", "POINT(1.6432, -19.123)");
builder.set("dtg", 1536041936000);
SimpleFeature point_feature = builder.buildFeature("1");

2.2 写入流程(GeoJson)

使用GeoTools进行数据写入的基本流程如下:

1.  构建GeoMesa连接,获取DataStore对象
2.  构建数据的属性描述信息,包括属性字段名、字段类型、是否对属性建索引
3.  根据属性描述信息创建SimpleFeatureType,即创建数据的schema
4.  设置时空索引的时间间隔、空间索引字段、时空索引精度
5.  DataStore根据SimpleFeatureType真正创建数据表的schema
6.  使用SimpleFeatureBuilder对象创建feature,并写入数据

在写入数据时,可以从csvtxt文件中写入,只是在写入之前要先把文件中的数据转换为WKT格式,WKT示例如下图。

WKT.png

2.3 代码示例

/**
 * 向Geomesa-Hbase中导入数据
 * */
public static void importData() throws Exception{
    Map params = new HashMap();
    params.put("hbase.zookeepers", "10.1.1.1");
    params.put("hbase.catalog", "nicole_test");

    //构建GeoMesa连接--DataStroe
    DataStore dataStore = DataStoreFinder.getDataStore(params);

    //声明简单类型SimpleFeatureType,用于操作GeoMesa中数据属性,如实行索引、属性描述等存放在该对象中
    SimpleFeatureType sft = null;

    //构建属性描述符,接收String类型的属性描述
    //属性字段描述方法-- 字段名:字段类型,字段名:字段类型,*地理信息字段名:地理信息字段类型:空间引用标识符=标识符编码
    //若对某一个属性做索引,则 -- 字段名:字段类型:index=true,
    StringBuilder attributes = new StringBuilder();
    attributes.append("plantNo:String:index=true,");
    attributes.append("color:String,");
    attributes.append("dateAttr:Date,");
    attributes.append("*geom:LineString:srid=4326");
   // srid是GIS当中的一个空间参考标识符。而此处的srid=4326表示这些数据对应的WGS 84空间参考系统
    //使用SimpleFeatureTypes创建SimpleFeatureType,string-data是自己定义的schema的名称
    sft = SimpleFeatureTypes.createType("string-data", attributes.toString());
    //设置xz3索引时间间隔为天
    sft.getUserData().put("geomesa.xz3.interval", "day");
    //设置时空索引时间字段为date
    sft.getUserData().put("geomesa.index.dtg", "dateAttr");
    //设置索引精度
    sft.getUserData().put("geomesa.xz.precision", 10);

    //DataStore通过SimpleFeatureType真正创建表的schema
    SimpleFeatureType sftCheck = dataStore.getSchema("string-data");
    if(sftCheck == null){
        dataStore.createSchema(sft);
    } else{
        System.out.println("schema exists !");
    }

    //声明SimpleFeatureBuilder对象,用于创建feature,一个feature对应一条数据
    SimpleFeatureBuilder builder = new SimpleFeatureBuilder(sft);
    //构造一条数据
    String plantNo = "A00001";
    String geomStr = "LINESTRING(120.777 20.444,120.333 30.188,120.3211 30.1902)";
    Date date = new Date(System.currentTimeMillis());

    builder.set("plantNo", plantNo);
    builder.set("geom", geomStr);
    builder.set("dateAttr", date);

    //设置FeatureID,该字段要求具有唯一性,推荐使用UUID
    String featureId = plantNo + 1;

    SimpleFeature simpleFeature = builder.buildFeature(featureId);


    //数据写入
    //通过DataStore获取featureWriter,设置事务自动提交
    FeatureWriter writer = dataStore.getFeatureWriterAppend("string-data", Transaction.AUTO_COMMIT);
    //服务端下一次要写入的数据feature,并为其设置我们的数据
    SimpleFeature toWrite = writer.next();
    toWrite.setAttributes(simpleFeature.getAttributes());
    ((FeatureIdImpl) toWrite.getIdentifier()).setID(simpleFeature.getID());
    //设置是否使用呢客户端提供的featureID作为 FID
    toWrite.getUserData().put(Hints.USE_PROVIDED_FID, Boolean.TRUE);
    toWrite.getUserData().putAll(simpleFeature.getUserData());
    //真正的执行写操作
    writer.write();
    writer.close();
}

数据写入之后在hbase中会生成五张表:

nicole_test                                                                                                                                 
nicole_test_string_2ddata_attr_v5                                                                                                           
nicole_test_string_2ddata_id                                                                                                                
nicole_test_string_2ddata_xz2                                                                                                               
nicole_test_string_2ddata_xz3   //因为导入的数据是线,所以这里会生成XZ的表

Geomesa存储到hbase中的数据是编码之后的,无法在hbase中查看到具体的数据样式。 通过java API查看每个表中的rowkey
nicole-test

Rowkey: string-data~attributes
Rowkey: string-data~id
Rowkey: string-data~stats-date
Rowkey: string-data~table.attr.v5
Rowkey: string-data~table.id.v1
Rowkey: string-data~table.xz2.v1
Rowkey: string-data~table.xz3.v1

nicole_test_string_2ddata_attr_v5:
Rowkey: A00001 F] !2I'9465d3a8-8f2d-4949-b0e8-cd20589682fa
nicole_test_string_2ddata_id:
Rowkey: 9465d3a8-8f2d-4949-b0e8-cd20589682fa
nicole_test_string_2ddata_xz2:
Rowkey: �W9465d3a8-8f2d-4949-b0e8-cd20589682fa
nicole_test_string_2ddata_xz3:
Rowkey: F] !2I'9465d3a8-8f2d-4949-b0e8-cd20589682fa

三、 索引创建

索引的创建在构建数据的schema时进行,分为属性索引创建、空间索引创建和时间索引创建。

注意
①时间索引必然要跟随空间索引,不能单独只有时间索引。即可以单独创建空间索引或者共同创建空间索引和时间索引。
②对同一属性不能重复建索引。

3.1 创建属性索引

属性索引比较简单,只需要在构建某个属性的schema信息时,在属性信息后面增加index=true的配置信息即可,如为plantNo属性创建属性索引:
attributes.append("plantNo:String:index=true,");

3.2 创建空间索引

在声明空间信息字段时用*来标识该字段是空间信息字段,配置空间引用标识符和标识符编码,会自动为其创建空间索引,如果该属性是Point,则创建Z索引,如果是LineString或者Polygon则创建XZ索引。
attributes.append("*geom:LineString:srid=4326");
该例子中,属性名是geom,类型是线。

3.3 创建时间索引

创建时间索引时,和前两种索引不同,前两种索引是在构建属性时直接指定,但时间索引需要为SimpleFeatureType对象来进行设置。(schema中只有一个Date类型的属性,GeoMesa会自动为其构建时间索引,但当有多个Date类型属性时,需手动指定为哪一个时间属性创建索引。

时间索引可以设定时间间隔周期(默认是week),并指定创建时间索引的字段。

//设置z3索引时间间隔为天
sft.getUserData().put("geomesa.z3.interval", "day");
//设置时空索引时间字段为date
sft.getUserData().put("geomesa.index.dtg", "dateAttr");
此外该可以对索引设置精度,该精度会影响存储和检索的效率。
//设置索引精度
sft.getUserData().put("geomesa.xz.precision", 10);

更多对索引的设置如下表。

配置项 操作代码
set schema options sft.getUserData().put("option.one", "foo");
设置属性索引 sft.getDescriptor("name").getUserData().put("index", "true");
有多个时间属性时设置时间索引 sft2.getUserData().put("geomesa.index.dtg", true);
设置对时间不设置索引 sft2.getUserData().put("geomesa.ignore.dtg", true);
设置Feature ID为uuid sft.getUserData().put("geomesa.fid.uuid", "true");
设置geomesa索引精度 sft.getDescriptor("geom").getUserData().put("precision", "4");
设置column-groups sft.getDescriptor("name").getUserData().put("column-groups", "a,b");
设置Z索引的预分片 sft. getUserData().put("geomesa.z.splits", "4");
设置时间索引的间隔 sft.getUserData().put("geomesa.z3.interval", "month");
设置XZ索引的精度 sft.getUserData().put("geomesa.xz.precision", 12);
设置属性索引的Shards sft.getUserData().put("geomesa.attr.splits", "4");
设置属性索引的Cardinality sft.getDescriptor("name").getUserData().put("cardinality", "high");
设置索引的分区依据 sft.getUserData().put("geomesa.table.partition", "time");

四、 数据查询

4.1 查询语法介绍

1.列举CQL查询语言语法:
比较运算法、BETWEEN、比较运算符、LIKE、两属性比较、算法表达式、IN、过滤函数、几何过滤等

2.空间关系查询谓词:
INTERSECTS、DISJOINT、CONTAINS、WITHIN、TOUCHES、CROSSES、EQUALS、BBOX

3.时空查询谓词:
BEFORE、BEFORE OR DURING、 DURING、 DURING OR AFTER、 AFTER

4.属性查询:
针对某一属性字段创建索引之后,可以对该列进行属性查询

5.用户可以配置Query对象参数指定结果具体返回哪些列:
query.setPropertyNames(returnFields) 其中returnFields是String数组

6. 用户可以配置Query对象的sortBy参数指定返回结果的排序:
query.setSortBy(sort)
其中sort=new SortBy[]{CommonFactoryFinder.getFilterFactory2().sort(sortField, order)}

具体谓词的作用和使用方法在此

4.2 查询计划

Geomesa查询计划是将GeoTools Query转换为特定后端的扫描和过滤器的过程,它包括以下几个步骤:
(1)重写cql过滤器以进行快速评估并进行优化;
(2)CQL过滤器根据可用索引拆分;
(3)选择一个可用索引来执行查询(索引选取策略);
(4)逻辑查询计划由核心geomesa索引代码创建(各种索引的查询ranges生成方式);
(5)为特定后端数据库创建物理查询计划(hbase转为scans查询)。
1、查询条件分解
查询Query可能包含多种过滤条件,在查询计划执行中会将这些条件进行拆分,确定主过滤器(用来确定从hbase拉数据的scans)和用来过滤的cql过滤器。主过滤器的选择最终是由索引选择决定的。
举例:

索引分解.png

对于上述有属性查询、时间和空间查询的请求,可以分解为两种索引使用方式。
(1):Z3索引+属性Filter索引;
(2):Z2索引+时间和属性Filter索引。
Geomesa会对不同的索引组合打分,哪种情况分高使用哪种索引进行查询,具体选择策略见下面。

2、索引选择
索引选择基于需要扫描数据库的数据范围最小原则,因此最佳的查询计划通常是扫描最少行的查询计划。Geomesa有两种方法可供选择:基于成本的策略和基于启发式策略。
(1)基于成本的策略
Geomesa在录入数据期间会收集统计数据,并将其存储用于查询计划。收集的统计数据是:

总数
默认几何,默认日期和任何索引属性的最小值/最大值(边界)
默认几何,默认日期和任何索引属性的直方图
任何索引属性的频率,按周拆分
任何索引属性的前k个
Z3直方图基于默认几何和默认日期(如果两者都存在)

根据geomesa官网介绍,当前只支持针对Accumulo数据存储,hbase暂不支持缓存统计信息。

在该例子BBOX(geom,120.227754,30.215471, 120.227898,30.215885) AND date DURING 2018-12-01T00:00:00.000Z/2018-12-01T03:00:00.000Z AND PlateNo = '苏Z1G31G',按照规则,则会选择车牌的属性索引进行查询过滤。

(2)基于启发式策略
启发式扫描可以仅基于查询过滤器用于查询计划。优先事项如下,括号内为耗时常量,属性索引耗时和条件有关。

查询条件 耗时常量
使用ID索引的功能ID谓词 1L
使用属性索引的高基数属性谓词 500L/25L/10L
属性相等谓词使用属性索引 100L
使用Z3 / XZ3指数的时空谓词 200L
使用属性索引的属性范围谓词 250L
使用Z2 / XZ2索引的空间谓词 400L
使用Z3 / XZ3指数的时态谓词 401L
使用属性索引的低基数属性谓词 50000L/2500L/1000L

此外,使用join属性索引的Accumulo数据存储将根据查询属性/转换对任何需要连接的谓词进行去优先级排序。

如果多个属性谓词与最高优先级相关联,则无法保证从该组中选择哪一个。

启发式策略则是预先设定好和查询优先级相关的耗时常量值,根据查询过滤的CQL来确定不同索引耗时的排序,选择耗时最少的索引作为执行查询的索引。属性索引常量选择和CQL条件相关,非空匹配耗时常量值5000、等于匹配耗时100、范围匹配耗时250,如果在创建属性索引时指定了索引基数HIGH/LOW,耗时对应调整除10 (HIGH), 乘10 (LOW),不指定则不变。Geomesa此时会根据这些索引耗时排序,选择耗时最少的索引。

查询时索引的选择顺序:

1. Feature ID predicates using the ID index 
2. High-cardinality attribute predicates using the attribute index 
3. Attribute equality predicates using the attribute index 
4. Spatio-temporal predicates using the Z3/XZ3 index 
5. Attribute range predicates using the attribute index 
6. Spatial predicates using the Z2/XZ2 index 
7. Temporal predicates using the Z3/XZ3 index

4.3 查询流程

查询数据使用GeoTools进行查询的基本流程如下:

1.  对想要查询的字段,编写相应的查询条件ECQL语句
2.  获取要查询的要素名称,即写入时SimpleFeatureType的Name,schema的名称
3.  用ECQL创建Filter类型的对象
4.  创建Query对象,将上两步中获取的schema名称和Filter对象作为参数传入
5.  构建GeoMesa连接,创建DataStore
6.  使用dataStore对象获取数据读取器,将Query对象传给数据读取器执行查询,查询结果数据通过读取器对象获取。

4.4 代码示例

/**
 * 对数据进行时空查询
 * */
public static void queryData() throws Exception{
    //先定义查询语句
    String during = "dateAttr DURING 2010-04-25T00:00:00.000Z/2020-04-28T00:00:00.000Z";
    String bbox = "bbox (geom, 115.31412 ,10.89577, 125.31412, 80.89577)";
    String spatioTemp = bbox + " AND " + during;
    
    //声明Query查询对象
    Query query = null;
    query = new Query("string-data", ECQL.toFilter(spatioTemp));

    //构建GeoMesa连接--DataStroe
    Map params = new HashMap();
    params.put("hbase.zookeepers", "10.3.69.191");
    params.put("hbase.catalog", "wcy_test");
    DataStore dataStore = DataStoreFinder.getDataStore(params);

    //获取读取器reader
    FeatureReader reader = dataStore.getFeatureReader(query, Transaction.AUTO_COMMIT);
    if(reader.hasNext()){
        SimpleFeature feature = reader.next();
        String locationWKT = feature.getAttribute("geom").toString();
        System.out.println(locationWKT);
    } else{
        System.out.println("No data");
    }
}

4.5 常用查询条件

  1. 设置最大返回条目
Query query = new Query(typeName, ECQL.toFilter(queryCQL));
query.setMaxFeatures(Integer.parseInt(maxView));
  1. 设置排序
Query query = new Query(typeName, ECQL.toFilter(queryCQL));
FilterFactoryImpl ff = new FilterFactoryImpl();
query.setSortBy(new SortBy[]{new SortByImpl(ff.property("startTime"), SortOrder.ASCENDING)});
  1. 统计查询-查总数
Query query = new Query(typeName);
query.getHints().put(QueryHints.STATS_STRING(), "Count()");
  1. 聚合查询-GroupBy,查每个分组的总数
Query query = new Query(typeName);
query.getHints().put(QueryHints.STATS_STRING(), "GroupBy(\"carID\",Count())");
  1. 统计查询-查最大最小值
Query query = new Query(typeName);
query.getHints().put(QueryHints.STATS_STRING(), "GroupBy(\"carID\",Count())");

你可能感兴趣的:(Geomesa学习3 - 数据操作)