地理位置服务:ElasticSearch Geo实现 - 使用样例
地理位置服务:ElasticSearch Geo实现 - 使用样例
搭建Elasticsearch集群
#单个进程中的最大线程数
vim /etc/sysctl.conf
vm.max_map_count=262144
#立即生效
/sbin/sysctl -p
mkdir /itcast/tanhua/es-cluster/node01 -p
mkdir /itcast/tanhua/es-cluster/node02 -p
mkdir /itcast/tanhua/es-cluster/node03 -p
#复制资料目录下的jvm.options到node01、node02、node03目录
#在node01目录下,创建elasticsearch.yml文件,并输入如下内容:
cluster.name: es-tanhua-cluster
node.name: node01
node.master: true
node.data: true
network.host: 192.168.56.11
http.port: 9200
discovery.zen.ping.unicast.hosts: ["192.168.56.11"]
discovery.zen.minimum_master_nodes: 2
http.cors.enabled: true
http.cors.allow-origin: "*"
#在node02目录下,创建elasticsearch.yml文件,并输入如下内容:
cluster.name: es-tanhua-cluster
node.name: node02
node.master: true
node.data: true
network.host: 192.168.56.11
http.port: 9201
discovery.zen.ping.unicast.hosts: ["192.168.56.11"]
discovery.zen.minimum_master_nodes: 2
http.cors.enabled: true
http.cors.allow-origin: "*"
#在node03目录下,创建elasticsearch.yml文件,并输入如下内容:
cluster.name: es-tanhua-cluster
node.name: node03
node.master: true
node.data: true
network.host: 192.168.56.11
http.port: 9202
discovery.zen.ping.unicast.hosts: ["192.168.56.11"]
discovery.zen.minimum_master_nodes: 2
http.cors.enabled: true
http.cors.allow-origin: "*"
#创建容器
docker create --restart=always --name es-node01 --net host -v /itcast/tanhua/es-cluster/node01/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml -v /itcast/tanhua/es-cluster/node01/jvm.options:/usr/share/elasticsearch/config/jvm.options -v es-cluster-node01-data:/usr/share/elasticsearch/data elasticsearch:6.5.4
docker create --restart=always --name es-node02 --net host -v /itcast/tanhua/es-cluster/node02/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml -v /itcast/tanhua/es-cluster/node02/jvm.options:/usr/share/elasticsearch/config/jvm.options -v es-cluster-node02-data:/usr/share/elasticsearch/data elasticsearch:6.5.4
docker create --restart=always --name es-node03 --net host -v /itcast/tanhua/es-cluster/node03/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml -v /itcast/tanhua/es-cluster/node03/jvm.options:/usr/share/elasticsearch/config/jvm.options -v es-cluster-node03-data:/usr/share/elasticsearch/data elasticsearch:6.5.4
#启动容器
docker start es-node01 es-node02 es-node03
#或单个启动并查看日志
docker start es-node01 && docker logs -f es-node01
docker start es-node02 && docker logs -f es-node02
docker start es-node03 && docker logs -f es-node03
测试:
06.Elasticsearch实现位置查询之工程搭建
pom.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<artifactId>my-tanhua</artifactId>
<groupId>cn.itcast.tanhua</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>my-tanhua-elasticsearch</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--其他工具包依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
application.properties
spring.application.name = itcast-tanhua-elasticsearch
server.port = 18082
spring.data.elasticsearch.cluster-name=es-tanhua-cluster
spring.data.elasticsearch.cluster-nodes=192.168.56.11:9300,192.168.56.11:9301,192.168.56.11:9302
log4j.properties
log4j.rootLogger=DEBUG,A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n
启动类:
package com.tanhua.es;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ESApplication {
public static void main(String[] args) {
SpringApplication.run(ESApplication.class, args);
}
}
07.Elasticsearch实现位置查询之编码实现(更新用户地理位置)
UserLocationES
package com.tanhua.es.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.elasticsearch.common.geo.GeoPoint;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.GeoPointField;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "tanhua", type = "user_location", shards = 6, replicas = 2)
public class UserLocationES {
@Id
private Long userId; //用户id
@GeoPointField
private GeoPoint location; //x:经度 y:纬度
@Field(type = FieldType.Keyword)
private String address; //位置描述
@Field(type = FieldType.Long)
private Long created; //创建时间
@Field(type = FieldType.Long)
private Long updated; //更新时间
@Field(type = FieldType.Long)
private Long lastUpdated; //上次更新时间
}
UserLocationController
package com.tanhua.es.controller;
import com.tanhua.es.pojo.UserLocationES;
import com.tanhua.es.service.UserLocationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("es/user/location")
public class UserLocationController {
@Autowired
private UserLocationService userLocationService;
/**
* 更新用户的地理位置
*
* @param param
* @return
*/
@PostMapping
public ResponseEntity<UserLocationES> updateUserLocation(@RequestBody Map<String, Object> param) {
try {
Long userId = Long.valueOf(param.get("userId").toString());
Double longitude = Double.valueOf(param.get("longitude").toString());
Double latitude = Double.valueOf(param.get("latitude").toString());
String address = param.get("address").toString();
boolean result = this.userLocationService.updateUserLocation(userId, longitude, latitude, address);
if (result) {
return ResponseEntity.ok(null);
}
} catch (NumberFormatException e) {
e.printStackTrace();
}
return ResponseEntity.status(HttpStatus.INSUFFICIENT_STORAGE).build();
}
}
UserLocationService
package com.tanhua.es.service;
import com.tanhua.es.pojo.UserLocationES;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.common.geo.GeoPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class UserLocationService {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
/**
* 更新用户的地理位置
*
* @param userId
* @param longitude
* @param latitude
* @param address
* @return
*/
public boolean updateUserLocation(Long userId, Double longitude, Double latitude, String address) {
try {
if (!this.elasticsearchTemplate.indexExists("tanhua")) {
// 创建索引
this.elasticsearchTemplate.createIndex(UserLocationES.class);
}
if (!this.elasticsearchTemplate.typeExists("tanhua", "user_location")) {
// 创建type
this.elasticsearchTemplate.putMapping(UserLocationES.class);
}
GetQuery getQuery = new GetQuery();
getQuery.setId(userId.toString());
UserLocationES ul = this.elasticsearchTemplate.queryForObject(getQuery, UserLocationES.class);
if (null == ul) {
UserLocationES userLocationES = new UserLocationES();
userLocationES.setLocation(new GeoPoint(latitude, longitude));
userLocationES.setAddress(address);
userLocationES.setUserId(userId);
userLocationES.setCreated(System.currentTimeMillis());
userLocationES.setUpdated(userLocationES.getCreated());
userLocationES.setLastUpdated(userLocationES.getCreated());
IndexQuery indexQuery = new IndexQueryBuilder().withObject(userLocationES).build();
this.elasticsearchTemplate.index(indexQuery);
} else {
Map<String, Object> map = new HashMap<>();
map.put("lastUpdated", ul.getUpdated());
map.put("updated", System.currentTimeMillis());
map.put("address", address);
map.put("location", new GeoPoint(latitude, longitude));
UpdateRequest updateRequest = new UpdateRequest();
updateRequest.doc(map);
UpdateQuery updateQuery = new UpdateQueryBuilder()
.withId(userId.toString())
.withClass(UserLocationES.class)
.withUpdateRequest(updateRequest).build();
this.elasticsearchTemplate.update(updateQuery);
}
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
08.Elasticsearch实现位置查询之编码实现(更新用户地理位置的单元则试)
package com.tanhua.es.service;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class TestUserLocationService {
@Autowired
private UserLocationService userLocationService;
@Test
public void testUpdateUserLocation() {
this.userLocationService.updateUserLocation(1L, 121.512253, 31.24094, "金茂大厦");
this.userLocationService.updateUserLocation(2L, 121.506377, 31.245105, "东方明珠广播电视塔");
this.userLocationService.updateUserLocation(10L, 121.508815, 31.243844, "陆家嘴地铁站");
this.userLocationService.updateUserLocation(12L, 121.511999, 31.239185, "上海中心大厦");
this.userLocationService.updateUserLocation(25L, 121.493444, 31.240513, "上海市公安局");
this.userLocationService.updateUserLocation(27L, 121.494108, 31.247011, "上海外滩美术馆");
this.userLocationService.updateUserLocation(30L, 121.462452, 31.253463, "上海火车站");
this.userLocationService.updateUserLocation(32L, 121.81509, 31.157478, "上海浦东国际机场");
this.userLocationService.updateUserLocation(34L, 121.327908, 31.20033, "虹桥火车站");
this.userLocationService.updateUserLocation(38L, 121.490155, 31.277476, "鲁迅公园");
this.userLocationService.updateUserLocation(40L, 121.425511, 31.227831, "中山公园");
this.userLocationService.updateUserLocation(43L, 121.594194, 31.207786, "张江高科");
}
}
09.Elasticsearch实现位置查询之编码实现(查询)
UserLocationController
/**
* 查询用户的地理位置
*
* @param userId
* @return
*/
@GetMapping("{userId}")
public ResponseEntity<UserLocationES> queryUserLocation(@PathVariable("userId") Long userId) {
try {
UserLocationES userLocationES = this.userLocationService.queryByUserId(userId);
if (null == userLocationES) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
return ResponseEntity.ok(userLocationES);
} catch (Exception e) {
e.printStackTrace();
}
return ResponseEntity.status(HttpStatus.INSUFFICIENT_STORAGE).build();
}
/**
* 搜索附近的人
*
* @param param
* @return
*/
@PostMapping("list")
public ResponseEntity<List<UserLocationES>> queryUserFromLocation(@RequestBody Map<String, Object> param) {
try {
Double longitude = Double.valueOf(param.get("longitude").toString());
Double latitude = Double.valueOf(param.get("latitude").toString());
Double distance = Double.valueOf(param.get("distance").toString());
Integer page = param.get("page") == null ? 1 : Integer.valueOf(param.get("page").toString());
Integer pageSize = param.get("pageSize") == null ? 100 : Integer.valueOf(param.get("pageSize").toString());
Page<UserLocationES> userLocationES = this.userLocationService.queryUserFromLocation(longitude, latitude, distance, page, pageSize);
return ResponseEntity.ok(userLocationES.getContent());
} catch (NumberFormatException e) {
e.printStackTrace();
}
return ResponseEntity.status(HttpStatus.INSUFFICIENT_STORAGE).build();
}
UserLocationService
/**
* 查询用户的位置信息
*
* @param userId
* @return
*/
public UserLocationES queryByUserId(Long userId) {
GetQuery getQuery = new GetQuery();
getQuery.setId(userId.toString());
return this.elasticsearchTemplate.queryForObject(getQuery, UserLocationES.class);
}
/**
* 根据位置搜索
*
* @param longitude 经度
* @param latitude 纬度
* @param distance 距离(米)
* @param page 页数
* @param pageSize 页面大小
*/
public Page<UserLocationES> queryUserFromLocation(Double longitude, Double latitude, Double distance, Integer page, Integer pageSize) {
String fieldName = "location";
// 实现了SearchQuery接口,用于组装QueryBuilder和SortBuilder以及Pageable等
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
// 分页
PageRequest pageRequest = PageRequest.of(page - 1, pageSize);
nativeSearchQueryBuilder.withPageable(pageRequest);
// 定义bool查询
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
// 以某点为中心,搜索指定范围
GeoDistanceQueryBuilder distanceQueryBuilder = new GeoDistanceQueryBuilder(fieldName);
distanceQueryBuilder.point(latitude, longitude);
// 定义查询单位:公里
distanceQueryBuilder.distance(distance / 1000, DistanceUnit.KILOMETERS);
boolQueryBuilder.must(distanceQueryBuilder);
nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
// 按距离升序
GeoDistanceSortBuilder distanceSortBuilder =
new GeoDistanceSortBuilder(fieldName, latitude, longitude);
distanceSortBuilder.unit(DistanceUnit.KILOMETERS); //设置单位
distanceSortBuilder.order(SortOrder.ASC); //正序排序
nativeSearchQueryBuilder.withSort(distanceSortBuilder);
return this.elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), UserLocationES.class);
}
TestUserLocationService
@Test
public void testQueryByUserId() {
UserLocationES userLocationES = this.userLocationService.queryByUserId(1l);
System.out.println(userLocationES);
}
@Test
public void testQuery() {
Page<UserLocationES> userLocationPage = this.userLocationService.queryUserFromLocation(121.512253, 31.24094, 1000d, 1, 100);
userLocationPage.forEach(userLocationES -> {
System.out.println(userLocationES);
double distance = GeoDistance.ARC.calculate(31.24094, 121.512253, userLocationES.getLocation().getLat(), userLocationES.getLocation().getLon(), DistanceUnit.METERS);
System.out.println("距离我 : " + distance + "米");
});
}
nginx.conf
location /user {
client_max_body_size 300m;
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_pass http://127.0.0.1:18080;
}
location /users/header {
client_max_body_size 300m;
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_pass http://127.0.0.1:18080;
}
location /es/ {
client_max_body_size 300m;
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_pass http://127.0.0.1:18082;
}
location / {
client_max_body_size 300m;
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_pass http://127.0.0.1:18081;
}
10.Elasticsearch版的dubbo服务实现
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
配置RestTemplateConfig
package com.tanhua.dubbo.server.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.Charset;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
RestTemplate restTemplate = new RestTemplate(factory);
// 支持中文编码
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(Charset.forName("UTF-8")));
return restTemplate;
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(5000);//单位为ms
factory.setConnectTimeout(5000);//单位为ms
return factory;
}
}
实现接口
application.properties增加配置:
server.port = 18083
# ES服务地址
es.server.url = http://127.0.0.1/es/
ESUserLocationApiImpl
package com.tanhua.dubbo.server.api;
import com.alibaba.dubbo.config.annotation.Service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.tanhua.dubbo.server.vo.UserLocationVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service(version = "2.0.0")
public class ESUserLocationApiImpl implements UserLocationApi {
private static final ObjectMapper MAPPER = new ObjectMapper();
@Autowired
private RestTemplate restTemplate;
@Value("${es.server.url}")
private String esServerUrl;
@Override
public String updateUserLocation(Long userId, Double longitude, Double latitude, String address) {
String url = this.esServerUrl + "user/location/";
Map<String, Object> param = new HashMap<>();
param.put("longitude", longitude);
param.put("latitude", latitude);
param.put("userId", userId);
param.put("address", address);
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> httpEntity = new HttpEntity<>(MAPPER.writeValueAsString(param), headers);
ResponseEntity<Void> responseEntity = this.restTemplate.postForEntity(url, httpEntity, Void.class);
if (responseEntity.getStatusCodeValue() == 200) {
return "ok";
}
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
@Override
public UserLocationVo queryByUserId(Long userId) {
String url = this.esServerUrl + "user/location/" + userId;
ResponseEntity<String> responseEntity = this.restTemplate.getForEntity(url, String.class);
if (responseEntity.getStatusCodeValue() != 200) {
return null;
}
try {
String body = responseEntity.getBody();
JsonNode jsonNode = MAPPER.readTree(body);
UserLocationVo userLocationVo = new UserLocationVo();
userLocationVo.setLatitude(jsonNode.get("location").get("lat").asDouble());
userLocationVo.setLongitude(jsonNode.get("location").get("lon").asDouble());
userLocationVo.setUserId(userId);
userLocationVo.setAddress(jsonNode.get("address").asText());
userLocationVo.setId(userId.toString());
userLocationVo.setCreated(jsonNode.get("created").asLong());
userLocationVo.setUpdated(jsonNode.get("updated").asLong());
userLocationVo.setLastUpdated(jsonNode.get("lastUpdated").asLong());
return userLocationVo;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public List<UserLocationVo> queryUserFromLocation(Double longitude, Double latitude, Integer range) {
String url = this.esServerUrl + "user/location/list";
Map<String, Object> param = new HashMap<>();
param.put("longitude", longitude);
param.put("latitude", latitude);
param.put("distance", range);
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> httpEntity = new HttpEntity<>(MAPPER.writeValueAsString(param), headers);
ResponseEntity<String> responseEntity = this.restTemplate.postForEntity(url, httpEntity, String.class);
if (responseEntity.getStatusCodeValue() != 200) {
return null;
}
String body = responseEntity.getBody();
ArrayNode jsonNode = (ArrayNode) MAPPER.readTree(body);
List<UserLocationVo> result = new ArrayList<>();
for (JsonNode node : jsonNode) {
UserLocationVo userLocationVo = new UserLocationVo();
userLocationVo.setLatitude(node.get("location").get("lat").asDouble());
userLocationVo.setLongitude(node.get("location").get("lon").asDouble());
userLocationVo.setUserId(node.get("userId").asLong());
userLocationVo.setAddress(node.get("address").asText());
userLocationVo.setId(userLocationVo.getUserId().toString());
userLocationVo.setCreated(node.get("created").asLong());
userLocationVo.setUpdated(node.get("updated").asLong());
userLocationVo.setLastUpdated(node.get("lastUpdated").asLong());
result.add(userLocationVo);
}
return result;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
server引用
测试
可以看到结果和之前是一样的。