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

news2024/9/22 4:31:18

目录

 实现功能点

技术选型

具体实现

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. 配置地图参数,初始化显示地图。

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

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

 添加商户:前端

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

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

// 地理编码(地址->坐标),如下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);
            }
        })
    })
}

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

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

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

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

// 根据经纬度坐标获取详细地址(逆地理编码(坐标->地址))
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的库索引结构。

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中文文档不全面。

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.12.1</version>
</dependency>

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

// 将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)));
    }
}
// 实体类,对应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;

}
// 新增接口
@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,实现当前用户地理坐标的查询。

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

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

// 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. 前端传入用户坐标和搜索半径获取商家数据展示。

// 使用圆形过滤,过滤出当前用户指定距离的附近酒店
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)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2086353.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GPT实现的adb shell命令实现某音自动点赞和关注

摘要:这个可能是没啥用的自动点赞和关注功能,自娱自乐为主哈 具体可行性步骤如下: 1.打开手机的开发者选项,将指针位置的设置开关打开,目的是看触屏时的坐标值 2.随便打开一个抖音,找到点赞的爱心图标的坐标轴,并记下来,待会有用 备注:尽量获取爱心尖端的坐标值,由…

erlang学习:用OTP构建系统2,警报管理

今日学习用OTP构建系统的警报管理&#xff0c; 首先进行配置错误记录器 [{sasl,[{sasl_error_logger, false},{error_logger_mf_dir, "/code/erlang/erlangstudy"},{error_logger_mf_maxbytes, 10485760},{error_logger_mf_maxfiles, 10}]} ].警报处理器gen_event的…

【Material-UI】Slider 组件中的 Discrete Sliders 详解

文章目录 一、Slider 组件概述1. 组件介绍2. Discrete Sliders 的特点 二、Discrete Sliders 的基本用法1. step 属性2. marks 属性3. valueLabelDisplay 属性 三、深入理解 Discrete Sliders 的配置1. 自定义刻度标记2. 限制可选值3. 设置较小的步长4. 始终显示值标签 四、应用…

Win10+GTX1050Ti安装Pytorch

目的 本文主要记录自己安装pytorch过程。 环境&#xff1a;win10 pycharm 显卡&#xff1a;GTX1050Ti 过程记录 1、确认pytorch版本 打开pytorch官网&#xff1a;https://pytorch.org/ 选择stable 2.3.0 版本&#xff0c;CUDA有11.8及12.1&#xff0c;我们看看GTX1050Ti支持…

发红包案例(java)

