文章目录

  • 前言
  • 一、GeoPoint和GeoShape的简单介绍
  • 二、整合springboot的具体使用
  • 1.引入依赖
  • 2.yml配置
  • 3.GeoPoint场景和相关代码
  • 3.1 索引实体类
  • 3.2 创建索引以及映射
  • 3.3 读取csv文件内容存入elasticsearch
  • 3.4 根据经纬度搜索附近POI点
  • 3.5 调用样例
  • 3.6 根据名称搜索POI点
  • 3.7 调用样例
  • 4.GeoShape场景和相关代码
  • 4.1 根据经纬度查询行政区域
  • 4.2 调用样例
  • 三、总结



前言

前面的文章提到了,如果要实现附近的人或者获取POI点的功能,可以使用redis的geo类型。但是提前得把这些数据的信息存进redis,redis是基于内存查找的,相较于硬盘,内存的资源显得更贵一点,所以将大量的数据存入redis显然不是很合适,这里我们介绍另一种方式来实现此功能,使用elasticsearch的GeoPoint和GeoShape类型。


一、GeoPoint和GeoShape的简单介绍

GeoPoint和GeoShape是elasticsearch中表示地理位置的两种方式

GeoPoint,顾名思义是针对于坐标点的地理位置操作。当你有很多不相关的坐标点时,你可以以某个坐标点为圆心,计算出固定半径距离的其他坐标点的信息并按照距离排序。

GeoShape则是跟形状有关,即当你有一组坐标点可以在地图上组成一个或多个闭合图形时,可以使用这个命令。然后你可以随便指定一个坐标点判断是否在你生成的这个闭合图形之内。

上面说的情况是本文将要介绍的用法,更多其他强大的用法大家可以自行去官网体会!

二、整合springboot的具体使用

1.引入依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.locationtech.spatial4j/spatial4j -->
        <dependency>
            <groupId>org.locationtech.spatial4j</groupId>
            <artifactId>spatial4j</artifactId>
            <version>0.7</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.locationtech.jts/jts-core -->
        <dependency>
            <groupId>org.locationtech.jts</groupId>
            <artifactId>jts-core</artifactId>
            <version>1.19.0</version>
        </dependency>

spring-boot-starter-data-elasticsearch即为springboot整合的依赖很好理解,下面两个spatial4j和jts-core为elasticsearch整合springboot的源码里面需要用到的画图类等,需要自己导入,记得注意版本对应,本文springboot版本为2.3.12.RELEASE

2.yml配置

代码如下(示例):

server:
  port: 1121
spring:
  profiles:
    active: dev
  application:
    name: bs-elasticsearch
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
  elasticsearch:
    rest:
      uris: localhost:9200
      username: elastic
      password: elastic
      connection-timeout: 1s
      read-timeout: 30s

3.GeoPoint场景和相关代码

我们现在将无锡新吴新安街道的POI点全部导入elasticsearch,在我们的案例中,POI点的信息包含名称、经纬度坐标、地址、电话、分类。我们获取的POI点是存在csv文件的,那先写个读取csv文件导入elasticsearch的接口。

csv文件部分内容截图

SpringBoot获取本机ip springboot获取当前地理位置_SpringBoot获取本机ip

3.1 索引实体类

@Data
@Document(indexName = "location_poi", createIndex = false)
public class LocationPoiVo implements Serializable {
    private static final long serialVersionUID = 2693696237747348257L;

    @Id
    @JsonFormat(shape = JsonFormat.Shape.STRING)
    private Long id;

    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String name;

    @GeoPointField
    private GeoPoint geoPoint;

    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String address;

    @Field(type = FieldType.Keyword)
    private String phone;

    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String catalog;

}

3.2 创建索引以及映射

public void createIndex() {
        IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(LocationPoiVo.class);
        indexOperations.create();
        Document mapping = indexOperations.createMapping();
        indexOperations.putMapping(mapping);
    }

3.3 读取csv文件内容存入elasticsearch

