ES配合高德地图JS-API实现地理位置查询

目录

实现功能点

技术选型

具体实现

[Vue3整合高德地图JS API-2.0](#Vue3整合高德地图JS API-2.0)

添加商户:前端

添加商户:后端/ES

查询用户当前地理坐标

获取附近(指定距离)的商户

总结/测试Demo代码地址


测试概述: 用户使用高德地图组件获取商户详细地址和地理坐标(经纬度)存入ES中,然后获取当前用户的地理坐标位置 ,用户可定义查询半径,用来搜索以用户位置为中心点,指定半径构成搜索圆形区域搜索出 包含在圆形区域内的商户(信息包含排序后的距离)。

实现功能点

  • **商户入驻选址:**商户使用地图组件标记来选择地址,获取商户选择的地址信息和地理位置传入后端,后端将信息存储到ES中。

  • 获取用户当前坐标:浏览器地理定位API:navigator.geolocation,需要用户在弹出授权框中赋予位置权限。

  • 显示商户距离: 使用ES中的地理查询(圆形过滤)geo_distance类型进行查询附近(指定半径)商户,并且在sort中对于商户远近进行排序。

技术选型

**前端:**Vite,Vue3.x ,Pinia,ElementPlus

**后端:**Spring Boot3.x , ElasticSearch7.x

**地图相关服务提供:**高德地图JS API 2.0

具体实现

Vue3整合高德地图JS API-2.0

1. 安装依赖包:npm i @amap/amap-jsapi-loader ,用于创建地图等相关对象。

2.配置key密钥

关于安全问题:一种解决可以将key密钥放到Vite环境变量中,然后组件内引用。

2.1注册高德开放平台账号,创建应用获取key和密钥。

访问:lbs.amap.com

注册账号,进入【我的应用】创建服务平台为为Web端的应用。

如果项目需要跑线上,为了保证我们的Key不暴露在代码中,可以将key和密钥信息放到环境变量中,或者使用代理服务器,在代理服务器处配置上密钥。

高德关于key安全说明准备-入门-教程-地图 JS API 1.4|高德地图API (amap.com)

**3.**配置地图参数,初始化显示地图。

javascript 复制代码
<template>
    <!-- 高德地图容器 -->
    <div ref="mapRef" id="map_container"></div>
</template>

<script setup>
import { load } from '@amap/amap-jsapi-loader';
import { onMounted, ref } from 'vue';

// 对应地图渲染的 ref 元素 <div ref="mapRef" />
const mapRef = ref()

// 配置key和密钥(取自环境变量)
const mapApiKey = reactive({
    securityJsCode: import.meta.env.VITE_APP_AMAP_SECURITY_JS_CODE,
    key: import.meta.env.VITE_APP_AMAP_API_KEY
})

// 地图相关对象
const map = ref()
let mapO = {};

// 设置地图默认显示的中心点(经度,纬度)
let centerLnglat = ref([109.428071, 24.326442]);


const initMapView = async () => {
    try {
        // 配置安全密钥
        window._AMapSecurityConfig = {
            securityJsCode: mapApiKey.securityJsCode
        }
        // 给map赋予操作构造器
        map.value = await load({
            key: mapApiKey.key,
            version: '2.0',  // 指定要加载的 JSAPI 的版本,默认为 1.4.15
            plugins: ['AMap.Scale'] //需要使用的的插件列表,多个插件用逗号分隔
        })
        // 获取地图对象
        mapO = new map.value.Map(mapRef.value, {
            viewMode: '3D', // 是否为3D地图模式
            zoom: 11, // 初始化地图级别
            center: centerLnglat.value, // 中心地理坐标
            resizeEnable: true
        })

        // 添加点击事件,创建标记,后续用到
        mapO.on('click', OnPoint)
    } catch (error) {
        console.log(error)
    }
}

onMounted(() => {
    // 执行地图初始化,显示地图
    initMapView()
})

</script>

编写完如上初始化地图代码之后,在地图容器中就能看到地图了。

添加商户:前端

过程:用户点击右上角"加入商户",显示表单填写商户信息,以及显示高德地图组件,根据用户填写的省市位置重载地图组件中心点,当用户标记地图中指定位置 时,获取标记位置的坐标 ,然后调用【逆地理编码(坐标->地址)】 接口将地理坐标转换为详细地址显示。用户可适当修改详细地址后,提交-新增完毕。

根据用户输入字段【省】和【市】重载地图中心点(正向地理编码):

javascript 复制代码
// 地理编码(地址->坐标),如下map.value在上方初始化地图代码中有定义和赋值
function selectLnglat() {
    map.value.plugin('AMap.Geocoder', function () {
        var geocoder = new map.value.Geocoder({
            city: info.address  // info.address变量就是省市内容
        })
        geocoder.getLocation(info.address, function (status, result) {
            if (status === 'complete' && result.info === 'OK') {
                // result中对应详细地理坐标信息,将地图赋予新地理中心点重载地图
                centerLnglat.value[0] = result.geocodes[0].location.lng;
                centerLnglat.value[1] = result.geocodes[0].location.lat;
                initMapView();
                console.log(result);
            } else {
                console.log(status);
            }
        })
    })
}

标记获取坐标,坐标获取地址(逆地理编码)代码:

javascript 复制代码
<script setup>
// 如上初始化地图代码中声明了:mapO.on('click', OnPoint),给地图添加了点击事件。

// 地图点击处理器,创建标记点
function OnPoint(e) {
    // 获取点击位置的经纬度
    const { lng, lat } = e.lnglat;

    // 处理标记点显示偏移问题
    // 如下map.value在上方初始化地图代码中有定义并赋值
    const pixel = mapO.lngLatToContainer(e.lnglat); // 获取地图的像素坐标
    const offset = new map.value.Pixel(-2, -12);// 图标的偏移量(根据图标的实际大小来设置)
    // 计算新的像素坐标
    const newPixel = new map.value.Pixel(pixel.getX() + offset.getX(), pixel.getY() + offset.getY());
    // 将新的像素坐标转换为经纬度
    const newLngLat = mapO.containerToLngLat(newPixel);

    // 创建标记点
    const marker = new map.value.Marker({
        position: newLngLat, // 使用调整后的经纬度位置,标记点图标显示位置
        title: '标记点', // 鼠标悬停时显示的文字
        icon: 'http://webapi.amap.com/theme/v1.3/markers/n/mark_b.png', // 自定义标记图标 URL(可选)
        offset: offset // 偏移量
    });

    // 将标记点添加到地图上
    marker.setMap(mapO);

    // 使用经纬度查询出详细地址(逆地理查询)
    getDetailAddress(newLngLat.lng, newLngLat.lat);

    // 将地理坐标存储,先纬度后经度,因为es存储String类型的地理坐标与WKT相反
    // 后续将此坐标存在es中,作为商户的地理坐标位置进行距离搜索
    info.setLatlng(newLngLat.lat+","+newLngLat.lng);

    // 输出经纬度坐标
    console.log("点击经纬度:", lng, lat);
    console.log("调整后的经纬度:", newLngLat.lat, newLngLat.lng);
}   

</script>

其中有标记点显示时偏移的调整处理,如果在实际开发中发现偏移可适当调整数值。

逆地理编码(根据用户的标记点,推出详细地址) :

javascript 复制代码
// 根据经纬度坐标获取详细地址(逆地理编码(坐标->地址))
function getDetailAddress(lng, lat) {
    map.value.plugin('AMap.Geocoder', function () {
        var geocoder = new map.value.Geocoder({
            city: info.address  // 这个值填写:城市名称,或城市代码都可
        })
        // 构造坐标参数数组
        let agrsPotin = [lng, lat];
        geocoder.getAddress(agrsPotin, function (status, result) {
            if (status === 'complete' && result.info === 'OK') {
                // result.regeocode.formattedAddress就是推出的地址
                // 将详细地址存入pinia中,在表单组件中取出展示
                info.setDetailAddress(result.regeocode.formattedAddress)
            } else {
                console.log("获取地址失败,", status);
            }
        })
    })
}

添加商户:后端/ES

创建ES的库索引,引入依赖并创建新增接口,接收前端传入的商户信息参数,将数据存储ES中。

1.创建ES的库索引结构。

bash 复制代码
PUT /hotels
{
  "mappings": {
    "properties": {
      "id":{"type": "keyword"},
      "picture": { "type": "keyword" },
      "name": { "type": "text","analyzer": "ik_smart"},
      "score": { "type": "float"},
      "distance": { "type": "float"},
      "desc": { "type": "text" },
      "newPrice": { "type": "float" },
      "oldPrice": { "type": "float" },
      "saleMonth": { "type": "integer" },
      "detailAddress":{"type": "text"},
      "location": { 
        "type": "geo_point" 
      }
    }
  }
}

2.SpringBoot引入ES依赖

推荐引入8以下的,8以上版本Java客户端变化太大,API中文文档不全面。

XML 复制代码
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.12.1</version>
</dependency>

3.编写对应实体类,和新增接口

java 复制代码
// 将ES客户端加入Ioc容器
@Configuration
public class ElasticsearchConfig {
    @Value("${esLInfo}") // 连接es信息定义在.yml配置文件中,如http://ip:9200
    private String esLInfo;

    @Bean
    public RestHighLevelClient createEsClient(){
        return new RestHighLevelClient(RestClient.builder(HttpHost.create(esLInfo)));
    }
}
java 复制代码
// 实体类,对应es库索引
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Hotel {

    private String id;

    private String picture;

    private String name;

    private Float score;

    private String detailAddress;

    private String desc;

    private Float newPrice;

    private Float oldPrice;

    private Integer saleMonth;

    private String location; // geo_point类型,格式为 "lat,lon"

    private String detailAddress;

}
java 复制代码
// 新增接口
@PostMapping
public String addHandle(@RequestBody Hotel hotel) throws IOException {
    // 构建新增请求
    IndexRequest request = new IndexRequest("hotels").id(hotel.getId());
    // 准备数据
    request.source(gson.toJson(hotel), XContentType.JSON);
    IndexResponse index = client.index(request, RequestOptions.DEFAULT);
    log.info(index+"===");
    return "新增成功";
}

查询用户当前地理坐标

使用浏览器内置函数:navigator.geolocation,实现当前用户地理坐标的查询。

弹出授权框,用户授权后可获取。

javascript 复制代码
<script setup>
    // 当前用户的地理坐标
	const position = ref(null);

	function getUserLocation(){
		if (navigator.geolocation) {
		navigator.geolocation.getCurrentPosition(
		  (pos) => {
			position.value = {
			  lat: pos.coords.latitude,
			  lng: pos.coords.longitude,
			};
			// 根据用户地理坐标获取周围指定距离的酒店
			getNearHotel(position.value);
			// console.log("当前用户地理位置:",position.value);
		  },
		  (err) => {
			error.value = err.message;
		  }
		);
	  } else {
		error.value = '浏览器不支持';
	  }
	}
</script>

获取附近(指定距离)的商户

1.编写后端接口,根据用户当前位置 和想要查询的半径获取包含在圆形区域的内商户信息。

javascript 复制代码
// searchVo参数封装定义三个参数:经度,纬度,半径,前端传入值
@PostMapping("/searchByLocation")
public List<Map<String, Object>> searchHotelsByLocation(
        @RequestBody SearchVo searchVo
        ) throws IOException {

    // 构建查询请求
    SearchRequest searchRequest = new SearchRequest("hotels");

    // 构建查询DSL
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    boolQuery.filter(QueryBuilders.geoDistanceQuery("location")
            .point(searchVo.getLat(), searchVo.getLon())
            .distance(searchVo.getRadiusKm(), DistanceUnit.KILOMETERS));

    sourceBuilder.query(boolQuery);
    sourceBuilder.sort(SortBuilders.geoDistanceSort("location", searchVo.getLat(), searchVo.getLon())
            .order(SortOrder.ASC) // 实现升序排序,距离越近越靠前
            .unit(DistanceUnit.KILOMETERS));

    searchRequest.source(sourceBuilder);

    // 执行查询
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
    SearchHits hits = searchResponse.getHits();
    List<Map<String, Object>> hotels = new ArrayList<>();

    for (SearchHit hit : hits) {
        Map<String, Object> hotel = hit.getSourceAsMap();
        // 可以获取距离信息(如果需要显示距离)
        double distance = (double) hit.getSortValues()[0];
        hotel.put("distance_km", distance);
        hotels.add(hotel);
    }

    return hotels;
}

2.前端传入用户坐标和搜索半径获取商家数据展示。

javascript 复制代码
// 使用圆形过滤,过滤出当前用户指定距离的附近酒店
async function getNearHotel(userLocation){
    // 定义搜索半径,实际可绑定表单组件让用户选择。
    let radiusKm = 3.0;
    
    // 构造查询参数Vo
    let searchArgs = {
        lat:userLocation.lat,
        lon:userLocation.lng,
        radiusKm:radiusKm
    }

    const res = await axios.post("http://localhost:9009/es/searchByLocation",searchArgs);
    console.log("查询到"+ radiusKm + "公里内,存在"+res.data.length+"家酒店/旅社");
    // 处理距离小数点,保留逗号后两位
    for(let item of res.data){
        item.distance_km = item.distance_km.toFixed(2);
    }
    // 赋值,显示搜索到的酒店
    cardsD.value = res.data
}

总结/测试Demo代码地址

至此,使用高德地图API配合ES完成地理位置查询功能点,测试完毕。

测试Demo完整代码地址gitee.com/maohe101/map-es-demo

更多高德地图的JS API 可查看文档:概述-地图 JS API 2.0|高德地图API (amap.com)

相关推荐
我是小酒4 小时前
掌握 Spring:从新手到高手的常见问题汇总
java·后端·spring·springboot
陈逸子风6 小时前
.net core8 使用JWT鉴权(附当前源码)
vue3·webapi·权限·流程
Lill_bin9 小时前
ElasticSearch底层原理解析
大数据·分布式·elasticsearch·搜索引擎·zookeeper·云原生·jenkins
Flying_Fish_roe21 小时前
Spring Boot- 配置中心问题
spring boot·后端·elasticsearch
SelectDB技术团队1 天前
查询性能提升 10 倍、存储空间节省 65%,Apache Doris 半结构化数据分析方案及典型场景
数据结构·数据仓库·elasticsearch·log4j·json
Chen_leilei1 天前
ES机制原理
大数据·elasticsearch·搜索引擎
西岭千秋雪_1 天前
谷粒商城のElasticsearch
java·大数据·服务器·spring boot·elasticsearch·搜索引擎
赚钱给孩子买茅台喝1 天前
智能BI项目第一期
java·人工智能·springboot·react
花晓木1 天前
python清除一个月以前的ES索引文档数据
开发语言·python·elasticsearch
好想有猫猫1 天前
【Git】常见命令(仅笔记)
linux·c++·笔记·git·elasticsearch