地理位置服务: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

测试:1572060102051

1572060178506

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引用

1572102333097

测试

1572102354711

1572102365009

可以看到结果和之前是一样的。

posted @ 2021-01-26 11:30  60kmph  阅读(825)  评论(0编辑  收藏  举报