User类创建 public class User {private String name;private int money;public User(){}public User(String name,int money){this.namename;this.moneymoney;}public void show(){System.out.println("Name:"name" Money:"money);}public String getNam…

ruoyi-vue-pro(v3)

启动上的问题 这里第二部npm install会有问题 切换成http 解决SSL 证书问题导致的连接错误 npm config set registry http://registry.npm.taobao.org/

Zookeeper 官方示例2-SyncPrimitive 代码解读(二)

测试命令 java jar .\ZookeeperDemo-0.0.1-SNAPSHOT.jar bTest 192.168.206.100:2181 2 1. Barrier&#xff08;阻塞原语&#xff09; 1.1 概念 [!quote] A barrier is a primitive that enables a group of processes to synchronize the beginning and the end of a comput…

猫头虎 分享:Python库 NumPy 的简介、安装、用法详解入门教程

猫头虎 分享&#xff1a;Python库 NumPy 的简介、安装、用法详解入门教程 &#x1f431;&#x1f42f; 摘要 在Python编程领域&#xff0c;特别是人工智能和数据科学方向&#xff0c;NumPy库的重要性不言而喻。 作为一个强大且广泛使用的库&#xff0c;NumPy为我们提供了处理…

WLAN原理实验简述——AP上线

一、需求&#xff1a; AP通过AC上线。 AC通过控制VLAN管理AP,创建VLAN100和放行。 AP同AC建立CAPWAP关系。 二、实验拓扑图&#xff1a; 三、实验步骤&#xff1a; LSW1: sys Enter system view, return user view with CtrlZ. [Huawei]Sysname lsw1 [lsw1]undo info enable I…

vue-ueditor-wrap设置autoHeightEnabled:true无效问题

问题描述 今天小伙伴遇到一个问题&#xff0c;使用vue-ueditor-wrap富文本编辑器&#xff0c;发现设置autoHeightEnabled为true后&#xff0c;对于某些文章&#xff0c;编辑器的高度依然没有按照实际的文章内容高度进行变化&#xff1a; 问题排查 通过调试代码发现是文章html…

C语言实现经典排序算法

1.排序的概念及其运用 1.1排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性&#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记…

SAP BW/BPC:实现自动执行BPC跑包程序

作者 idan lian 如需转载备注出处 如果对你有帮助&#xff0c;请点赞收藏~~~ 用途&#xff1a;创建程序&#xff0c;跑BPC包&#xff0c;把数据从BW应用层跑到BPC,程序可放到处理链或自动作业中&#xff0c;实现定时跑包。 1.步骤 首先需要BPC顾问创建一个他们手动执行的包…

数据挖掘之分类算法

分类算法是数据挖掘中常用的一类算法&#xff0c;其主要任务是根据已知的训练数据&#xff08;即带有标签的数据&#xff09;构建模型&#xff0c;然后利用该模型对新的数据进行分类。分类算法广泛应用于金融、医疗、市场营销等领域&#xff0c;用于预测、决策支持等任务。以下…

并查集【算法 12】

并查集 (Union-Find) 的基础概念与实现 并查集&#xff08;Union-Find&#xff09;是一种用于处理不相交集合&#xff08;disjoint sets&#xff09;的数据结构&#xff0c;常用于解决连通性问题。典型的应用场景包括动态连通性问题&#xff08;如网络节点连通性检测&#xff0…

数据库sqlite3

数据库 数组、链表、变量 ----->内存&#xff1a;程序运行结束&#xff0c;掉电数据丢失 文件 ----------------------->硬盘&#xff1a;程序运行结束&#xff0c;掉电数据不丢失 数据库&#xff1a;专业存储数据、大量数据 ----->硬盘 常用数据库&#xff1a; …

linux 如何查看cpu核心数量

在Linux系统中&#xff0c;有多种方法可以查看CPU的核心数量。 一、lscpu lscpu命令是最直接的方法之一&#xff0c;它可以显示CPU架构信息&#xff0c;包括CPU数量、每个CPU的核心数、每个核心的线程数等。要查看CPU核心数量&#xff0c;可以直接查看lscpu命令输出的Core(s) …

力扣面试150 删除排序链表中的重复元素 II 哑兵 双指针

Problem: 82. 删除排序链表中的重复元素 II &#x1f468;‍&#x1f3eb; 灵神题解 Code /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* List…

企业车辆|基于SprinBoot+vue的企业车辆管理系统(源码+数据库+文档)

企业车辆管理系统 基于SprinBootvue的企业车辆管理系统 一、前言 二、系统设计 三、系统功能设计 系统功能实现 后台模块实现 管理员模块实现 驾驶员模块实现 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主…

悬浮翻译软件有哪些?试试这些利器

在观看外国电影或电视剧的奇幻旅程中&#xff0c;面对字幕如流星般划过屏幕&#xff0c;是否渴望能即时捕捉每一个细微的情感涟漪与幽默火花&#xff0c;让体验更加完整无憾&#xff1f; 此刻&#xff0c;无需再为语言障碍而烦恼&#xff01;悬浮翻译器电脑版作为你贴心的跨文…

新买的笔记本只有一个C盘,进行磁盘分区的操作

开始是这样的: 快捷键 window x 找到磁盘管理 102,400M 100GB 然后右键重命名磁盘名字 最终得到结果如下: