文档章节

使用elasticsearch1.5.2查询指定距离范围内的城市(类似微信附近的人)

凯文加内特
 凯文加内特
发布于 2015/10/10 17:47
字数 1799
阅读 1375
收藏 8

获取附近的人

mongodb实现方式:http://www.infoq.com/cn/articles/depth-study-of-Symfony2

mysql实现功能:http://www.wubiao.info/470

在此使用elasticsearch,简称es实现:

pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.heli</groupId>
	<artifactId>ElasticSearch</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>ElasticSearch</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<es.version>1.5.2</es.version>
		<lucene.maven.version>4.10.4</lucene.maven.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.elasticsearch</groupId>
			<artifactId>elasticsearch</artifactId>
			<version>${es.version}</version>
		</dependency>
		
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.8.2</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
</project>

实体City:

package com.heli.es;

public class City {

	private long id;// id
	private String city;// 城市名
	private double lat;// 纬度
	private double lon;// 经度
	private double[] location;// 经纬度数组,第一个元素纬度,第二个元素经度
	private String title;// 标题

	public City(long id, String city, double lon, double lat, String title) {
		super();
		this.id = id;
		this.city = city;
		this.lat = lat;
		this.lon = lon;
		this.title = title;
	}

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public double getLat() {
		return lat;
	}

	public void setLat(double lat) {
		this.lat = lat;
	}

	public double getLon() {
		return lon;
	}

	public void setLon(double lon) {
		this.lon = lon;
	}

	public String getCity() {
		return city;
	}

	public void setCity(String city) {
		this.city = city;
	}

	public double[] getLocation() {
		return location;
	}

	public void setLocation(double[] location) {
		this.location = location;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

}

测试类:

package com.heli.es;

import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.FilterBuilders.geoDistanceRangeFilter;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.Requests;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.query.FilterBuilder;
import org.elasticsearch.index.query.QueryStringQueryBuilder;
import org.elasticsearch.node.Node;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.sort.GeoDistanceSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import static org.elasticsearch.node.NodeBuilder.*;

public class ES {

	// 创建索引
	public static void createIndex(String indexName, String indexType) throws IOException {
		Client esClient = new TransportClient().addTransportAddress(new InetSocketTransportAddress("127.0.0.1", 9300));
		// 创建Mapping
		XContentBuilder mapping = createMapping(indexType);
		System.out.println("mapping:" + mapping.string());
		// 创建一个空索引
		esClient.admin().indices().prepareCreate(indexName).execute().actionGet();
		PutMappingRequest putMapping = Requests.putMappingRequest(indexName).type(indexType).source(mapping);
		PutMappingResponse response = esClient.admin().indices().putMapping(putMapping).actionGet();
		if (!response.isAcknowledged()) {
			System.out.println("Could not define mapping for type [" + indexName + "]/[" + indexType + "].");
		} else {
			System.out.println("Mapping definition for [" + indexName + "]/[" + indexType + "] succesfully created.");
		}
	}

	// 创建mapping
	public static XContentBuilder createMapping(String indexType) {
		XContentBuilder mapping = null;
		try {
			mapping = jsonBuilder().startObject()
					// 索引库名(类似数据库中的表)
					.startObject(indexType).startObject("properties")
					// ID
					.startObject("id").field("type", "long").endObject()
					// 城市
					.startObject("city").field("type", "string").endObject()
					// 位置
					.startObject("location").field("type", "geo_point").endObject()
					// 标题
					.startObject("title").field("type", "string").endObject()

			.endObject().endObject().endObject();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return mapping;
	}

	// 添加数据
	public static Integer addIndexData(String indexName, String indexType) {
		Client client = new TransportClient().addTransportAddress(new InetSocketTransportAddress("127.0.0.1", 9300));
		List<String> cityList = new ArrayList<String>();

		City city1 = new City(1L, "北京", 116.395645, 39.929986, "中国人民站起来了,北京人民可以天天站在天安门广场吃烤鸭了");
		City city2 = new City(2L, "天津", 117.210813, 39.143931, "中国人民站起来了,天津人民可以天天在迎宾广场吃麻花了");
		City city3 = new City(3L, "青岛", 120.384428, 36.105215, "中国人民站起来了,青岛人民可以天天在五四广场吃海鲜了,虾TM就是贵点儿,38元一只,38元最后一次!!!最后一次,不要错过今天");
		City city4 = new City(4L, "哈尔滨", 126.657717, 45.773225, "中国人民站起来了,哈尔滨人民可以天天站在索菲亚广场吃红肠了");
		City city5 = new City(5L, "乌鲁木齐", 87.564988, 43.840381, "中国人民站起来了,乌鲁木齐人民可以天天在人民广场啃羊腿了");
		City city6 = new City(6L, "三亚", 109.522771, 18.257776, "中国人民站起来了,三亚人民可以天天在明珠广场吃鲍鱼了,三亚人民这次没丢脸,脸让青岛政府去丢吧,让他们创城去吧!");

		cityList.add(obj2JsonUserData(city1));
		cityList.add(obj2JsonUserData(city2));
		cityList.add(obj2JsonUserData(city3));
		cityList.add(obj2JsonUserData(city4));
		cityList.add(obj2JsonUserData(city5));
		cityList.add(obj2JsonUserData(city6));

		// 创建索引库
		List<IndexRequest> requests = new ArrayList<IndexRequest>();
		for (int i = 0; i < cityList.size(); i++) {
			IndexRequest request = client.prepareIndex(indexName, indexType).setSource(cityList.get(i)).request();
			requests.add(request);
		}

		// 批量创建索引
		BulkRequestBuilder bulkRequest = client.prepareBulk();
		for (IndexRequest request : requests) {
			bulkRequest.add(request);
		}

		BulkResponse bulkResponse = bulkRequest.execute().actionGet();
		if (bulkResponse.hasFailures()) {
			System.out.println("批量创建索引错误!");
		}
		return bulkRequest.numberOfActions();
	}

	public static String obj2JsonUserData(City city) {
		String jsonData = null;
		try {
			// 使用XContentBuilder创建json数据
			XContentBuilder jsonBuild = XContentFactory.jsonBuilder();
			jsonBuild.startObject().field("id", city.getId()).field("city", city.getCity()).startArray("location").value(city.getLat()).value(city.getLon()).endArray().field("title", city.getTitle())
					.endObject();
			jsonData = jsonBuild.string();
			System.out.println(jsonData);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return jsonData;
	}

	// 模糊查询
	public static void query(String query) {
		Client client = new TransportClient().addTransportAddress(new InetSocketTransportAddress("127.0.0.1", 9300));
		QueryStringQueryBuilder qsqb = new QueryStringQueryBuilder(query);
		// qsqb.analyzer("ik").field("title");
		qsqb.field("title");
		client.admin().indices().prepareRefresh().execute().actionGet();

		SearchResponse searchResponse = client.prepareSearch("testes").setTypes("xq").setQuery(qsqb)
				// .setScroll(new TimeValue(60000))
				.addFields("id", "title", "updatetime")
				// .addSort("updatetime", SortOrder.DESC)
				.addSort("_score", SortOrder.DESC)
				// .addHighlightedField("title")
				.setHighlighterEncoder("UTF-8").execute().actionGet();
		// 搜索耗时
		Float usetime = searchResponse.getTookInMillis() / 1000f;
		// 命中记录数
		Long hits = searchResponse.getHits().totalHits();
		System.out.println("查询到记录数=" + hits);

		for (SearchHit hit : searchResponse.getHits()) {
			// 打分
			Float score = hit.getScore();
			Integer id = Integer.parseInt(hit.getFields().get("id").value().toString());
			String title = hit.getFields().get("title").value().toString();
			System.out.println(title);
		}
	}

	// 获取附近的城市
	public static void testGetNearbyCities(Client client, String index, String type, double lat, double lon) {
		SearchRequestBuilder srb = client.prepareSearch(index).setTypes(type);
		// wx4g0th9p0gk 为北京的geohash 范围为lt(小于) 1500km内的数据
		FilterBuilder builder = geoDistanceRangeFilter("location").point(lon, lat).from("1km").to("1000km").optimizeBbox("memory").geoDistance(GeoDistance.PLANE);
		srb.setPostFilter(builder);
		// 获取距离多少公里 这个才是获取点与点之间的距离的
		GeoDistanceSortBuilder sort = SortBuilders.geoDistanceSort("location");
		sort.unit(DistanceUnit.KILOMETERS);
		sort.order(SortOrder.ASC);
		sort.point(lon, lat);
		srb.addSort(sort);

		SearchResponse searchResponse = srb.execute().actionGet();

		SearchHits hits = searchResponse.getHits();
		SearchHit[] searchHists = hits.getHits();
		System.out.println("北京附近的城市(" + hits.getTotalHits() + "个):");
		for (SearchHit hit : searchHists) {
			String city = (String) hit.getSource().get("city");
			String title = (String) hit.getSource().get("title");
			// 获取距离值,并保留两位小数点
			BigDecimal geoDis = new BigDecimal((Double) hit.getSortValues()[0]);
			Map<String, Object> hitMap = hit.getSource();
			// 在创建MAPPING的时候,属性名的不可为geoDistance。
			hitMap.put("geoDistance", geoDis.setScale(2, BigDecimal.ROUND_HALF_DOWN));
			System.out.println(city + "距离北京" + hit.getSource().get("geoDistance") + DistanceUnit.KILOMETERS.toString() + "---" + title);
		}
		
	}

	public static void main(String[] args) throws IOException {
		Client client = new TransportClient().addTransportAddress(new InetSocketTransportAddress("127.0.0.1", 9300));
		String index = "testes";
		String type = "xq";
		// createIndex("testes", "xq");
		// addIndexData("testes", "xq");
		//
		double lat = 39.929986;
		double lon = 116.395645;
		long start = System.currentTimeMillis();
		testGetNearbyCities(client, index, type, lat, lon);
		// query("*海鲜*");
		long end = System.currentTimeMillis();
		System.out.println((end - start) + "毫秒");
		client.close();
	}
}

输出结果:

北京附近的城市(2个):
天津距离北京98.69km---中国人民站起来了,天津人民可以天天在迎宾广场吃麻花了
青岛距离北京486.53km---中国人民站起来了,青岛人民可以天天在五四广场吃海鲜了,虾TM就是贵点儿,38元一只,38元最后一次!!!最后一次,不要错过今天
1192毫秒

注:server 和client版本使用的是1.5.2,如果server版本用elasticsearch-rtf-master,sort的时候总是报:

Exception in thread "main" org.elasticsearch.action.search.SearchPhaseExecutionException: Failed to execute phase [query], all shards failed; shardFailures {[alee59cPQNuzRP4go6-5vw][testes][4]: SearchParseException[[testes][4]: from[-1],size[-1]: Parse Failure [Failed to parse source [{"post_filter":{"geo_distance_range":{"location":"wx4g0th9p0gk","from":"1km","to":"2000km","include_lower":true,"include_upper":true,"distance_type":"arc","optimize_bbox":"memory"}},"sort":[{"_geo_distance":{"location":[{"lat":39.929986,"lon":116.395645}],"unit":"km","distance_type":"arc"}}]}]]]; nested: ElasticsearchParseException[Numeric value expected]; }{[alee59cPQNuzRP4go6-5vw][testes][0]: SearchParseException[[testes][0]: from[-1],size[-1]: Parse Failure [Failed to parse source [{"post_filter":{"geo_distance_range":{"location":"wx4g0th9p0gk","from":"1km","to":"2000km","include_lower":true,"include_upper":true,"distance_type":"arc","optimize_bbox":"memory"}},"sort":[{"_geo_distance":{"location":[{"lat":39.929986,"lon":116.395645}],"unit":"km","distance_type":"arc"}}]}]]]; nested: ElasticsearchParseException[Numeric value expected]; }{[alee59cPQNuzRP4go6-5vw][testes][1]: SearchParseException[[testes][1]: from[-1],size[-1]: Parse Failure [Failed to parse source [{"post_filter":{"geo_distance_range":{"location":"wx4g0th9p0gk","from":"1km","to":"2000km","include_lower":true,"include_upper":true,"distance_type":"arc","optimize_bbox":"memory"}},"sort":[{"_geo_distance":{"location":[{"lat":39.929986,"lon":116.395645}],"unit":"km","distance_type":"arc"}}]}]]]; nested: ElasticsearchParseException[Numeric value expected]; }{[alee59cPQNuzRP4go6-5vw][testes][2]: SearchParseException[[testes][2]: from[-1],size[-1]: Parse Failure [Failed to parse source [{"post_filter":{"geo_distance_range":{"location":"wx4g0th9p0gk","from":"1km","to":"2000km","include_lower":true,"include_upper":true,"distance_type":"arc","optimize_bbox":"memory"}},"sort":[{"_geo_distance":{"location":[{"lat":39.929986,"lon":116.395645}],"unit":"km","distance_type":"arc"}}]}]]]; nested: ElasticsearchParseException[Numeric value expected]; }{[alee59cPQNuzRP4go6-5vw][testes][3]: SearchParseException[[testes][3]: from[-1],size[-1]: Parse Failure [Failed to parse source [{"post_filter":{"geo_distance_range":{"location":"wx4g0th9p0gk","from":"1km","to":"2000km","include_lower":true,"include_upper":true,"distance_type":"arc","optimize_bbox":"memory"}},"sort":[{"_geo_distance":{"location":[{"lat":39.929986,"lon":116.395645}],"unit":"km","distance_type":"arc"}}]}]]]; nested: ElasticsearchParseException[Numeric value expected]; }

 换成1.5.2结果就好了,还有

.point(lon, lat)

  必须经度在前,纬度在后,不然查询为空,跟一朋友聊说这个可能是个bug

  另外查询速度太慢,应该哪个地方配置的问题,回头下周研究研究,周末愉快。

经过试验,原来创建client消耗了1秒左右,查询80毫秒,非常快

借鉴http://blog.csdn.net/loveisnull/article/details/45914115

© 著作权归作者所有

共有 人打赏支持
凯文加内特
粉丝 330
博文 667
码字总数 87615
作品 0
青岛
后端工程师
MongoDB索引

1.创建索引 --单索引 > db.test.ensureIndex({"username":1}) 复合索引 --数字1表示username键的索引按升序存储,-1表示age键的索引按照降序方式存储。 >db.test.ensureIndex({"username":1...

满小茂
2016/09/02
26
0
Redis新特性GEOHASH

一、简言 Redis 的 GEO 特性将在 Redis 3.2 版本释出, 这个功能可以将用户给定的地理位置信息储存起来, 并对这些信息进行操作 将指定的地理空间项目(纬度,经度,名称)添加到指定的键。数...

DBAspace
07/03
0
0
结合MongoDB开发LBS应用

简介 随着近几年各类移动终端的迅速普及,基于地理位置的服务(LBS)和相关应用也越来越多,而支撑这些应用的最基础技术之一,就是基于地理位置信息的处理。我所在的项目也正从事相关系统的...

凯文加内特
2015/09/09
318
0
mongodb地理信息应用

二维空间索引 MongoDB支持二维空间索引,这是设计时考虑到基于位置的查询。例如“找到离目标位置最近的N条记录”。并且可以有效地作为附加条件过滤。 如果需要使用这种索引,应确定对象中存储...

引鸩怼孑
2016/03/03
126
0
redis3.2新功能--GEO地理位置命令介绍

一、概述 redis3.2发布rc版本已经有一段时间了,估计RedisConf 2016左右,3.2版本就能release了。3.2版本中增加的最大功能就是对GEO(地理位置)的支持。说起redis的GEO特性,最大的贡献还是...

IT--小哥
07/19
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Spring Cloud Gateway真的有那么差吗?

前言 Spring Cloud从一开始最受大家质疑的就是网关性能,那是由于Spring Cloud最初选择了使用Netflix几年前开源的Zuul作为基础,而高性能版的Zuul 2在经过了多次跳票之后,对于Spring这样的整...

Java小铺
37分钟前
1
0
SpringBoot远程调试,远程debug你的线上项目

开发环境中代码出错了,可以利用IDE的debug功能来进行调试。那线上环境出错呢? 一、假设我们的项目是部署在tomcat中,那我们就需要对tomcat进行一定对配置,配置如下。 1. windows系统中,找...

nonnetta
42分钟前
0
0
JAVA秒杀优化方向

秒杀优化方向 将请求尽量拦截在系统上游:传统秒杀系统之所以挂,请求都压倒了后端数据层,数据读写锁冲突严重,几乎所有请求都超时,流量虽大,下单成功的有效流量甚小,我们可以通过限流、...

小贱是个程序员
50分钟前
0
0
C# 统计字符串中大写字母和小写字母的个数

static void Main() { int count1 = 0; int count2 = 0; Console.WriteLine("请输入字符串"); string str = Convert.ToString(Consol......

熊二的爸爸是谁
52分钟前
0
0
分布式服务框架之远程通讯技术及原理分析

在分布式服务框架中,一个最基础的问题就是远程服务是怎么通讯的,在Java领域中有很多可实现远程通讯的技术,例如:RMI、MINA、ESB、Burlap、Hessian、SOAP、EJB和JMS等,这些名词之间到底是...

老道士
58分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部