- A+
空间字段
针对空间数据建立空间索引,Elasticsearch会自动为每个字段自动建立索引,空间字段也是如此,Elasticsearch已经提供了几种类型的空间字段,下面主要针对空间字段进行讲解。空间字段主要分为两种类型,geo_point和geo_shape。ps,作者对于Elasticsearch研究的还不是很透彻,如果各位发现哪里有错误,欢迎在评论区留言。
geo_point
geo_point是为点要素专门设定的字段,字段内容填点要素的经纬度,该字段可以使用Elasticsearch以下几个功能。
- 找出落在指定矩形框中的坐标点、找出与指定点在给定距离内的点、找出与指定点距离在给定最小距离和最大距离之间的点、找出落在多边形中的点。
- 从地理位置角度或者依据距离中心点的距离进行空间聚合。
- 按距离排序
- 将距离整合到记录的相关性得分中
geo_shape
当数据中包含点要素以外的线、面等要素时,需要利用geo_shape类型。该字段将geo_json几何对象映射到geo_shape类型,如果要启用该类型,必须将某个字段定义为geo_shape类型。geo_shape有两种构建索引的方式,分别是geohash索引和四叉树索引,同时需要设置索引的建立精度。
定义mapping
采用如下语句定义mapping,其中包含了geo_shape和geo_point。本文主要对浮动车数据建立数据表,因此,geo_point主要存储起止点数据,点要素格式;geo_shape保存轨迹数据,线要素格式。
{ "settings":{ "number_of_shards": 3, "number_of_replicas": 1 }, "mappings":{ "Trace":{ "properties":{ "id":{ "type":"keyword" }, "taxiId":{ "type":"keyword" }, "startTime":{ "type":"date", "format":"yyyy-MM-dd HH:mm:ss" }, "startTimeIndex":{ "type":"integer" }, "endTime":{ "type":"date", "format":"yyyy-MM-dd HH:mm:ss" }, "endTimeIndex":{ "type":"integer" }, "averageSpeed":{ "type":"integer" }, "length":{ "type":"double" }, "emptyOrFillStatus":{ "type":"integer" }, "startPoint": { "type": "geo_point" }, "endPoint": { "type": "geo_point" }, "pointList": { "type": "geo_shape", "tree":"geohash", "precision": "10km" } } } } }
数据库结构图
插入数据后,格式如下,可以发现,空间字段并没有显示出来,但实际上,数据已经保存在数据库中。
插入数据
本文插入数据的代码采用java代码编写,主要讲解如何用java插入geo_point和geo_shape类型的数据,网上基本没有对这种方法的讲解,亲测有效。
Map<String,Object> jsonMap = new HashMap<String,Object>(); //创建一个map,用于存储数据,每一个jsonMap即为一条记录,string为字段名称,object为数据封装对象
double[] endPoint = {Double.parseDouble(lon_lat_end[0]),Double.parseDouble(lon_lat_end[1])}; //一维double数组,第一位为经度,第二位为纬度 jsonMap.put("endPoint",endPoint); //将数据加入map中
double[][] ptsArray = new double[ptList.size()][2]; //ptList为轨迹数据列表,列表中每个元素为一个轨迹点,用经度+“,”+纬度形式表示 //采用二维数组表示轨迹数据 for (int i=0;i<ptList.size();i++) { String ptstr = ptList.get(i); String[] lon_lat = ptstr.split(","); ptsArray[i][0] = Double.parseDouble(lon_lat[0]);//经度 ptsArray[i][1] = Double.parseDouble(lon_lat[1]);//纬度 } Map<String,Object> pointlistMap = new HashMap<String,Object>(); //专门为geoshape类型定义map,主要是因为在插入记录时必须指定geoshape类型,如这里使用的是“lingestring” pointlistMap.put("type","linestring"); pointlistMap.put("coordinates",ptsArray); jsonMap.put("pointList",pointlistMap);//将数据加入结果map中
List<Map<String, Object>> dataList = new ArrayList<Map<String, Object>>(); //定义一个存放jsonMap的list,每个jsonMap即代表一条数据记录 List idList = new ArrayList();//这里指定每条记录的id,不使用系统自动生成的id dataList.add(jsonMap);//使用for循环,将所有的jsonmap都加入到dataList中 idList.add(id);
TransportClient client = EsClient.getConnection();//获取Elasticsearch的连接 BulkRequestBuilder bulkRequestBuilder = client.prepareBulk(); //一定要采用批处理,极大程度加快插入速度,我这里1s可以插入10000条 //批处理一次性处理的数量可以自己定,主要是在下面的for循环外在加一层for循环进行批处理控制 for (int i = 0; i < dataList.size(); i++) { bulkRequestBuilder.add(client.prepareIndex("20170307trace", "Trace",idList.get(i)).setSource(dataList.get(i))); } bulkRequestBuilder.get();
空间聚合查询
这里主要讲解空间聚合查询,使用java语言,将标识为空间类型的字段进行空间聚合
public List<double[]> searchUpHeatPoints(String ptLeftTop, String ptRightBottom, String startTime, String endTime) { long startFuncTime = System.currentTimeMillis(); TransportClient client = EsClient.getConnection(); //查询条件 QueryBuilder queryBuilder = QueryBuilders.boolQuery() .must(QueryBuilders.termQuery("emptyOrFillStatus", 1)) .must(QueryBuilders.rangeQuery("startTime").from("2017-03-07 17:00:00").to("2017-03-07 19:00:00")); //聚合条件 AggregationBuilder aggregation = AggregationBuilders .geohashGrid("agg") .field("startPoint") .precision(7); //定义查询、加入条件,并开启查询 SearchResponse response = client.prepareSearch("20170307trace") .setTypes("Trace") .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setQuery(queryBuilder) // Query .addAggregation(aggregation) //.setSize(100)//这里不要设置size,聚合查询不要设置size .get(); System.out.println(response.getHits().getTotalHits() + "个");// !总! 记录数,总记录数 List<double[]> result = new ArrayList<double[]>(); //遍历聚合结果 GeoHashGrid agg = response.getAggregations().get("agg"); for ( GeoHashGrid.Bucket entry : agg.getBuckets()) { //String keyAsString = entry.getKeyAsString(); // key as String GeoPoint key = (GeoPoint) entry.getKey(); // key as geo point long docCount = entry.getDocCount(); // Doc count double[] bd09 = CoordinateTransformUtil.wgs84tobd09(key.getLon(), key.getLat()); //这里做了个wgs-84到百度坐标的转换,可以不用管 result.add(new double[]{bd09[0], bd09[1], docCount}); } long endFuncTime = System.currentTimeMillis(); //获取结束时间 System.out.println("运行耗时"+(endFuncTime - startFuncTime)/1000.0 + "秒"); //表中记录12万条,聚合的结果集近20000个,耗时不到200毫秒; //依我估测,哪怕数据量为千万条,耗时应该也在1s内,个人感觉,es对聚合的支持非常非常棒,very good!!! return result;//接下来就可以用百度地图做热力图了,非常方便 }
聚合结果如下图所示。