Geomesa学习3 - 数据操作

作者: 汤圆毛毛 | 来源:发表于2020-01-07 19:28 被阅读0次

    本章主要介绍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<String, String> params = new HashMap<String, String>();
        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<SimpleFeatureType, SimpleFeature> 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<String, String> params = new HashMap<String, String>();
        params.put("hbase.zookeepers", "10.3.69.191");
        params.put("hbase.catalog", "wcy_test");
        DataStore dataStore = DataStoreFinder.getDataStore(params);
    
        //获取读取器reader
        FeatureReader<SimpleFeatureType, SimpleFeature> 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 - 数据操作

        本文链接:https://www.haomeiwen.com/subject/hlsdactx.html