获取附近的人
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