elasticsearch之十八springboot测试地理信息搜索

个人专题目录](https://www.jianshu.com/p/140e2a59db2c)


1. elasticsearch地理信息搜索

随着生活服务类应用最近一年的崛起和普及,基于地理位置的内容正在日益重要。LBS已是老生常谈,不过在PC、在移动互联网时代,LBS在导航之外都未出现第二个杀手级应用。在没有O2O之前,LBS所依重的POI(Point of Interest)并未真正成为用户的“Interest”,人们的兴趣还是只存在于网络的虚拟内容,游戏、新闻、文学、网页、多媒体,等等。

O2O大热之后,越来越多的杀手级应用开始出现:打车拼车租车等用车服务进去主界面是地图,陌陌这个声名鹊起的后来者基于位置做出了社交的差异化,团购上门分享所有热门O2O应用均离不开地图。线下实体、生活服务、身边内容的呈现形式都基于地图的“点”而不是基于时间的“线”,人们不需刷而是搜索、缩放、点选,乃至不做任何操作根据位置移动来与之交互。我将这类内容称之为LocationPoint,这将成为Timeline之后的又一种至关重要的内容形式。

1.1 地理坐标点

地理坐标点 是指地球表面可以用经纬度描述的一个点。 地理坐标点可以用来计算两个坐标间的距离,还可以判断一个坐标是否在一个区域中,或在聚合中。

地理坐标点不能被动态映射 (dynamic mapping)自动检测,而是需要显式声明对应字段类型为 geo-point

PUT /map
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1
  },
  "mappings": {
    "cp": {
      "properties": {
        "name": {
          "type": "keyword"
        },
        "location": {
          "type": "geo_point" #经度lon,纬度lat
        }
      }
    }
  }
}

1.2 经纬度坐标格式

如上例,location 字段被声明为 geo_point 后,我们就可以索引包含了经纬度信息的文档了。 经纬度信息的形式可以是字符串、数组或者对象:

####  北科
POST /map/cp/1
{
  "name": "北科",
  "location": {
    "lon": 116.2577,
    "lat": 40.1225
  }
}

#### 巩华城地铁站
POST /map/cp/2
{
  "name": "巩华城地铁站",
  "location": {
    "lon": 116.3004,
    "lat": 40.137399
  }
}

#### 生命科学园地铁站
POST /map/cp/3
{
  "name": "生命科学园地铁站",
  "location": {
    "lon": 116.300582,
    "lat": 40.101053
  }
}
字符串形式以半角逗号分割,如 "lat,lon"
对象形式显式命名为 latlon
数组形式表示为 [lon,lat]

大家可以上百度或者高德的地图开放平台了解相关的操作,以百度地图开放为例(http://lbsyun.baidu.com/jsdemo.htm),比如想获取地图上某个点的经纬度,如下图所示:

geo3.png

1.3 通过地理坐标点过滤

我们有时候,希望可以根据当前所在的位置,找到自己身边的符合条件的一些商店,酒店之类的。它主要支持两种类型的地理查询:

一种是地理点(geo_point),即经纬度查询,另一种是地理形状查询(geo_shape),即支持点、线、圈、多边形查询.

ES中有3中位置相关的过滤器,用于过滤位置信息:

  • geo_distance: 查找距离某个中心点距离在一定范围内的位置
  • geo_bounding_box: 查找某个长方形区域内的位置
  • geo_polygon: 查找位于多边形内的地点。

1.4 geo_distance

地理距离过滤器( geo_distance )以给定位置为圆心画一个圆,来找出那些地理坐标落在其中的文档.

广泛地应用在O2O,LBS领域,比如查找附近的酒店、包店、共享单车等。如下图所示,按距用户的距离查询周边酒店。

例如:查询以某个经纬度为中心周围500KM以内的城市

#### 定位一个点,使用长度定义一个圆
POST /map/cp/_search
{
  "query": {
    "geo_distance": {
      "location": {             ### 确定坐标位置
        "lon": 116.2577,
        "lat": 40.1225
      },
      "distance": 5000,         ### 半径
      "distance_type": "arc"    ### 指定圆
    }
  }
}
/**
 * 以某个经纬度为中心查询周围限定距离的文档
 *
 * @param indexName 索引
 * @param lot       经度
 * @param lon       纬度
 * @param distance  距离
 * @throws Exception
 */
@Override
public void geoDistanceQuery(String indexName, double lot, double lon, int distance) throws Exception {
    GeoDistanceQueryBuilder geoDistanceQueryBuilder = QueryBuilders.geoDistanceQuery("location")
            .point(lot, lon)
            .distance(distance, DistanceUnit.KILOMETERS)
            .geoDistance(GeoDistance.ARC);
    baseQuery.builder(indexName, geoDistanceQueryBuilder);
}
代码依次执行:
1、建立索引
2、初始化数据
3、执行testGeoDistanceQuery,搜索“距厦门500公里以内的城市”
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SearchServiceApplication.class)
@WebAppConfiguration
@Log4j2
public class GeoQueryTest {
    private String indexName = "cn_large_cities";

    @Autowired
    private IndexService indexService;
    @Autowired
    private DocService docService;
    @Autowired
    private GeoQuery geoQuery;

    @Test
    public void testCreateIndex() throws Exception {
        CreateIndexRequest request = new CreateIndexRequest(indexName);
        buildSetting(request);
        buildIndexMapping(request);
        indexService.createIndex(indexName, request, false);
    }

    @Test
    public void testDelIndex() throws Exception {
        indexService.deleteIndex(indexName, false);
    }

    //设置分片
    private void buildSetting(CreateIndexRequest request) {
        request.settings(Settings.builder().put("index.number_of_shards", 3)
                .put("index.number_of_replicas", 2));
    }

    /**
     * 生成地理信息表索引结构
     * 

* city 城市 * state 省 * location 位置 * * @param request * @throws IOException */ private void buildIndexMapping(CreateIndexRequest request) throws IOException { XContentBuilder mappingBuilder = JsonXContent.contentBuilder() .startObject() .startObject("properties") .startObject("city") .field("type", "keyword") .field("index", "true") .endObject() .startObject("state") .field("type", "keyword") .field("index", "true") .endObject() .startObject("location") .field("type", "geo_point") // .field("index", "true") .endObject() .endObject() .endObject(); request.mapping(mappingBuilder); } @Test public void testInitData() throws IOException { String json1 = "{" + "\"city\": \"北京\", " + "\"state\": \"北京\"," + "\"location\": {\"lat\": \"39.91667\", \"lon\": \"116.41667\"}" + "}"; String json2 = "{" + "\"city\": \"上海\", " + "\"state\": \"上海\"," + "\"location\": {\"lat\": \"34.50000\", \"lon\": \"121.43333\"}" + "}"; String json3 = "{" + "\"city\": \"厦门\", " + "\"state\": \"福建\"," + "\"location\": {\"lat\": \"24.46667\", \"lon\": \"118.10000\"}" + "}"; String json4 = "{" + "\"city\": \"福州\", " + "\"state\": \"福建\"," + "\"location\": {\"lat\": \"26.08333\", \"lon\": \"119.30000\"}" + "}"; String json5 = "{" + "\"city\": \"广州\", " + "\"state\": \"广东\"," + "\"location\": {\"lat\": \"23.16667\", \"lon\": \"113.23333\"}" + "}"; docService.addDoc(indexName, json1, null); docService.addDoc(indexName, json2, null); docService.addDoc(indexName, json3, null); docService.addDoc(indexName, json4, null); docService.addDoc(indexName, json5, null); } @Test public void testGeoDistanceQuery() throws Exception { //距厦门500公里以内的城市 geoQuery.geoDistanceQuery(indexName, 24.46667, 118.0000, 500); } @Test public void testGeoBoundingBox() throws Exception { geoQuery.geoBoundingBoxQuery(indexName, 40.8, -74.0, 40.715, -73.0); } @Test public void testPolygonQuery() throws Exception { geoQuery.geoPolygonQuery(indexName); } }

1.5 geo_bounding_box

查找某个长方形区域内的位置,以高德地图开放平台为例(https://lbs.amap.com/api/javascript-api/example/overlayers/rectangle-draw-and-edit),通过在地图上用矩形框选取一定范围来搜索。如下图所示:

geo5.png

这是目前为止最有效的地理坐标过滤器了,因为它计算起来非常简单。 你指定一个矩形的 顶部 , 底部 , 左边界 ,和 右边界 ,然后过滤器只需判断坐标的经度是否在左右边界之间,纬度是否在上下边界之间:

他可以指定一下几个属性:

top_left: 指定最左边的经度和最上边的纬度

bottom_right: 指定右边的经度和最下边的纬度

例如:查询某个矩形范围内的文档

POST /map/cp/_search
{
  "query": {
    "geo_bounding_box": {
      "location": {
        "top_left": {
          "lon": 116.242461,
          "lat": 40.139123
        },
        "bottom_right": {
          "lon": 116.305702,
          "lat": 40.11987
        }
      }
    }
  }
}
/**
 * 搜索矩形范围内的文档
 *
 * @param indexName 索引
 * @param top       最上边的纬度
 * @param left      最左边的经度
 * @param bottom    最下边的纬度
 * @param right     右边的经度
 * @throws Exception
 */
@Override
public void geoBoundingBoxQuery(String indexName, double top, double left, double bottom, double right) throws Exception {
    GeoBoundingBoxQueryBuilder address = QueryBuilders.geoBoundingBoxQuery("location")
            .setCorners(top, left, bottom, right);
    baseQuery.builder(indexName, address);
}
@Test
public void testGeoBoundingBoxh() throws Exception {
    geoQuery.geoBoundingBoxQuery(indexName,40.8,-74.0,40.715,-73.0);
}

1.6 geo_polygon

根据给定的多个点组成的多边形,查询范围内的点。以高德地图开放平台为例(https://lbs.amap.com/api/javascript-api/example/overlayers/polygon-draw-and-edit),通过多个点来确定一个面积,在些面积区域内搜索。

geo6.png

例如:搜索某个多边型区域的文档。

POST /map/cp/_search
{
  "query": {
    "geo_polygon": {
      "location": {
        "points": [
          {
            "lon": 116.292695,
            "lat": 40.110104
          },
          {
            "lon": 116.296288,
            "lat": 40.096114
          },
          {
            "lon": 116.303798,
            "lat": 40.099977
          },
          {
            "lon": 116.304876,
            "lat": 40.102902
          }
        ]
      }
    }
  }
}
@Override
public void geoPolygonQuery(String indexName) throws Exception {
    List points = new ArrayList<>();
    points.add(new GeoPoint(40.110104, 116.292695));
    points.add(new GeoPoint(40.096114, 116.296288));
    points.add(new GeoPoint(40.099977, 116.303798));
    points.add(new GeoPoint(40.102902, 116.304876));
    GeoPolygonQueryBuilder geoPolygonQueryBuilder = QueryBuilders.geoPolygonQuery("location", points);
    baseQuery.builder(indexName, geoPolygonQueryBuilder);
}
@Test
public void testPolygonQuery() throws Exception {
    geoQuery.geoPolygonQuery(indexName);
}

你可能感兴趣的:(elasticsearch之十八springboot测试地理信息搜索)