Elasticsearch教程---地理信息搜索(十三)

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

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

7.1 地理坐标点

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

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

PUT /attractions
{
  "mappings": {
    "restaurant": {
      "properties": {
        "name": {
          "type": "string"
        },
        "location": {
          "type": "geo_point"
        }
      }
    }
  }
}

7.2 经纬度坐标格式

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

PUT /attractions/restaurant/1
{
  "name":     "Chipotle Mexican Grill",
  "location": "40.715, -74.011" 
}

PUT /attractions/restaurant/2
{
  "name":     "Pala Pizza",
  "location": { 
    "lat":     40.722,
    "lon":    -73.989
  }
}

PUT /attractions/restaurant/3
{
  "name":     "Mini Munchies Pizza",
  "location": [ -73.983, 40.719 ] 
}
1字符串形式以半角逗号分割,如 "lat,lon"
2对象形式显式命名为 latlon
3数组形式表示为 [lon,lat]

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

7.3 通过地理坐标点过滤

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

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

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

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

7.4 geo_distance

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

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

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

{
	"query": {
		"geo_distance": {
			"location": [118.0, 24.46667],
			"distance": 500000.0,
			"distance_type": "arc",
			"validation_method": "STRICT",
			"ignore_unmapped": false,
			"boost": 1.0
		}
	}
}

JAVA代码示例:com.javablog.elasticsearch.query.impl.GeoQueryImpl

/**
 * 以某个经纬度为中心查询周围限定距离的文档
 * @param indexName    索引
 * @param typeName     类型
 * @param lot          经度
 * @param lon          纬度
 * @param distance     距离
 * @throws IOException
 */
public void geoDistanceQuery(String indexName, String typeName, double lot, double lon, int distance) throws IOException {
    SearchRequest searchRequest = new SearchRequest(indexName);
    searchRequest.types(typeName);
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    GeoDistanceQueryBuilder geoDistanceQueryBuilder = QueryBuilders.geoDistanceQuery("location")
            .point(lot,lon).
            distance(distance, DistanceUnit.KILOMETERS).
            geoDistance(GeoDistance.ARC);
    searchSourceBuilder.query(geoDistanceQueryBuilder);
    searchRequest.source(searchSourceBuilder);
    log.info("source:" + searchRequest.source());
    SearchResponse searchResponse =  restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
    SearchHits hits = searchResponse.getHits();
    System.out.println(hits.totalHits);
    SearchHit[] h =  hits.getHits();
    for (SearchHit hit : h) {
        System.out.println(hit.getSourceAsMap());
    }
}

演示用例:com.javablog.elasticsearch.test.document.GeoQueryTest

代码依次执行:
1、建立索引
2、初始化数据
3、执行testGeoDistanceQuery,搜索“距厦门500公里以内的城市”
package com.javablog.elasticsearch.test.document;

import com.javablog.elasticsearch.SearchServiceApplication;
import com.javablog.elasticsearch.document.DocService;
import com.javablog.elasticsearch.document.IndexService;
import com.javablog.elasticsearch.query.GeoQuery;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;

import java.io.IOException;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SearchServiceApplication.class)
@WebAppConfiguration
public class GeoQueryTest {
    private final static Logger log = LoggerFactory.getLogger(GeoQueryTest.class);
    private String indexName = "cn_large_cities";
    private String type = "city_type";

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

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

    @Test
    public void testDelIndex() throws IOException {
        indexService.deleteIndex(indexName);
    }

    //设置分片
    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
     * @param type
     * @throws IOException
     */
    private void  buildIndexMapping(CreateIndexRequest request, String type) 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(type, 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.add(indexName, type, json1);
        docService.add(indexName, type, json2);
        docService.add(indexName, type, json3);
        docService.add(indexName, type, json4);
        docService.add(indexName, type, json5);
    }

    @Test
    public void testGeoDistanceQuery() throws IOException {
        //距厦门500公里以内的城市
        geoQuery.geoDistanceQuery(indexName,type,24.46667,118.0000,500);
    }
}

7.5 geo_bounding_box

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

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

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

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

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

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

{
	"query": {
		"geo_bounding_box": {
			"location": {
				"top_left": [-74.0, 40.8],
				"bottom_right": [-73.0, 40.715]
			},
			"validation_method": "STRICT",
			"type": "MEMORY",
			"ignore_unmapped": false,
			"boost": 1.0
		}
	}
}

JAVA代码示例:com.javablog.elasticsearch.query.impl.GeoQueryImpl

/**
 * 搜索矩形范围内的文档
 * @param indexName    索引
 * @param typeName     TYPE
 * @param top          最上边的纬度
 * @param left         最左边的经度
 * @param bottom       最下边的纬度 
 * @param right        右边的经度 
 * @throws IOException
 */
public void geoBoundingBoxQuery(String indexName, String typeName, double top,double left,double bottom,double right) throws IOException {
    SearchRequest searchRequest = new SearchRequest(indexName);
    searchRequest.types(typeName);
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    GeoBoundingBoxQueryBuilder address = QueryBuilders.geoBoundingBoxQuery("location").setCorners(top, left, bottom, right);
    searchSourceBuilder.query(address);
    searchRequest.source(searchSourceBuilder);
    log.info("source:" + searchRequest.source());
    SearchResponse searchResponse =  restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
    SearchHits hits = searchResponse.getHits();
    System.out.println(hits.totalHits);
    SearchHit[] h =  hits.getHits();
    for (SearchHit hit : h) {
        System.out.println(hit.getSourceAsMap());
    }
}

演示用例:com.javablog.elasticsearch.test.document.GeoQueryTest

@Test
public void testGeoBoundingBoxh() throws IOException {
    geoQuery.geoBoundingBoxQuery(indexName,type,40.8,-74.0,40.715,-73.0);
}

7.6 geo_polygon

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

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

{
	"query": {
		"geo_polygon": {
			"location": {
				"points": [
					[-72.0, 42.0],
					[117.0, 39.0],
					[117.0, 40.0],
					[-72.0, 42.0]
				]
			},
			"validation_method": "STRICT",
			"ignore_unmapped": false,
			"boost": 1.0
		}
	}
}

JAVA代码示例:com.javablog.elasticsearch.query.impl.GeoQueryImpl

public void geoPolygonQuery(String indexName, String typeName) throws IOException {
    SearchRequest searchRequest = new SearchRequest(indexName);
    searchRequest.types(typeName);
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    List<GeoPoint> points=new ArrayList<GeoPoint>();
    points.add(new GeoPoint(42, -72));
    points.add(new GeoPoint(39, 117));
    points.add(new GeoPoint(40, 117));
    GeoPolygonQueryBuilder geoPolygonQueryBuilder = QueryBuilders.geoPolygonQuery("location", points);
    searchSourceBuilder.query(geoPolygonQueryBuilder);
    searchRequest.source(searchSourceBuilder);
    log.info("source:" + searchRequest.source());
    SearchResponse searchResponse =  restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
    SearchHits hits = searchResponse.getHits();
    System.out.println(hits.totalHits);
    SearchHit[] h =  hits.getHits();
    for (SearchHit hit : h) {
        System.out.println(hit.getSourceAsMap());
    }
}

演示用例:com.javablog.elasticsearch.test.document.GeoQueryTest

@Test
public void testPolygonQuery() throws IOException {
    geoQuery.geoPolygonQuery(indexName,type);
}

完整代码:https://github.com/chutianmen/elasticsearch-examples

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值