热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

基于百度地图SDK和ElasticsearchGEO查询的地理围栏分析系统(2)-查询实现

在上一篇博客中,我们准备好了数据。现在数据已经以我们需要的格式,存放在Elasticsearch中了。本文讲述如何在Elasticsearch中进行空间GEO查询和聚合查询,以及如何准备ajax

 

在上一篇博客中,我们准备好了数据。现在数据已经以我们需要的格式,存放在Elasticsearch中了。

本文讲述如何在Elasticsearch中进行空间GEO查询和聚合查询,以及如何准备ajax接口。

平台的服务端部分使用的springboot+mybatis的基本开发模式。工程结构如下。

可以看到本工程有三个module:

1)moonlight-web是controller和service层的实现;

2)moonlight-dsl封装了ES空间索引查询和聚合查询的方法;

3)moonlight-dao封装了持久化地理围栏的方法。

我们以客户端请求的处理顺序为例进行讲解。

 

1、controller

在controller层中,我们实现了4个接口,分别是circle、box、polygon、heatmap,也就是圆形圈选,矩形圈选,多边形圈选和热力图。

先看一下代码的具体实现。

@RestController
@RequestMapping(
"/moonlight")
public class MoonlightController {

protected final Logger logger = LoggerFactory.getLogger(this.getClass());

@Autowired
private MoonlightService moonlightService;

@RequestMapping(value
= "/circle", method = RequestMethod.GET)
public ResponseEntity circle(HttpServletRequest request, HttpServletResponse response) {
String point
= request.getParameter("point");
String radius
= request.getParameter("radius");
try {
Map
result = moonlightService.circle(point, radius);
logger.info(
"circle圈选成功, points={}, radius={}, result={}", point, radius, result);
return new ResponseEntity<>(
new Response(ResultCode.SUCCESS, "circle圈选成功", result),
HttpStatus.OK);
}
catch (Exception e) {
logger.error(
"circle圈选失败, points={}, radius={}, result={}", point, radius, null, e);
return new ResponseEntity<>(
new Response(ResultCode.EXCEPTION, "circle圈选失败", null),
HttpStatus.INTERNAL_SERVER_ERROR);
}
}

@RequestMapping(value
= "/box", method = RequestMethod.GET)
public ResponseEntity box(HttpServletRequest request, HttpServletResponse response) {
String point1
= request.getParameter("point1");
String point2
= request.getParameter("point2");
String point3
= request.getParameter("point3");
String point4
= request.getParameter("point4");
try {
Map
result = moonlightService.boundingBox(point1, point2, point3, point4);
logger.info(
"box圈选成功, point1={}, point2={}, point3={}, point4={}, result={}", point1, point2, point3, point4, result);
return new ResponseEntity<>(
new Response(ResultCode.SUCCESS, "box圈选成功", result),
HttpStatus.OK);
}
catch (Exception e) {
logger.error(
"box圈选失败, point1={}, point2={}, point3={}, point4={}, result={}", point1, point2, point3, point4, null, e);
return new ResponseEntity<>(
new Response(ResultCode.EXCEPTION, "box圈选失败", null),
HttpStatus.INTERNAL_SERVER_ERROR);
}
}

@RequestMapping(value
= "/polygon", method = RequestMethod.GET)
public ResponseEntity polygon(HttpServletRequest request, HttpServletResponse response) {
List
points = new ArrayList<>();
Enumeration
paramNames = request.getParameterNames();
while (paramNames.hasMoreElements()) {
String paramName
= paramNames.nextElement();
if (paramName.startsWith("point")) {
points.add(request.getParameter(paramName));
}
}
try {
Map
result = moonlightService.polygon(points);
logger.info(
"polygon圈选成功, points={}, result={}", points, result);
return new ResponseEntity<>(
new Response(ResultCode.SUCCESS, "polygon圈选成功", result),
HttpStatus.OK);
}
catch (Exception e) {
logger.error(
"polygon圈选失败, points={}, result={}", points, null, e);
return new ResponseEntity<>(
new Response(ResultCode.EXCEPTION, "polygon圈选失败", null),
HttpStatus.INTERNAL_SERVER_ERROR);
}
}

@RequestMapping(value
= "/heatMap", method = RequestMethod.GET)
public ResponseEntity heatMap(HttpServletRequest request, HttpServletResponse response) {
try {
List
> result = moonlightService.heatMap();
logger.info(
"heatMap请求成功, result={}", result);
return new ResponseEntity<>(
new Response(ResultCode.SUCCESS, "heatMap请求成功", result),
HttpStatus.OK);
}
catch (Exception e) {
logger.error(
"heatMap请求失败, result={}", null, e);
return new ResponseEntity<>(
new Response(ResultCode.EXCEPTION, "heatMap请求失败", null),
HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}

我们以圆形圈选(circle接口)为例,circle接口传入两个参数,一个是point,也就是中心点坐标,一个是radius,也就是半径,它干的事情就是圈选出,point点周围radius长度内的所有订单数据,具体实现是调用了service层的方法,controller得到圈选的数据后就返回了。

下面我们来看一下service层。

 

2、service

service层是具体业务的实现。我们这里的service仍然比较简单,可以看到只是初始化了esDao的句柄,然后进行es的geo查询。

先看一下具体代码。

@Service
public class MoonlightService {

protected final Logger logger = LoggerFactory.getLogger(this.getClass());

@Autowired
private ESDao esDao;

public Map circle(String point, String radius) {
POI center
= new POI(point);
return esDao.circle(center, Double.parseDouble(radius));
}

public Map boundingBox(String point1, String point2, String point3, String point4) {
POI poi1
= new POI(point1);
POI poi2
= new POI(point2);
POI poi3
= new POI(point3);
POI poi4
= new POI(point4);
POI topLeft
= getTopLeft(poi1, poi2, poi3, poi4);
POI bottomRight
= getBottomRight(poi1, poi2, poi3, poi4);
logger.info(
"topLeft - lat={}, lng={}, bottomRight - lat={}, lng={}",
topLeft.getLat(), topLeft.getLng(), bottomRight.getLat(), bottomRight.getLng());
return esDao.boundingBox(topLeft, bottomRight);
}

public Map polygon(List points) {
List
poiList = new ArrayList<>();
for (String point : points) {
POI poi
= new POI(point);
poiList.add(poi);
}
return esDao.polygon(poiList);
}

public List> heatMap() {
return esDao.heatMap();
}

private POI getTopLeft(POI poi1, POI poi2, POI poi3, POI poi4) {
POI topLeft
= new POI();
List
latList = new ArrayList<>();
List
lngList = new ArrayList<>();
latList.add(poi1.getLat());
latList.add(poi2.getLat());
latList.add(poi3.getLat());
latList.add(poi4.getLat());
Collections.sort(latList);
Double minLat
= latList.get(0);
Double maxLat
= latList.get(3);

lngList.add(poi1.getLng());
lngList.add(poi2.getLng());
lngList.add(poi3.getLng());
lngList.add(poi4.getLng());
Collections.sort(lngList);
Double minLng
= lngList.get(0);
Double maxLng
= lngList.get(3);

topLeft.setLat(maxLat);
topLeft.setLng(minLng);
return topLeft;
}

private POI getBottomRight(POI poi1, POI poi2, POI poi3, POI poi4) {
POI bottomRight
= new POI();
List
latList = new ArrayList<>();
List
lngList = new ArrayList<>();
latList.add(poi1.getLat());
latList.add(poi2.getLat());
latList.add(poi3.getLat());
latList.add(poi4.getLat());
Collections.sort(latList);
Double minLat
= latList.get(0);
Double maxLat
= latList.get(3);

lngList.add(poi1.getLng());
lngList.add(poi2.getLng());
lngList.add(poi3.getLng());
lngList.add(poi4.getLng());
Collections.sort(lngList);
Double minLng
= lngList.get(0);
Double maxLng
= lngList.get(3);

bottomRight.setLat(minLat);
bottomRight.setLng(maxLng);
return bottomRight;
}
}

我们仍然是以圆形圈选为例,可以看到,service代码的逻辑就是,创建出圈选需要的数据接口,然后调用Dao层进行查询就是了。

circle圈选需要的是一个中心点POI类型,和一个Double半径。

box矩形查询需要的是左上坐标点和右下坐标点,里面有两个函数getTopLeft、getBottomRight分别可以求出矩形的左上点和右下点。

polygon多边形查询需要的是一系列点,这些点顺序的连接所绘制出来的图形就是目标多边形。

heatmap热力图什么参数也不要,将返回一定精度的经纬度计数值,后面我们会详述。

之后所有的service都调用了Dao层的es查询逻辑。所以最重要的一部分是esDao的实现,下面我们就来看一看。

 

3、Dao

Dao层代码是整个项目的核心,包括对Elasticsearch数据进行圈选和聚合两部分,此外就是热力图数据的准备。

先看一下代码。

@Component
public class ESDao {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());

@Autowired
private ESClient esClient;

public Map circle(POI center, Double radius) {

TermsQueryBuilder termsQuery
= termsQuery("product_id", new double[]{3, 4});

GeoDistanceRangeQueryBuilder geoDistanceRangeQuery
= QueryBuilders.geoDistanceRangeQuery("location")
.point(center.getLat(), center.getLng())
.from(
"0m")
.to(String.format(
"%fm", radius))
.includeLower(
true)
.includeUpper(
true)
.optimizeBbox(
"memory")
.geoDistance(GeoDistance.SLOPPY_ARC);

QueryBuilder queryBuilder
= QueryBuilders.boolQuery().must(termsQuery).must(geoDistanceRangeQuery);

SearchRequestBuilder search
= esClient.getClient().prepareSearch("moon").setTypes("bj")
.setSearchType(SearchType.DFS_QUERY_AND_FETCH)
.setQuery(queryBuilder);

return agg(search);
}

public Map boundingBox(POI topLeft, POI bottomRight) {

TermsQueryBuilder termsQuery
= termsQuery("product_id", new double[]{3, 4});

GeoBoundingBoxQueryBuilder geoBoundingBoxQuery
= QueryBuilders.geoBoundingBoxQuery("location")
.topLeft(topLeft.getLat(), topLeft.getLng())
.bottomRight(bottomRight.getLat(), bottomRight.getLng());

QueryBuilder queryBuilder
= QueryBuilders.boolQuery().must(termsQuery).must(geoBoundingBoxQuery);

SearchRequestBuilder search
= esClient.getClient().prepareSearch("moon").setTypes("bj")
.setSearchType(SearchType.DFS_QUERY_AND_FETCH)
.setQuery(queryBuilder);

return agg(search);
}

public Map polygon(List poiList) {

TermsQueryBuilder termsQuery
= termsQuery("product_id", new double[]{3, 4});

GeoPolygonQueryBuilder geoPolygonQuery
= QueryBuilders.geoPolygonQuery("location");
for (POI poi : poiList) {
geoPolygonQuery.addPoint(poi.getLat(), poi.getLng());
}

QueryBuilder queryBuilder
= QueryBuilders.boolQuery().must(termsQuery).must(geoPolygonQuery);

SearchRequestBuilder search
= esClient.getClient().prepareSearch("moon").setTypes("bj")
.setSearchType(SearchType.DFS_QUERY_AND_FETCH)
.setQuery(queryBuilder);

return agg(search);
}

public List> heatMap() {

TermQueryBuilder queryBuilder
= termQuery("date", "2017-11-24");
SearchRequestBuilder searchRequestBuilder
= esClient.getClient()
.prepareSearch(
"moon").setTypes("bj");
SearchResponse response
= searchRequestBuilder
.setQuery(queryBuilder)
.setFrom(
0).setSize(10000)
.setExplain(
true).execute().actionGet();

SearchHits hits
= response.getHits();
Map
countMap = new HashMap<>();
for (SearchHit hit : hits) {
Map
source = hit.getSource();
Map
locatiOnMap= (Map) source.get("location");
DecimalFormat df
= new DecimalFormat("#.000");
String lat
= df.format(locationMap.get("lat"));
String lon
= df.format(locationMap.get("lon"));
String key
= lat+"-"+lon;
if (countMap.containsKey(key)) {
countMap.put(key, countMap.get(key)
+ 1);
}
else {
countMap.put(key,
1);
}
}
List
> result = new ArrayList<>();
for (Map.Entry entry : countMap.entrySet()) {
String lat
= entry.getKey().split("-")[0];
String lon
= entry.getKey().split("-")[1];
Integer count
= entry.getValue();
Map
map = new HashMap<>();
map.put(
"lat", Double.parseDouble(lat));
map.put(
"lng", Double.parseDouble(lon));
map.put(
"count", count);
result.add(map);
}
return result;
}

private Map agg(SearchRequestBuilder search) {

Map
resultMap = new HashMap<>();

GroupBy groupBy
= new GroupBy(search, "date_group", "date", true);
groupBy.addSumAgg(
"pre_total_fee_sum", "pre_total_fee");
groupBy.addCountAgg(
"order_id_count", "order_id");
groupBy.addSumAgg(
"cancel_count", "type");

List
xAxis = new ArrayList<>();
List
profits = new ArrayList<>();
List
totals = new ArrayList<>();
List
cancelRatios = new ArrayList<>();
List
> details = new ArrayList<>();

Map
groupbyRespOnse= groupBy.getGroupbyResponse();
for (Map.Entry entry : groupbyResponse.entrySet()) {
String date
= entry.getKey();
xAxis.add(date);
Map
subAggMap = (Map) entry.getValue();
String profit
= subAggMap.get("pre_total_fee_sum");
profits.add(profit);
String total
= subAggMap.get("order_id_count");
totals.add(total);
String cancelRatioDouble
= new DecimalFormat("#.0000").format(
Double.parseDouble(subAggMap.get(
"cancel_count")) / Double.parseDouble(subAggMap.get("order_id_count"))
);
String cancelRatio
= new DecimalFormat("0.00%").format(
Double.parseDouble(subAggMap.get(
"cancel_count")) / Double.parseDouble(subAggMap.get("order_id_count"))
);
cancelRatios.add(cancelRatioDouble);

Map
tempMap = new HashMap<>();
tempMap.put(
"profit", profit);
tempMap.put(
"total", total);
tempMap.put(
"cancelRatio", cancelRatio);
tempMap.put(
"date", date);
details.add(tempMap);
}

resultMap.put(
"xAxis", xAxis);
resultMap.put(
"profit", profits);
resultMap.put(
"total", totals);
resultMap.put(
"cancelRatio", cancelRatios);
resultMap.put(
"detail", details);

return resultMap;
}

}

es圈选部分

circle为例,我们构造了一个geoDistanceRangeQuery查询,这个查询到上一篇博客准备好的moon索引,bj type中去将数据圈选出来。

类似的我们有矩形geoBoundingBoxQuery查询,多边形geoPolygonQuery查询,具体构造查询的方式可以参照代码,这个代码还是很简单的,熟悉es的同学很快可以上手并且实现这样的查询,不熟悉的话可以自行百度一下。如果还有其他的查询条件,可以通过QueryBuilders.boolQuery().must(termsQuery).must(geoDistanceRangeQuery)加入,例如我这里在圈选之外加入了一个terms查询,这个查询相当于sql中的where product_id in (3,4) and ...。

 

es聚合部分

es聚合部分做的事情是,对查询出的订单进行了聚合运算,例如求和和计数,是两个最常见的运算,这部分在这里不详细叙述了,请参见这篇博客。

 

热力图

这里要额外说明的是,热力图heatmap,和圈选不一样,他是查询了最近一天type=bj分区里的所有数据,按照坐标进行了计数,可以看到的是,计数的时候,我们指定了精度,这里是小数点后三位有效数字

            DecimalFormat df = new DecimalFormat("#.000");
String lat
= df.format(locationMap.get("lat"));
String lon
= df.format(locationMap.get("lon"));
String key
= lat+"-"+lon;

然后将计数结果返回。百度地图SDK会将计数结果绘制成热力图,这个不用我们管,我会在另一篇博客中讲述这个过程。

 

到这里,整个工程的基本功能就介绍完了。

 


推荐阅读
  • Sleuth+zipkin链路追踪SpringCloud微服务的解决方案
    在庞大的微服务群中,随着业务扩展,微服务个数增多,系统调用链路复杂化。Sleuth+zipkin是解决SpringCloud微服务定位和追踪的方案。通过TraceId将不同服务调用的日志串联起来,实现请求链路跟踪。通过Feign调用和Request传递TraceId,将整个调用链路的服务日志归组合并,提供定位和追踪的功能。 ... [详细]
  • Java实战之电影在线观看系统的实现
    本文介绍了Java实战之电影在线观看系统的实现过程。首先对项目进行了简述,然后展示了系统的效果图。接着介绍了系统的核心代码,包括后台用户管理控制器、电影管理控制器和前台电影控制器。最后对项目的环境配置和使用的技术进行了说明,包括JSP、Spring、SpringMVC、MyBatis、html、css、JavaScript、JQuery、Ajax、layui和maven等。 ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • springmvc学习笔记(十):控制器业务方法中通过注解实现封装Javabean接收表单提交的数据
    本文介绍了在springmvc学习笔记系列的第十篇中,控制器的业务方法中如何通过注解实现封装Javabean来接收表单提交的数据。同时还讨论了当有多个注册表单且字段完全相同时,如何将其交给同一个控制器处理。 ... [详细]
  • 在springmvc框架中,前台ajax调用方法,对图片批量下载,如何弹出提示保存位置选框?Controller方法 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 网络请求模块选择——axios框架的基本使用和封装
    本文介绍了选择网络请求模块axios的原因,以及axios框架的基本使用和封装方法。包括发送并发请求的演示,全局配置的设置,创建axios实例的方法,拦截器的使用,以及如何封装和请求响应劫持等内容。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • SpringMVC接收请求参数的方式总结
    本文总结了在SpringMVC开发中处理控制器参数的各种方式,包括处理使用@RequestParam注解的参数、MultipartFile类型参数和Simple类型参数的RequestParamMethodArgumentResolver,处理@RequestBody注解的参数的RequestResponseBodyMethodProcessor,以及PathVariableMapMethodArgumentResol等子类。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • Netty源代码分析服务器端启动ServerBootstrap初始化
    本文主要分析了Netty源代码中服务器端启动的过程,包括ServerBootstrap的初始化和相关参数的设置。通过分析NioEventLoopGroup、NioServerSocketChannel、ChannelOption.SO_BACKLOG等关键组件和选项的作用,深入理解Netty服务器端的启动过程。同时,还介绍了LoggingHandler的作用和使用方法,帮助读者更好地理解Netty源代码。 ... [详细]
  • Servlet多用户登录时HttpSession会话信息覆盖问题的解决方案
    本文讨论了在Servlet多用户登录时可能出现的HttpSession会话信息覆盖问题,并提供了解决方案。通过分析JSESSIONID的作用机制和编码方式,我们可以得出每个HttpSession对象都是通过客户端发送的唯一JSESSIONID来识别的,因此无需担心会话信息被覆盖的问题。需要注意的是,本文讨论的是多个客户端级别上的多用户登录,而非同一个浏览器级别上的多用户登录。 ... [详细]
  • 本文介绍了Java后台Jsonp处理方法及其应用场景。首先解释了Jsonp是一个非官方的协议,它允许在服务器端通过Script tags返回至客户端,并通过javascript callback的形式实现跨域访问。然后介绍了JSON系统开发方法,它是一种面向数据结构的分析和设计方法,以活动为中心,将一连串的活动顺序组合成一个完整的工作进程。接着给出了一个客户端示例代码,使用了jQuery的ajax方法请求一个Jsonp数据。 ... [详细]
author-avatar
哈王豐3_408
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有