public void readPoiCsv(MultipartFile file) throws Exception {
        List<LocationPoiVo> locationPoiVoList = new ArrayList<>();
        CsvReadConfig csvReadConfig = new CsvReadConfig();
        csvReadConfig.setContainsHeader(true);
        csvReadConfig.setSkipEmptyRows(false);
        CsvData read = CsvUtil.getReader(csvReadConfig).read(IoUtil.getReader(new InputStreamReader(file.getInputStream())));
        Iterator<CsvRow> iterator = read.iterator();
        while (iterator.hasNext()) {
            try {
                CsvRow csvRow = iterator.next();
                LocationPoiVo locationPoiVo = new LocationPoiVo();
                locationPoiVo.setId(SnowFlakeUtil.getId());
                locationPoiVo.setName(csvRow.get(0));
                locationPoiVo.setAddress(csvRow.get(3));
                locationPoiVo.setPhone(csvRow.get(4));
                locationPoiVo.setCatalog(csvRow.get(5));
                GeoPoint geoPoint = new GeoPoint(Double.parseDouble(csvRow.get(2)), Double.parseDouble(csvRow.get(1)));
                locationPoiVo.setGeoPoint(geoPoint);
                locationPoiVoList.add(locationPoiVo);
            } catch (Exception e) {
                log.info(e.getMessage());
                Assert.isError(e.getMessage());
            }

        }
        elasticsearchRestTemplate.save(locationPoiVoList);
    }

数据准备好了,我们现在试试几个功能

  1. 根据经纬度搜索附近POI点
  2. 根据名称搜索POI点

3.4 根据经纬度搜索附近POI点

查询参数

@Data
public class NearByPoiParam implements Serializable {
    private static final long serialVersionUID = 4865877962216516316L;
	// 经度
    private Double lon;
	// 纬度
    private Double lat; 
	// 默认展示数量
    private Integer size = 15;
	// 默认查找多少(km,m...)范围内的POI点,默认半径为1,单位后面在代码指定
    private Integer distance = 1;
}

代码

public List<GeoLocationPoiVo> getNearByPoi(NearByPoiParam nearByPoiParam) {
        List<GeoLocationPoiVo> geoLocationPoiVoList = new ArrayList<>();

        Pageable pageable = PageRequest.of(0, nearByPoiParam.getSize());

        // 搜索字段为 location
        GeoDistanceQueryBuilder geoBuilder = new GeoDistanceQueryBuilder("geoPoint");
        //指定从哪个位置搜索
        geoBuilder.point(nearByPoiParam.getLat(), nearByPoiParam.getLon());
        //指定搜索多少km
        geoBuilder.distance(nearByPoiParam.getDistance(), DistanceUnit.KILOMETERS);

        // 距离排序
        GeoDistanceSortBuilder sortBuilder = new GeoDistanceSortBuilder("geoPoint", nearByPoiParam.getLat(), nearByPoiParam.getLon());
        //升序
        sortBuilder.order(SortOrder.ASC);

        SearchHits<LocationPoiVo> searchHits = elasticsearchRestTemplate
                .search(new NativeSearchQueryBuilder()
                        .withPageable(pageable)
                        .withFilter(geoBuilder)
                        .withSort(sortBuilder)
                        .build(), LocationPoiVo.class);
        searchHits.forEach(searchHit -> {
            double calculate = GeoDistance.PLANE.calculate(searchHit.getContent().getGeoPoint().getLat(), searchHit.getContent().getGeoPoint().getLon(), nearByPoiParam.getLat(), nearByPoiParam.getLon(), DistanceUnit.METERS);
            GeoLocationPoiVo geoLocationPoiVo = new GeoLocationPoiVo();
            BeanUtils.copyProperties(searchHit.getContent(), geoLocationPoiVo);
            geoLocationPoiVo.setSpecificDistance(calculate + "米");
            geoLocationPoiVoList.add(geoLocationPoiVo);
        });
        return geoLocationPoiVoList;
    }

出参GeoLocationPoiVo

@Data
public class GeoLocationPoiVo implements Serializable {
    private static final long serialVersionUID = 7244264082382770349L;

    private String name;

    @GeoPointField
    private GeoPoint geoPoint;

    private String address;

    private String phone;

    private String catalog;

    private String specificDistance;

}

3.5 调用样例

SpringBoot获取本机ip springboot获取当前地理位置_java_02

3.6 根据名称搜索POI点

查询参数

@Data
public class SearchNamePoiParam implements Serializable {
    private static final long serialVersionUID = 6051650356301418395L;

    private String name;

    private Integer size = 15;
}

代码

public List<GeoLocationPoiVo> searchNamePoi(SearchNamePoiParam searchNamePoiParam) {
        List<GeoLocationPoiVo> geoLocationPoiVoList = new ArrayList<>();
        Pageable pageable = PageRequest.of(0, searchNamePoiParam.getSize());
        SearchHits<LocationPoiVo> searchHits = elasticsearchRestTemplate
                .search(new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchQuery("name", searchNamePoiParam.getName()))
                        .withPageable(pageable)
                        .build(), LocationPoiVo.class);
        searchHits.forEach(searchHit -> {
            GeoLocationPoiVo geoLocationPoiVo = new GeoLocationPoiVo();
            BeanUtils.copyProperties(searchHit.getContent(), geoLocationPoiVo);
            geoLocationPoiVoList.add(geoLocationPoiVo);
        });
        return geoLocationPoiVoList;
    }

3.7 调用样例

SpringBoot获取本机ip springboot获取当前地理位置_SpringBoot获取本机ip_03

4.GeoShape场景和相关代码

同样的,我们将全国的行政区域范围的经纬度导入到elasticsearch中,导入到elasticsearch的部分数据截图如下

SpringBoot获取本机ip springboot获取当前地理位置_java_04


coordinates 里面是行政区域边界的坐标点集合,数量较多就不展示了。

4.1 根据经纬度查询行政区域

出参GeoLocationAreaVo

@Data
public class GeoLocationAreaVo implements Serializable {
    private static final long serialVersionUID = 3044495894780971134L;

    private List<String> areaNameList;

    private List<String> areaCodeList;

    @GeoPointField
    private GeoPoint geoWkt;

    private List<Object> coordinates;
}

代码

public GeoLocationAreaVo geoByLonAndLat(Double lon, Double lat) throws IOException {
        GeoLocationAreaVo geoLocationAreaVo = new GeoLocationAreaVo();
        List<String> areaNameList = new ArrayList<>();
        List<String> areaCodeList = new ArrayList<>();
        List<GeoLocationVo> geoLocationVoList = new ArrayList<>();
        GeoShapeQueryBuilder geoShapeQueryBuilder = QueryBuilders.geoShapeQuery("coordinates", PointBuilder.newPoint(lon, lat).buildGeometry());
        SearchHits<GeoLocationVo> searchHits = elasticsearchRestTemplate.search(new NativeSearchQueryBuilder()
                .withFilter(geoShapeQueryBuilder)
                .build(), GeoLocationVo.class);
        searchHits.getSearchHits().forEach(searchHit -> {
            GeoLocationVo geoLocationVo = searchHit.getContent();
            geoLocationVoList.add(geoLocationVo);
        });
        List<GeoLocationVo> geoLocationVos = geoLocationVoList.stream().sorted(Comparator.comparing(GeoLocationVo::getDeep)).collect(Collectors.toList());
        geoLocationVos.forEach(geoLocationVo -> {
            areaNameList.add(geoLocationVo.getName());
            areaCodeList.add(CommonUtil.formatNumber(geoLocationVo.getAreaId(), 9));
        });
        geoLocationAreaVo.setAreaNameList(areaNameList);
        geoLocationAreaVo.setAreaCodeList(areaCodeList);
        geoLocationAreaVo.setGeoWkt(geoLocationVos.get(geoLocationVos.size() - 1).getGeo_wkt());
        geoLocationAreaVo.setCoordinates(geoLocationVos.get(geoLocationVos.size() - 1).getCoordinates());
        return geoLocationAreaVo;
    }

4.2 调用样例

SpringBoot获取本机ip springboot获取当前地理位置_List_05


三、总结

上述为elasticsearch里面简单的地理位置的用法,欢迎大家在评论区交流